From a5128fd7e6e5b8893a8cb92119aeb1148249e797 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Apr 2021 00:38:23 -0400 Subject: [PATCH 01/22] dwarfvet: minor logic cleanup - Use at() to crash immediately on out-of-range errors instead of introducing memory corruption (see #1824) - Replace custom implementation of df::unit::find() - Use range-based for with get_vector() where appropriate --- plugins/dwarfvet.cpp | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index e178ffed3..d8f555781 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -159,7 +159,7 @@ bool AnimalHospital::acceptPatient(int32_t id, color_ostream &out) { // Yup, let's find the next open spot, // and give it to our patient - int spot_cur = 0; // fuck the STL for requiring a second counter to make this usable + int spot_cur = 0; for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { if (*spot == false) { *spot = true; @@ -324,7 +324,7 @@ void AnimalHospital::calculateHospital(bool force, color_ostream &out) { /* Start marking! */ for (int i = 0; i < building_height; i++) { for (int j = 0; j < building_length; j++) { - spots_in_use[spot_cur+j] = true; + spots_in_use.at(spot_cur+j) = true; } // Wind the cursor to the start of the next row @@ -348,7 +348,7 @@ void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { if ( (*accepted_patient)->getID() == id) { out.print("Discharging unit %d from hospital %d\n", id, this->id); // Reclaim their spot - spots_in_use[(*accepted_patient)->getSpotIndex()] = false; + spots_in_use.at((*accepted_patient)->getSpotIndex()) = false; this->spots_open++; delete (*accepted_patient); this->accepted_patients.erase(accepted_patient); @@ -371,17 +371,7 @@ void AnimalHospital::processPatients(color_ostream &out) { // Where the magic happens for (vector::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { int id = (*patient)->getID(); - df::unit * real_unit = nullptr; - // Appears the health bits can get freed/realloced too -_-;, Find the unit from the main - // index and check it there. - auto units = world->units.all; - - for ( size_t a = 0; a < units.size(); a++ ) { - real_unit = units[a]; - if (real_unit->id == id) { - break; - } - } + df::unit *real_unit = df::unit::find(id); // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed if (!real_unit || !Units::isActive(real_unit) || !real_unit->health->flags.bits.needs_healthcare) { @@ -474,8 +464,6 @@ void tickHandler(color_ostream& out, void* data) { return; CoreSuspender suspend; int32_t own_race_id = df::global::ui->race_id; - int32_t own_civ_id = df::global::ui->civ_id; - auto units = world->units.all; /** * Generate a list of animal hospitals on the map @@ -496,8 +484,7 @@ void tickHandler(color_ostream& out, void* data) { // Walk the building tree, and generate a list of animal hospitals on the map - for (size_t b =0 ; b < world->buildings.all.size(); b++) { - df::building* building = world->buildings.all[b]; + for (df::building* building : df::building::get_vector()) { if (isActiveAnimalHospital(building)) { hospitals_on_map.push_back(building); } @@ -621,9 +608,7 @@ void tickHandler(color_ostream& out, void* data) { */ processUnits: /* Code borrowed from petcapRemover.cpp */ - for ( size_t a = 0; a < units.size(); a++ ) { - df::unit* unit = units[a]; - + for (df::unit *unit : df::unit::get_vector()) { /* As hilarious as it would be, lets not treat FB :) */ if ( !Units::isActive(unit) || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) { continue; From 1c3e60337c72b9b3970238e3457a2e0ee3cdc397 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Apr 2021 23:23:31 -0400 Subject: [PATCH 02/22] Switch to range-based for --- plugins/dwarfvet.cpp | 78 ++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index d8f555781..e0e9c9a3f 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -145,8 +145,8 @@ AnimalHospital::AnimalHospital(df::building * building, color_ostream &out) { AnimalHospital::~AnimalHospital() { // Go through and delete all the patients - for (vector::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { - delete (*accepted_patient); + for (Patient* accepted_patient : this->accepted_patients) { + delete accepted_patient; } } @@ -193,9 +193,9 @@ void AnimalHospital::reportUsage(color_ostream &out) { // Debugging tool to see parts of the hospital in use int length_cursor = this->length; - for (vector::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { - if (*spot) out.print("t"); - if (!(*spot)) out.print("f"); + for (bool spot : this->spots_in_use) { + if (spot) out.print("t"); + else out.print("f"); length_cursor--; if (length_cursor < 0) { out.print("\n"); @@ -369,14 +369,14 @@ void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { void AnimalHospital::processPatients(color_ostream &out) { // Where the magic happens - for (vector::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { - int id = (*patient)->getID(); + for (Patient *patient : this->accepted_patients) { + int id = patient->getID(); df::unit *real_unit = df::unit::find(id); // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed if (!real_unit || !Units::isActive(real_unit) || !real_unit->health->flags.bits.needs_healthcare) { // discharge the patient from the hospital - this->dischargePatient(*patient, out); + this->dischargePatient(patient, out); return; } @@ -386,9 +386,9 @@ void AnimalHospital::processPatients(color_ostream &out) { df::job * job = new df::job; DFHack::Job::linkIntoWorld(job); - job->pos.x = (*patient)->returnX(); - job->pos.y = (*patient)->returnY(); - job->pos.z = (*patient)->returnZ(); + job->pos.x = patient->returnX(); + job->pos.y = patient->returnY(); + job->pos.z = patient->returnZ(); job->flags.bits.special = 1; job->job_type = df::enums::job_type::Rest; df::general_ref *ref = df::allocate(); @@ -408,8 +408,8 @@ void delete_animal_hospital_vector(color_ostream &out) { if (dwarfvet_enabled) { out.print("Clearing all animal hospitals\n"); } - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { - delete (*animal_hospital); + for (AnimalHospital *animal_hospital : animal_hospital_zones) { + delete animal_hospital; } animal_hospital_zones.clear(); } @@ -518,7 +518,7 @@ void tickHandler(color_ostream& out, void* data) { // Now walk our list of known hospitals, do a bit of checking, then compare // TODO: this doesn't handle zone resizes at all - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { + for (AnimalHospital *animal_hospital : animal_hospital_zones) { // If a zone is changed at all, DF seems to reallocate it. // // Each AnimalHospital has a "to_be_deleted" bool. We're going to set that to true, and clear it if we can't @@ -527,13 +527,13 @@ void tickHandler(color_ostream& out, void* data) { // // Surviving hospitals will be copied to scratch which will become the new AHZ vector - (*animal_hospital)->to_be_deleted = true; - for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + animal_hospital->to_be_deleted = true; + for (df::building *current_hospital : hospitals_on_map) { /* Keep the hospital if its still valid */ - if ((*animal_hospital)->getID() == (*current_hospital)->id) { - ahz_scratch.push_back(*animal_hospital); - (*animal_hospital)->to_be_deleted = false; + if (animal_hospital->getID() == current_hospital->id) { + ahz_scratch.push_back(animal_hospital); + animal_hospital->to_be_deleted = false; break; } @@ -543,20 +543,20 @@ void tickHandler(color_ostream& out, void* data) { // Report what we're deleting by checking the to_be_deleted bool. // // Whatsever left is added to the pending add list - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { - if ((*animal_hospital)->to_be_deleted) { - out.print("Hospital #%d removed\n", (*animal_hospital)->getID()); - delete *animal_hospital; + for (AnimalHospital *animal_hospital : animal_hospital_zones) { + if (animal_hospital->to_be_deleted) { + out.print("Hospital #%d removed\n", animal_hospital->getID()); + delete animal_hospital; } } /* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */ - for (vector::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { + for (df::building *current_hospital : hospitals_on_map) { bool new_hospital = true; - for (vector::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { - if ((*animal_hospital)->getID() == (*current_hospital)->id) { + for (AnimalHospital *animal_hospital : ahz_scratch) { + if (animal_hospital->getID() == current_hospital->id) { // Next if we're already here new_hospital = false; break; @@ -564,19 +564,19 @@ void tickHandler(color_ostream& out, void* data) { } // Add it if its new - if (new_hospital == true) to_be_added.push_back(*current_hospital); + if (new_hospital == true) to_be_added.push_back(current_hospital); } /* Now add it to the scratch AHZ */ - for (vector::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { + for (df::building *current_hospital : to_be_added) { // Add it to the vector out.print("Adding new hospital #id: %d at x1 %d y1: %d z: %d\n", - (*current_hospital)->id, - (*current_hospital)->x1, - (*current_hospital)->y1, - (*current_hospital)->z + current_hospital->id, + current_hospital->x1, + current_hospital->y1, + current_hospital->z ); - AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); + AnimalHospital * hospital = new AnimalHospital(current_hospital, out); ahz_scratch.push_back(hospital); } @@ -731,9 +731,9 @@ processUnits: } // The final step, process all patients! - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { - (*animal_hospital)->calculateHospital(true, out); - (*animal_hospital)->processPatients(out); + for (AnimalHospital *animal_hospital : animal_hospital_zones) { + animal_hospital->calculateHospital(true, out); + animal_hospital->processPatients(out); } cleanup: @@ -767,9 +767,9 @@ command_result dwarfvet (color_ostream &out, std::vector & paramet } if ( parameters[a] == "report-usage") { out.print("Current animal hospitals are:\n"); - for (vector::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { - (*animal_hospital)->calculateHospital(true, out); - (*animal_hospital)->reportUsage(out); + for (AnimalHospital *animal_hospital : animal_hospital_zones) { + animal_hospital->calculateHospital(true, out); + animal_hospital->reportUsage(out); } return CR_OK; } From 40c2b190834203b92b55f865e54e3dfb26dc0a5d Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Apr 2021 23:28:42 -0400 Subject: [PATCH 03/22] Switch from vector to unordered_set --- plugins/dwarfvet.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index e0e9c9a3f..473eaa260 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -46,6 +46,7 @@ #include "df/world.h" #include +#include #include using namespace DFHack; @@ -60,7 +61,7 @@ DFHACK_PLUGIN_IS_ENABLED(dwarfvet_enabled); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(world); -static vector tracked_units; +static unordered_set tracked_units; static int32_t howOften = 100; struct hospital_spot { @@ -357,12 +358,7 @@ void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) { } // And the master list - for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { - if ((*it) == id) { - tracked_units.erase(it); - break; - } - } + tracked_units.erase(id); return; } @@ -708,12 +704,7 @@ processUnits: */ // Now we need to find if this unit can be accepted at a hospital - bool awareOfUnit = false; - for (vector::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { - if ((*it) == unit->id) { - awareOfUnit = true; - } - } + bool awareOfUnit = tracked_units.count(unit->id); // New unit for dwarfvet to be aware of! if (!awareOfUnit) { // The master list handles all patients which are accepted @@ -722,7 +713,7 @@ processUnits: for (auto animal_hospital : animal_hospital_zones) { if (animal_hospital->acceptPatient(unit->id, out)) { out.print("Accepted patient %d at hospital %d\n", unit->id, animal_hospital->getID()); - tracked_units.push_back(unit->id); + tracked_units.insert(unit->id); break; } } From 14172b7c0f5edc707ddcffb9db83d87b22db1aa9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Apr 2021 23:33:53 -0400 Subject: [PATCH 04/22] Improve readability of report-usage output --- plugins/dwarfvet.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 473eaa260..a51b8cb83 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -195,8 +195,8 @@ void AnimalHospital::reportUsage(color_ostream &out) { int length_cursor = this->length; for (bool spot : this->spots_in_use) { - if (spot) out.print("t"); - else out.print("f"); + if (spot) out.print("X"); + else out.print("-"); length_cursor--; if (length_cursor < 0) { out.print("\n"); @@ -748,8 +748,7 @@ command_result dwarfvet (color_ostream &out, std::vector & paramet } if ( parameters[a] == "report") { out.print("Current animal hospitals are:\n"); - for (size_t b =0 ; b < world->buildings.all.size(); b++) { - df::building* building = world->buildings.all[b]; + for (df::building *building : df::building::get_vector()) { if (isActiveAnimalHospital(building)) { out.print(" at x1: %d, x2: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z); } From 7b1d7a7ff9828161afd3bebbb60e7d3efaaa486e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Apr 2021 23:35:11 -0400 Subject: [PATCH 05/22] Fix off-by-one error in report-usage --- plugins/dwarfvet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index a51b8cb83..8ca2fe0d3 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -198,7 +198,7 @@ void AnimalHospital::reportUsage(color_ostream &out) { if (spot) out.print("X"); else out.print("-"); length_cursor--; - if (length_cursor < 0) { + if (length_cursor <= 0) { out.print("\n"); length_cursor = this->length; } From e4de302048c94706b1019aa762f73f57787b14ba Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Apr 2021 00:59:43 -0400 Subject: [PATCH 06/22] Simplify building overlap calculations significantly Fixes #1824 --- plugins/dwarfvet.cpp | 62 ++++++-------------------------------------- 1 file changed, 8 insertions(+), 54 deletions(-) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 8ca2fe0d3..30308bc57 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -277,62 +277,16 @@ void AnimalHospital::calculateHospital(bool force, color_ostream &out) { // NOTE: under some conditions, this generates a false warning. Not a lot I can do about it // Mark spots used by that building as used; FIXME: handle special logic for traction benches and such - int building_offset_x = building->x1 - this->x1; - int building_offset_y = building->y1 - this->y1; - int building_length = building->x2 - building->x1 + 1; - int building_height = building->y2 - building->y1 + 1; - - // Cap the used calculation to only include the part in the hospital - if (this->x1 > building->x1) { - building_offset_x -= (this->x1 - building->x1); - } - - if (this->y1 > building->y1) { - building_offset_y -= (building->y1 - this->y1); - } - - if ((this->x2 < building->x2) && building_offset_x) { - building_length -= (this->x2 - building->x2) + 1; - } - - if ((this->y2 < building->y2) && building_offset_y) { - building_height = (building->y2 - this->y2) + 1; - } - - // Quick explination, if a building is north or east of the activity zone, - // we get a negative offset, we'll just skip those lines below. If its - // south or west, we make the building length/height lower to compinsate. - - /* if we have a negative x offset, we correct that */ - if (building_offset_x < 0) { - building_height += building_offset_x; - building_offset_x = 0; - } - - /* Handle negative y offset */ - if (building_offset_y < 0) { - building_length += building_offset_y; - building_offset_y = 0; - }; - - /* Advance the pointer to first row we need to mark */ - int spot_cur = 0; - if (building_offset_y) { - spot_cur = (length+1) * building_offset_y; - } - - spot_cur += building_offset_x; - /* Start marking! */ - for (int i = 0; i < building_height; i++) { - for (int j = 0; j < building_length; j++) { - spots_in_use.at(spot_cur+j) = true; + int overlap_x1 = std::max(building->x1, this->x1); + int overlap_y1 = std::max(building->y1, this->y1); + int overlap_x2 = std::min(building->x2, this->x2); + int overlap_y2 = std::min(building->y2, this->y2); + for (int x = overlap_x1; x <= overlap_x2; x++) { + for (int y = overlap_y1; y <= overlap_y2; y++) { + int spot_index = (x - this->x1) + (this->length * (y - this->y1)); + spots_in_use.at(spot_index) = true; } - - // Wind the cursor to the start of the next row - spot_cur += length+1; } - - // *phew*, done. Now repeat the process for the next building! } } From 9b416a8662f3d816c9b42dfcfb4c5b3509f59882 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 25 Apr 2021 09:43:40 -0700 Subject: [PATCH 07/22] speed up dig blueprint creation by 70% remove the unneeded cache layer. the cache is for writing. we're just reading. all the cache is doing is adding latency as it makes its copies of map data structures. generating a 190x190x100 dig blueprint: before change: 1.7s after change: 1.0s the performance gains aren't as important here as the reduced complexity of the algorithm, though. for reasonably-sized blueprints, the time savings are unnoticeable. --- plugins/blueprint.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 4653b469c..2bc39df29 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -1,6 +1,9 @@ -//Blueprint -//By cdombroski -//Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort +/** + * Translates a region of tiles specified by the cursor and arguments/prompts + * into a series of blueprint files suitable for replay via quickfort. + * + * Written by cdombroski. + */ #include #include @@ -43,7 +46,7 @@ command_result blueprint(color_ostream &out, vector ¶meters); DFhackCExport command_result plugin_init(color_ostream &out, vector &commands) { - commands.push_back(PluginCommand("blueprint", "Convert map tiles into a blueprint", blueprint, false)); + commands.push_back(PluginCommand("blueprint", "Record the structure of a live game map in a quickfort blueprint", blueprint, false)); return CR_OK; } @@ -76,10 +79,9 @@ pair get_building_size(df::building* b) return pair(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1); } -char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) +char get_tile_dig(int32_t x, int32_t y, int32_t z) { - df::tiletype tt = mc.tiletypeAt(DFCoord(x, y, z)); - df::tiletype_shape ts = tileShape(tt); + df::tiletype_shape ts = tileShape(*Maps::getTileType(x, y , z)); switch (ts) { case tiletype_shape::EMPTY: @@ -102,7 +104,6 @@ char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) return 'r'; default: return ' '; - } } @@ -629,7 +630,6 @@ command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t ph end.z++; } - MapExtras::MapCache mc; for (int32_t z = start.z; z < end.z; z++) { for (int32_t y = start.y; y < end.y; y++) @@ -644,7 +644,7 @@ command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t ph if (phases & BUILD) build << get_tile_build(x, y, b) << ','; if (phases & DIG) - dig << get_tile_dig(mc, x, y, z) << ','; + dig << get_tile_dig(x, y, z) << ','; } if (phases & QUERY) query << "#" << endl; From e516cf1a8009bbdf9f9c292d273da7b28bc37cc3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 25 Apr 2021 10:12:00 -0700 Subject: [PATCH 08/22] remove MapCache header include --- plugins/blueprint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 2bc39df29..9d788a076 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -11,11 +11,11 @@ #include #include #include "LuaTools.h" +#include "TileTypes.h" #include "modules/Buildings.h" #include "modules/Filesystem.h" #include "modules/Gui.h" -#include "modules/MapCache.h" #include "df/building_axle_horizontalst.h" #include "df/building_bridgest.h" From e28bbd3a2055da6b2a92b7f629c464602842540e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 25 Apr 2021 10:17:51 -0700 Subject: [PATCH 09/22] don't crash for invalid coordinates --- plugins/blueprint.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 9d788a076..f460cdb40 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -81,7 +81,8 @@ pair get_building_size(df::building* b) char get_tile_dig(int32_t x, int32_t y, int32_t z) { - df::tiletype_shape ts = tileShape(*Maps::getTileType(x, y , z)); + df::tiletype *tt = Maps::getTileType(x, y , z); + df::tiletype_shape ts = tileShape(tt ? *tt : tiletype::Void); switch (ts) { case tiletype_shape::EMPTY: From 7848771b1a896e4469e1ad193af4caf15b846383 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 4 May 2021 12:35:22 -0700 Subject: [PATCH 10/22] fix label misspelling in tiletypes --- plugins/tiletypes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 31ff096f1..7487ef649 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -316,7 +316,7 @@ std::ostream &operator<<(std::ostream &stream, const TileType &paint) needSpace = false; } - stream << (paint.dig ? "DESIGNATED" : "UNDESIGATNED"); + stream << (paint.dig ? "DESIGNATED" : "UNDESIGNATED"); used = true; needSpace = true; } From d288bc6bde7ddae48e62ac91e3ff385788ac6db9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 19 Apr 2021 11:26:13 -0700 Subject: [PATCH 11/22] implement 'fortress' unit test mode - implement navigation function for loading a fortress from the title screen (requires a fortress save to be ready in region1/). - ensure we don't try repeatedly to enter a mode that we can't reach (such as getting back to the title screen from fortress mode). failing to enter the mode once will skip all remaining tests in that mode. --- ci/test.lua | 111 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 23 deletions(-) diff --git a/ci/test.lua b/ci/test.lua index b69a27712..1df3ce999 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -66,8 +66,6 @@ local TestStatus = { FAILED = 'failed', } -local VALID_MODES = utils.invert{'none', 'title', 'fortress'} - local function delay(frames) frames = frames or 1 script.sleep(frames, 'frames') @@ -116,29 +114,87 @@ end test_envvars.require = clean_require test_envvars.reqscript = clean_reqscript +local function is_title_screen(scr) + scr = scr or dfhack.gui.getCurViewscreen() + return df.viewscreen_titlest:is_instance(scr) +end + +-- This only handles pre-fortress-load screens. It will time out if the player +-- has already loaded a fortress or is in any screen that can't get to the title +-- screen by sending ESC keys. local function ensure_title_screen() - if df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then - return - end - print('Looking for title screen...') - for i = 0, 100 do + for i = 1, 100 do local scr = dfhack.gui.getCurViewscreen() - if df.viewscreen_titlest:is_instance(scr) then + if is_title_screen(scr) then print('Found title screen') - break - else + return + end + scr:feed_key(df.interface_key.LEAVESCREEN) + delay(10) + if i % 10 == 0 then print('Looking for title screen...') end + end + qerror(string.format('Could not find title screen (timed out at %s)', + dfhack.gui.getCurFocus(true))) +end + +local function is_fortress(focus_string) + focus_string = focus_string or dfhack.gui.getCurFocus(true) + return focus_string == 'dwarfmode/Default' +end + +-- Requires that a fortress game is already loaded or is ready to be loaded via +-- the "Continue Playing" option in the title screen. Otherwise the function +-- will time out and/or exit with error. +local function ensure_fortress() + local focus_string = dfhack.gui.getCurFocus(true) + for screen_timeout = 1,10 do + if is_fortress(focus_string) then + print('Loaded fortress map') + -- pause the game (if it's not already paused) + dfhack.gui.resetDwarfmodeView(true) + return + end + local scr = dfhack.gui.getCurViewscreen() + if focus_string == 'title' then + scr:feed_key(df.interface_key.SELECT) + scr:feed_key(df.interface_key.SELECT) + elseif focus_string == 'dfhack/lua/load_screen' or + focus_string == 'dfhack/lua' then + scr:feed_key(df.interface_key.SELECT) + elseif focus_string == 'new_region' or + focus_string == 'adopt_region' then + qerror('Please ensure a fortress save exists in region1/') + elseif focus_string ~= 'loadgame' then + -- if we're not actively loading a game, assume we're in + -- a loaded fortress, but in some subscreen scr:feed_key(df.interface_key.LEAVESCREEN) + end + -- wait for active screen to change + local prev_focus_string = focus_string + for frame_timeout = 1,100 do delay(10) + focus_string = dfhack.gui.getCurFocus(true) + if focus_string ~= prev_focus_string then + goto next_screen + end + if frame_timeout % 10 == 0 then + print(string.format( + 'Loading fortress (currently at screen: %s)', + focus_string)) + end end + print('Timed out waiting for screen to change') + break + ::next_screen:: end - if not df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then - error('Could not find title screen') - end + qerror(string.format('Could not load fortress (timed out at %s)', + focus_string)) end -local MODE_NAVIGATE_FNS = { - none = function() end, - title = ensure_title_screen, +local MODES = { + none = {order=1, detect=function() return true end}, + title = {order=2, detect=is_title_screen, navigate=ensure_title_screen}, + fortress = {order=3, detect=is_fortress, navigate=ensure_fortress}, } local function load_test_config(config_file) @@ -266,7 +322,7 @@ local function load_tests(file, tests) dfhack.printerr('Error when running file: ' .. tostring(err)) return false else - if not VALID_MODES[env.config.mode] then + if not MODES[env.config.mode] then dfhack.printerr('Invalid config.mode: ' .. tostring(env.config.mode)) return false end @@ -290,10 +346,9 @@ local function sort_tests(tests) local test_index = utils.invert(tests) table.sort(tests, function(a, b) if a.config.mode ~= b.config.mode then - return VALID_MODES[a.config.mode] < VALID_MODES[b.config.mode] - else - return test_index[a] < test_index[b] + return MODES[a.config.mode].order < MODES[b.config.mode].order end + return test_index[a] < test_index[b] end) end @@ -418,9 +473,19 @@ end local function run_tests(tests, status, counts) print(('Running %d tests'):format(#tests)) for _, test in pairs(tests) do - MODE_NAVIGATE_FNS[test.config.mode]() - local passed = run_test(test, status, counts) - status[test.full_name] = passed and TestStatus.PASSED or TestStatus.FAILED + status[test.full_name] = TestStatus.FAILED + if MODES[test.config.mode].failed then goto skip end + if not MODES[test.config.mode].detect() then + local ok, err = pcall(MODES[test.config.mode].navigate) + if not ok then + MODES[test.config.mode].failed = true + dfhack.printerr(tostring(err)) + goto skip + end + end + status[test.full_name] = run_test(test, status, counts) and + TestStatus.PASSED or TestStatus.FAILED + ::skip:: save_test_status(status) end From f8baba2cfd8601c029d5c8c7eea207e57c71dc35 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 19 Apr 2021 11:29:56 -0700 Subject: [PATCH 12/22] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index b1d409fda..3c9a3b6f5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - The DFHack test harness is now much easier to use for iterative development. Configuration can now be specified on the commandline, there are more test filter options, and the test harness can now easily rerun tests that have been run before. - The ``test/main`` command to invoke the test harness has been renamed to just ``test`` - DFHack unit tests must now match any output expected to be printed via ``dfhack.printerr()`` +- ``fortress mode`` is now supported for unit tests (tests that require a fortress map to be loaded) # 0.47.05-r1 From eaf0722cdff0592767c9f7f3a357a4bf8e21769e Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 19 Apr 2021 20:16:58 -0700 Subject: [PATCH 13/22] make save_dir configurable, use load-save to load --- ci/test.lua | 63 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/ci/test.lua b/ci/test.lua index 1df3ce999..9812b94b3 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -27,14 +27,26 @@ Options: -d, --test_dir specifies which directory to look in for tests. defaults to the "hack/scripts/test" folder in your DF installation. -m, --modes only run tests in the given comma separated list of modes. - valid modes are 'none' (test can be run on any screen) and - 'title' (test must be run on the DF title screen). if not - specified, no modes are filtered. + see the next section for a list of valid modes. if not + specified, the tests are not filtered by modes. -r, --resume skip tests that have already been run. remove the test_status.json file to reset the record. + -s, --save_dir the save folder to load for "fortress" mode tests. this + save is only loaded if a fort is not already loaded when + a "fortress" mode test is run. if not specified, defaults to + 'region1'. -t, --tests only run tests that match one of the comma separated list of patterns. if not specified, no tests are filtered. +Modes: + + none the test can be run on any screen + title the test must be run on the DF title screen. note that if the game + has a map loaded, "title" mode tests cannot be run + fortress the test must be run while a map is loaded. if the game is + currently on the title screen, the save specified by the save_dir + parameter will be loaded. + Examples: test runs all tests @@ -145,7 +157,7 @@ end -- Requires that a fortress game is already loaded or is ready to be loaded via -- the "Continue Playing" option in the title screen. Otherwise the function -- will time out and/or exit with error. -local function ensure_fortress() +local function ensure_fortress(config) local focus_string = dfhack.gui.getCurFocus(true) for screen_timeout = 1,10 do if is_fortress(focus_string) then @@ -155,21 +167,17 @@ local function ensure_fortress() return end local scr = dfhack.gui.getCurViewscreen() - if focus_string == 'title' then - scr:feed_key(df.interface_key.SELECT) - scr:feed_key(df.interface_key.SELECT) - elseif focus_string == 'dfhack/lua/load_screen' or - focus_string == 'dfhack/lua' then - scr:feed_key(df.interface_key.SELECT) - elseif focus_string == 'new_region' or - focus_string == 'adopt_region' then - qerror('Please ensure a fortress save exists in region1/') + if focus_string == 'title' or + focus_string == 'dfhack/lua/load_screen' then + -- qerror()'s on falure + dfhack.run_script('load-save', config.save_dir) elseif focus_string ~= 'loadgame' then - -- if we're not actively loading a game, assume we're in - -- a loaded fortress, but in some subscreen + -- if we're not actively loading a game, hope we're in + -- a screen where hitting ESC will get us to the game map + -- or the title screen scr:feed_key(df.interface_key.LEAVESCREEN) end - -- wait for active screen to change + -- wait for current screen to change local prev_focus_string = focus_string for frame_timeout = 1,100 do delay(10) @@ -207,6 +215,10 @@ local function load_test_config(config_file) config.test_dir = dfhack.getHackPath() .. 'scripts/test' end + if not config.save_dir then + config.save_dir = 'region1' + end + return config end @@ -470,13 +482,13 @@ local function filter_tests(tests, config) return status end -local function run_tests(tests, status, counts) +local function run_tests(tests, status, counts, config) print(('Running %d tests'):format(#tests)) for _, test in pairs(tests) do status[test.full_name] = TestStatus.FAILED if MODES[test.config.mode].failed then goto skip end if not MODES[test.config.mode].detect() then - local ok, err = pcall(MODES[test.config.mode].navigate) + local ok, err = pcall(MODES[test.config.mode].navigate, config) if not ok then MODES[test.config.mode].failed = true dfhack.printerr(tostring(err)) @@ -511,17 +523,19 @@ local function run_tests(tests, status, counts) end local function main(args) - local help, resume, test_dir, mode_filter, test_filter = - false, false, nil, {}, {} + local help, resume, test_dir, mode_filter, save_dir, test_filter = + false, false, nil, {}, nil, {} local other_args = utils.processArgsGetopt(args, { {'h', 'help', handler=function() help = true end}, {'d', 'test_dir', hasArg=true, - handler=function(arg) test_dir = arg end}, + handler=function(arg) test_dir = arg end}, {'m', 'modes', hasArg=true, - handler=function(arg) mode_filter = arg:split(',') end}, + handler=function(arg) mode_filter = arg:split(',') end}, {'r', 'resume', handler=function() resume = true end}, + {'s', 'save_dir', hasArg=true, + handler=function(arg) save_dir = arg end}, {'t', 'tests', hasArg=true, - handler=function(arg) test_filter = arg:split(',') end}, + handler=function(arg) test_filter = arg:split(',') end}, }) if help then print(help_text) return end @@ -532,6 +546,7 @@ local function main(args) -- override config with any params specified on the commandline if test_dir then config.test_dir = test_dir end if resume then config.resume = true end + if save_dir then config.save_dir = save_dir end if #mode_filter > 0 then config.modes = mode_filter end if #test_filter > 0 then config.tests = test_filter end if #done_command > 0 then config.done_command = done_command end @@ -555,7 +570,7 @@ local function main(args) script.start(function() dfhack.call_with_finalizer(1, true, finish_tests, config.done_command, - run_tests, tests, status, counts) + run_tests, tests, status, counts, config) end) end From e07635b345171dd24888429d359950977a32e98e Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 19 Apr 2021 22:18:42 -0700 Subject: [PATCH 14/22] don't record test status for unreachable tests and print a summary line for how many tests were unreachable --- ci/test.lua | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ci/test.lua b/ci/test.lua index 9812b94b3..96b4aeabd 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -484,21 +484,28 @@ end local function run_tests(tests, status, counts, config) print(('Running %d tests'):format(#tests)) + local num_skipped = 0 for _, test in pairs(tests) do - status[test.full_name] = TestStatus.FAILED - if MODES[test.config.mode].failed then goto skip end + if MODES[test.config.mode].failed then + num_skipped = num_skipped + 1 + goto skip + end if not MODES[test.config.mode].detect() then local ok, err = pcall(MODES[test.config.mode].navigate, config) if not ok then MODES[test.config.mode].failed = true dfhack.printerr(tostring(err)) + num_skipped = num_skipped + 1 goto skip end end - status[test.full_name] = run_test(test, status, counts) and - TestStatus.PASSED or TestStatus.FAILED - ::skip:: + if run_test(test, status, counts) then + status[test.full_name] = TestStatus.PASSED + else + status[test.full_name] = TestStatus.FAILED + end save_test_status(status) + ::skip:: end local function print_summary_line(ok, message) @@ -518,6 +525,8 @@ local function run_tests(tests, status, counts, config) ('%d/%d checks passed'):format(counts.checks_ok, counts.checks)) print_summary_line(counts.file_errors == 0, ('%d test files failed to load'):format(counts.file_errors)) + print_summary_line(num_skipped == 0, + ('%d tests in unreachable modes'):format(num_skipped)) save_test_status(status) end From 7060c297a8f35e6da0007538b4ea2b16a059481e Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 5 May 2021 12:51:53 -0700 Subject: [PATCH 15/22] disable fortress mode tests in CI for now until we get a reuable fortress save set up --- docs/changelog.txt | 2 +- travis/run-tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 3c9a3b6f5..aed34dd46 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,7 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - The DFHack test harness is now much easier to use for iterative development. Configuration can now be specified on the commandline, there are more test filter options, and the test harness can now easily rerun tests that have been run before. - The ``test/main`` command to invoke the test harness has been renamed to just ``test`` - DFHack unit tests must now match any output expected to be printed via ``dfhack.printerr()`` -- ``fortress mode`` is now supported for unit tests (tests that require a fortress map to be loaded) +- ``fortress mode`` is now supported for unit tests (tests that require a fortress map to be loaded). Note that fortress mode tests will be skipped by the continuous integration framework for now. They'll be re-enabled once we get a reusable fortress save set up. # 0.47.05-r1 diff --git a/travis/run-tests.py b/travis/run-tests.py index 400a7141d..132631903 100755 --- a/travis/run-tests.py +++ b/travis/run-tests.py @@ -73,7 +73,7 @@ with open(test_init_file, 'w') as f: f.write(''' devel/dump-rpc dfhack-rpc.txt :lua dfhack.internal.addScriptPath(dfhack.getHackPath()) - test --resume "lua scr.breakdown_level=df.interface_breakdown_types.%s" + test --resume --modes=none,title "lua scr.breakdown_level=df.interface_breakdown_types.%s" ''' % ('NONE' if args.no_quit else 'QUIT')) test_config_file = 'test_config.json' From dfe4ddee962c121725ecce8f3705a332c155a894 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 7 May 2021 15:19:34 -0700 Subject: [PATCH 16/22] ignore dismissed screens when checking for fort --- ci/test.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/test.lua b/ci/test.lua index 96b4aeabd..8172e2e2c 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -166,7 +166,7 @@ local function ensure_fortress(config) dfhack.gui.resetDwarfmodeView(true) return end - local scr = dfhack.gui.getCurViewscreen() + local scr = dfhack.gui.getCurViewscreen(true) if focus_string == 'title' or focus_string == 'dfhack/lua/load_screen' then -- qerror()'s on falure From 9254db5a4db6cfffdae8aa969152fd1ca87db568 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 8 May 2021 07:21:08 +0000 Subject: [PATCH 17/22] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f1f26d1a9..50f4bd781 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f1f26d1a9aea3a5dd940ac2a8e768ade9590a86f +Subproject commit 50f4bd7819372b12c8b887e3649f226fd553c3c5 From 26b148923506ba5c9a69ae815eed9e131196b292 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 8 May 2021 07:41:08 -0700 Subject: [PATCH 18/22] make isPlanModeEnabled() consider enable_all --- docs/changelog.txt | 1 + plugins/buildingplan.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index b1d409fda..1cad0a823 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -35,6 +35,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `buildingplan`: fixed an issue where planned constructions designated with DF's sizing keys (``umkh``) would sometimes be larger than requested +- `buildingplan`: make ``isPlanModeEnabled()`` always return true if "enable all" is set. this fixes the `automaterial` plugin interface when building constructions and buildingplan's "enable all" setting is set. - `command-prompt`: fixed issues where overlays created by running certain commands (e.g. `gui/liquids`, `gui/teleport`) would not update the parent screen correctly - ``quickfortress.csv`` blueprint: fixed refuse stockpile config and prevented stockpiles from covering stairways diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 6e18219f4..f82f7d40a 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1087,7 +1087,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) static bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom) { - return planmode_enabled[toBuildingTypeKey(type, subtype, custom)]; + return is_planmode_enabled(toBuildingTypeKey(type, subtype, custom)); } static bool isPlannableBuilding(df::building_type type, From 4968d97c22b2f0cbc21e65b2d7a97a5388f5d099 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 8 May 2021 22:12:07 -0400 Subject: [PATCH 19/22] test/structures/find: avoid overwriting other potential config values --- test/structures/find.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/structures/find.lua b/test/structures/find.lua index 57a659afa..c01f071e1 100644 --- a/test/structures/find.lua +++ b/test/structures/find.lua @@ -1,6 +1,4 @@ -config = { - mode = 'title', -} +config.mode = 'title' local function clean_vec(vec) while #vec > 0 do From dc1cfb32b915119360b54a2bc1e5e2077da1a779 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 May 2021 00:15:41 -0400 Subject: [PATCH 20/22] Adjust changelog --- docs/changelog.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 9333fc86c..dc48e9109 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -35,7 +35,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `buildingplan`: fixed an issue where planned constructions designated with DF's sizing keys (``umkh``) would sometimes be larger than requested -- `buildingplan`: make ``isPlanModeEnabled()`` always return true if "enable all" is set. this fixes the `automaterial` plugin interface when building constructions and buildingplan's "enable all" setting is set. +- `buildingplan`: fixed an issue preventing other plugins like `automaterial` from planning constructions if the "enable all" buildingplan setting was turned on - `command-prompt`: fixed issues where overlays created by running certain commands (e.g. `gui/liquids`, `gui/teleport`) would not update the parent screen correctly - ``quickfortress.csv`` blueprint: fixed refuse stockpile config and prevented stockpiles from covering stairways @@ -44,7 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Lua - ``gui.Painter``: fixed error when calling ``viewport()`` method -- `xlsxreader`: Added Lua class wrappers for the xlsxreader plugin API +- `xlsxreader`: added Lua class wrappers for the xlsxreader plugin API ## Documentation - Added more client library implementations to the `remote interface docs ` @@ -53,7 +53,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - The DFHack test harness is now much easier to use for iterative development. Configuration can now be specified on the commandline, there are more test filter options, and the test harness can now easily rerun tests that have been run before. - The ``test/main`` command to invoke the test harness has been renamed to just ``test`` - DFHack unit tests must now match any output expected to be printed via ``dfhack.printerr()`` -- ``fortress mode`` is now supported for unit tests (tests that require a fortress map to be loaded). Note that fortress mode tests will be skipped by the continuous integration framework for now. They'll be re-enabled once we get a reusable fortress save set up. +- Fortress mode is now supported for unit tests (allowing tests that require a fortress map to be loaded) - note that these tests are skipped by continuous integration for now, pending a suitable test fortress # 0.47.05-r1 From c07ca1f3241c0b723aee1914bcf202f8cb1dae5d Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 May 2021 00:27:21 -0400 Subject: [PATCH 21/22] Update changelog (#1826, #1824) --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index dc48e9109..5a5f56538 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: fixed an issue where planned constructions designated with DF's sizing keys (``umkh``) would sometimes be larger than requested - `buildingplan`: fixed an issue preventing other plugins like `automaterial` from planning constructions if the "enable all" buildingplan setting was turned on - `command-prompt`: fixed issues where overlays created by running certain commands (e.g. `gui/liquids`, `gui/teleport`) would not update the parent screen correctly +- `dwarfvet`: fixed a crash that could occur with hospitals overlapping with other buildings in certain ways - ``quickfortress.csv`` blueprint: fixed refuse stockpile config and prevented stockpiles from covering stairways ## Misc Improvements From 82ca4c35a5c9e1a671e6e8eee85337ace39fcf7b Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 10 May 2021 00:36:03 -0400 Subject: [PATCH 22/22] Force maximum of Sphinx 3 for now See #1851 --- .github/workflows/build.yml | 2 +- docs/Documentation.rst | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 869ca407e..db6310d71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -111,7 +111,7 @@ jobs: python-version: 3 - name: Install dependencies run: | - pip install sphinx + pip install 'sphinx<4' - name: Clone DFHack uses: actions/checkout@v1 with: diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 5e1f8c599..1698038bc 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -127,6 +127,10 @@ Required dependencies In order to build the documentation, you must have Python with Sphinx version |sphinx_min_version| or later. Python 3 is recommended. +.. warning:: + + Sphinx 4 is currently not supported. See :issue:`1851` for details. + When installing Sphinx from OS package managers, be aware that there is another program called Sphinx, completely unrelated to documentation management. Be sure you are installing the right Sphinx; it may be called ``python-sphinx``,