Merge branch 'develop' into myk_negative_number_params

develop
Myk 2021-05-09 21:46:07 -07:00 committed by GitHub
commit 654b3e9c56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 185 deletions

@ -111,7 +111,7 @@ jobs:
python-version: 3 python-version: 3
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install sphinx pip install 'sphinx<4'
- name: Clone DFHack - name: Clone DFHack
uses: actions/checkout@v1 uses: actions/checkout@v1
with: with:

@ -27,14 +27,26 @@ Options:
-d, --test_dir specifies which directory to look in for tests. defaults to -d, --test_dir specifies which directory to look in for tests. defaults to
the "hack/scripts/test" folder in your DF installation. the "hack/scripts/test" folder in your DF installation.
-m, --modes only run tests in the given comma separated list of modes. -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 see the next section for a list of valid modes. if not
'title' (test must be run on the DF title screen). if not specified, the tests are not filtered by modes.
specified, no modes are filtered.
-r, --resume skip tests that have already been run. remove the -r, --resume skip tests that have already been run. remove the
test_status.json file to reset the record. 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 -t, --tests only run tests that match one of the comma separated list of
patterns. if not specified, no tests are filtered. 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: Examples:
test runs all tests test runs all tests
@ -66,8 +78,6 @@ local TestStatus = {
FAILED = 'failed', FAILED = 'failed',
} }
local VALID_MODES = utils.invert{'none', 'title', 'fortress'}
local function delay(frames) local function delay(frames)
frames = frames or 1 frames = frames or 1
script.sleep(frames, 'frames') script.sleep(frames, 'frames')
@ -116,29 +126,83 @@ end
test_envvars.require = clean_require test_envvars.require = clean_require
test_envvars.reqscript = clean_reqscript 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() local function ensure_title_screen()
if df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then for i = 1, 100 do
return
end
print('Looking for title screen...')
for i = 0, 100 do
local scr = dfhack.gui.getCurViewscreen() local scr = dfhack.gui.getCurViewscreen()
if df.viewscreen_titlest:is_instance(scr) then if is_title_screen(scr) then
print('Found title screen') print('Found title screen')
break return
else 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(config)
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(true)
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, 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) scr:feed_key(df.interface_key.LEAVESCREEN)
end
-- wait for current screen to change
local prev_focus_string = focus_string
for frame_timeout = 1,100 do
delay(10) 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 end
print('Timed out waiting for screen to change')
break
::next_screen::
end end
if not df.viewscreen_titlest:is_instance(dfhack.gui.getCurViewscreen()) then qerror(string.format('Could not load fortress (timed out at %s)',
error('Could not find title screen') focus_string))
end
end end
local MODE_NAVIGATE_FNS = { local MODES = {
none = function() end, none = {order=1, detect=function() return true end},
title = ensure_title_screen, 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) local function load_test_config(config_file)
@ -151,6 +215,10 @@ local function load_test_config(config_file)
config.test_dir = dfhack.getHackPath() .. 'scripts/test' config.test_dir = dfhack.getHackPath() .. 'scripts/test'
end end
if not config.save_dir then
config.save_dir = 'region1'
end
return config return config
end end
@ -266,7 +334,7 @@ local function load_tests(file, tests)
dfhack.printerr('Error when running file: ' .. tostring(err)) dfhack.printerr('Error when running file: ' .. tostring(err))
return false return false
else 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)) dfhack.printerr('Invalid config.mode: ' .. tostring(env.config.mode))
return false return false
end end
@ -290,10 +358,9 @@ local function sort_tests(tests)
local test_index = utils.invert(tests) local test_index = utils.invert(tests)
table.sort(tests, function(a, b) table.sort(tests, function(a, b)
if a.config.mode ~= b.config.mode then if a.config.mode ~= b.config.mode then
return VALID_MODES[a.config.mode] < VALID_MODES[b.config.mode] return MODES[a.config.mode].order < MODES[b.config.mode].order
else
return test_index[a] < test_index[b]
end end
return test_index[a] < test_index[b]
end) end)
end end
@ -415,13 +482,30 @@ local function filter_tests(tests, config)
return status return status
end end
local function run_tests(tests, status, counts) local function run_tests(tests, status, counts, config)
print(('Running %d tests'):format(#tests)) print(('Running %d tests'):format(#tests))
local num_skipped = 0
for _, test in pairs(tests) do for _, test in pairs(tests) do
MODE_NAVIGATE_FNS[test.config.mode]() if MODES[test.config.mode].failed then
local passed = run_test(test, status, counts) num_skipped = num_skipped + 1
status[test.full_name] = passed and TestStatus.PASSED or TestStatus.FAILED 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
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) save_test_status(status)
::skip::
end end
local function print_summary_line(ok, message) local function print_summary_line(ok, message)
@ -441,22 +525,26 @@ local function run_tests(tests, status, counts)
('%d/%d checks passed'):format(counts.checks_ok, counts.checks)) ('%d/%d checks passed'):format(counts.checks_ok, counts.checks))
print_summary_line(counts.file_errors == 0, print_summary_line(counts.file_errors == 0,
('%d test files failed to load'):format(counts.file_errors)) ('%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) save_test_status(status)
end end
local function main(args) local function main(args)
local help, resume, test_dir, mode_filter, test_filter = local help, resume, test_dir, mode_filter, save_dir, test_filter =
false, false, nil, {}, {} false, false, nil, {}, nil, {}
local other_args = utils.processArgsGetopt(args, { local other_args = utils.processArgsGetopt(args, {
{'h', 'help', handler=function() help = true end}, {'h', 'help', handler=function() help = true end},
{'d', 'test_dir', hasArg=true, {'d', 'test_dir', hasArg=true,
handler=function(arg) test_dir = arg end}, handler=function(arg) test_dir = arg end},
{'m', 'modes', hasArg=true, {'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}, {'r', 'resume', handler=function() resume = true end},
{'s', 'save_dir', hasArg=true,
handler=function(arg) save_dir = arg end},
{'t', 'tests', hasArg=true, {'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 if help then print(help_text) return end
@ -467,6 +555,7 @@ local function main(args)
-- override config with any params specified on the commandline -- override config with any params specified on the commandline
if test_dir then config.test_dir = test_dir end if test_dir then config.test_dir = test_dir end
if resume then config.resume = true 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 #mode_filter > 0 then config.modes = mode_filter end
if #test_filter > 0 then config.tests = test_filter end if #test_filter > 0 then config.tests = test_filter end
if #done_command > 0 then config.done_command = done_command end if #done_command > 0 then config.done_command = done_command end
@ -490,7 +579,7 @@ local function main(args)
script.start(function() script.start(function()
dfhack.call_with_finalizer(1, true, dfhack.call_with_finalizer(1, true,
finish_tests, config.done_command, finish_tests, config.done_command,
run_tests, tests, status, counts) run_tests, tests, status, counts, config)
end) end)
end end

@ -127,6 +127,10 @@ Required dependencies
In order to build the documentation, you must have Python with Sphinx In order to build the documentation, you must have Python with Sphinx
version |sphinx_min_version| or later. Python 3 is recommended. 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 When installing Sphinx from OS package managers, be aware that there is
another program called Sphinx, completely unrelated to documentation management. another program called Sphinx, completely unrelated to documentation management.
Be sure you are installing the right Sphinx; it may be called ``python-sphinx``, Be sure you are installing the right Sphinx; it may be called ``python-sphinx``,

@ -35,7 +35,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Fixes ## Fixes
- `buildingplan`: fixed an issue where planned constructions designated with DF's sizing keys (``umkh``) would sometimes be larger than requested - `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 - `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 - ``quickfortress.csv`` blueprint: fixed refuse stockpile config and prevented stockpiles from covering stairways
## Misc Improvements ## Misc Improvements
@ -43,10 +45,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Lua ## Lua
- ``gui.Painter``: fixed error when calling ``viewport()`` method - ``gui.Painter``: fixed error when calling ``viewport()`` method
- `xlsxreader`: Added Lua class wrappers for the xlsxreader plugin API
- ``utils.processArgsGetopt()``: now returns negative numbers (e.g. ``-10``) in the list of positional parameters instead of treating it as an option string equivalent to ``-1 -0`` - ``utils.processArgsGetopt()``: now returns negative numbers (e.g. ``-10``) in the list of positional parameters instead of treating it as an option string equivalent to ``-1 -0``
- ``utils.processArgsGetopt()``: now properly handles ``--`` as a marker to treat all further parameters as non-options - ``utils.processArgsGetopt()``: now properly handles ``--`` like GNU ``getopt`` as a marker to treat all further parameters as non-options
- ``utils.processArgsGetopt()``: now detects when a required arguments to long-form options are missing - ``utils.processArgsGetopt()``: now detects when required arguments to long-form options are missing
- `xlsxreader`: Added Lua class wrappers for the xlsxreader plugin API
## Documentation ## Documentation
- Added more client library implementations to the `remote interface docs <remote-client-libs>` - Added more client library implementations to the `remote interface docs <remote-client-libs>`
@ -55,6 +57,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 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`` - 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()`` - DFHack unit tests must now match any output expected to be printed via ``dfhack.printerr()``
- 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 # 0.47.05-r1

@ -1,6 +1,9 @@
//Blueprint /**
//By cdombroski * Translates a region of tiles specified by the cursor and arguments/prompts
//Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort * into a series of blueprint files suitable for replay via quickfort.
*
* Written by cdombroski.
*/
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
@ -8,11 +11,11 @@
#include <Console.h> #include <Console.h>
#include <PluginManager.h> #include <PluginManager.h>
#include "LuaTools.h" #include "LuaTools.h"
#include "TileTypes.h"
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/Filesystem.h" #include "modules/Filesystem.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/MapCache.h"
#include "df/building_axle_horizontalst.h" #include "df/building_axle_horizontalst.h"
#include "df/building_bridgest.h" #include "df/building_bridgest.h"
@ -43,7 +46,7 @@ command_result blueprint(color_ostream &out, vector <string> &parameters);
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &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; return CR_OK;
} }
@ -76,10 +79,10 @@ pair<uint32_t, uint32_t> get_building_size(df::building* b)
return pair<uint32_t, uint32_t>(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1); return pair<uint32_t, uint32_t>(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 *tt = Maps::getTileType(x, y , z);
df::tiletype_shape ts = tileShape(tt); df::tiletype_shape ts = tileShape(tt ? *tt : tiletype::Void);
switch (ts) switch (ts)
{ {
case tiletype_shape::EMPTY: case tiletype_shape::EMPTY:
@ -102,7 +105,6 @@ char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z)
return 'r'; return 'r';
default: default:
return ' '; return ' ';
} }
} }
@ -629,7 +631,6 @@ command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t ph
end.z++; end.z++;
} }
MapExtras::MapCache mc;
for (int32_t z = start.z; z < end.z; z++) for (int32_t z = start.z; z < end.z; z++)
{ {
for (int32_t y = start.y; y < end.y; y++) for (int32_t y = start.y; y < end.y; y++)
@ -644,7 +645,7 @@ command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t ph
if (phases & BUILD) if (phases & BUILD)
build << get_tile_build(x, y, b) << ','; build << get_tile_build(x, y, b) << ',';
if (phases & DIG) if (phases & DIG)
dig << get_tile_dig(mc, x, y, z) << ','; dig << get_tile_dig(x, y, z) << ',';
} }
if (phases & QUERY) if (phases & QUERY)
query << "#" << endl; query << "#" << endl;

@ -1087,7 +1087,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &)
static bool isPlanModeEnabled(df::building_type type, static bool isPlanModeEnabled(df::building_type type,
int16_t subtype, int16_t subtype,
int32_t custom) { 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, static bool isPlannableBuilding(df::building_type type,

@ -46,6 +46,7 @@
#include "df/world.h" #include "df/world.h"
#include <map> #include <map>
#include <unordered_set>
#include <vector> #include <vector>
using namespace DFHack; using namespace DFHack;
@ -60,7 +61,7 @@ DFHACK_PLUGIN_IS_ENABLED(dwarfvet_enabled);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
static vector<int32_t> tracked_units; static unordered_set<int32_t> tracked_units;
static int32_t howOften = 100; static int32_t howOften = 100;
struct hospital_spot { struct hospital_spot {
@ -145,8 +146,8 @@ AnimalHospital::AnimalHospital(df::building * building, color_ostream &out) {
AnimalHospital::~AnimalHospital() { AnimalHospital::~AnimalHospital() {
// Go through and delete all the patients // Go through and delete all the patients
for (vector<Patient*>::iterator accepted_patient = this->accepted_patients.begin(); accepted_patient != this->accepted_patients.end(); accepted_patient++) { for (Patient* accepted_patient : this->accepted_patients) {
delete (*accepted_patient); delete accepted_patient;
} }
} }
@ -159,7 +160,7 @@ bool AnimalHospital::acceptPatient(int32_t id, color_ostream &out) {
// Yup, let's find the next open spot, // Yup, let's find the next open spot,
// and give it to our patient // 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<bool>::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { for (vector<bool>::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) {
if (*spot == false) { if (*spot == false) {
*spot = true; *spot = true;
@ -193,11 +194,11 @@ void AnimalHospital::reportUsage(color_ostream &out) {
// Debugging tool to see parts of the hospital in use // Debugging tool to see parts of the hospital in use
int length_cursor = this->length; int length_cursor = this->length;
for (vector<bool>::iterator spot = this->spots_in_use.begin(); spot != this->spots_in_use.end(); spot++) { for (bool spot : this->spots_in_use) {
if (*spot) out.print("t"); if (spot) out.print("X");
if (!(*spot)) out.print("f"); else out.print("-");
length_cursor--; length_cursor--;
if (length_cursor < 0) { if (length_cursor <= 0) {
out.print("\n"); out.print("\n");
length_cursor = this->length; length_cursor = this->length;
} }
@ -276,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 // 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 // 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 overlap_x1 = std::max(building->x1, this->x1);
int building_offset_y = building->y1 - this->y1; int overlap_y1 = std::max(building->y1, this->y1);
int building_length = building->x2 - building->x1 + 1; int overlap_x2 = std::min(building->x2, this->x2);
int building_height = building->y2 - building->y1 + 1; int overlap_y2 = std::min(building->y2, this->y2);
for (int x = overlap_x1; x <= overlap_x2; x++) {
// Cap the used calculation to only include the part in the hospital for (int y = overlap_y1; y <= overlap_y2; y++) {
if (this->x1 > building->x1) { int spot_index = (x - this->x1) + (this->length * (y - this->y1));
building_offset_x -= (this->x1 - building->x1); spots_in_use.at(spot_index) = true;
}
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[spot_cur+j] = 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!
} }
} }
@ -348,7 +303,7 @@ void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) {
if ( (*accepted_patient)->getID() == id) { if ( (*accepted_patient)->getID() == id) {
out.print("Discharging unit %d from hospital %d\n", id, this->id); out.print("Discharging unit %d from hospital %d\n", id, this->id);
// Reclaim their spot // Reclaim their spot
spots_in_use[(*accepted_patient)->getSpotIndex()] = false; spots_in_use.at((*accepted_patient)->getSpotIndex()) = false;
this->spots_open++; this->spots_open++;
delete (*accepted_patient); delete (*accepted_patient);
this->accepted_patients.erase(accepted_patient); this->accepted_patients.erase(accepted_patient);
@ -357,36 +312,21 @@ void AnimalHospital::dischargePatient(Patient * patient, color_ostream &out) {
} }
// And the master list // And the master list
for (vector<int32_t>::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) { tracked_units.erase(id);
if ((*it) == id) {
tracked_units.erase(it);
break;
}
}
return; return;
} }
void AnimalHospital::processPatients(color_ostream &out) { void AnimalHospital::processPatients(color_ostream &out) {
// Where the magic happens // Where the magic happens
for (vector<Patient*>::iterator patient = this->accepted_patients.begin(); patient != this->accepted_patients.end(); patient++) { for (Patient *patient : this->accepted_patients) {
int id = (*patient)->getID(); int id = patient->getID();
df::unit * real_unit = nullptr; df::unit *real_unit = df::unit::find(id);
// 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;
}
}
// Check to make sure the unit hasn't expired before assigning a job, or if they've been healed // 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) { if (!real_unit || !Units::isActive(real_unit) || !real_unit->health->flags.bits.needs_healthcare) {
// discharge the patient from the hospital // discharge the patient from the hospital
this->dischargePatient(*patient, out); this->dischargePatient(patient, out);
return; return;
} }
@ -396,9 +336,9 @@ void AnimalHospital::processPatients(color_ostream &out) {
df::job * job = new df::job; df::job * job = new df::job;
DFHack::Job::linkIntoWorld(job); DFHack::Job::linkIntoWorld(job);
job->pos.x = (*patient)->returnX(); job->pos.x = patient->returnX();
job->pos.y = (*patient)->returnY(); job->pos.y = patient->returnY();
job->pos.z = (*patient)->returnZ(); job->pos.z = patient->returnZ();
job->flags.bits.special = 1; job->flags.bits.special = 1;
job->job_type = df::enums::job_type::Rest; job->job_type = df::enums::job_type::Rest;
df::general_ref *ref = df::allocate<df::general_ref_unit_workerst>(); df::general_ref *ref = df::allocate<df::general_ref_unit_workerst>();
@ -418,8 +358,8 @@ void delete_animal_hospital_vector(color_ostream &out) {
if (dwarfvet_enabled) { if (dwarfvet_enabled) {
out.print("Clearing all animal hospitals\n"); out.print("Clearing all animal hospitals\n");
} }
for (vector<AnimalHospital*>::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { for (AnimalHospital *animal_hospital : animal_hospital_zones) {
delete (*animal_hospital); delete animal_hospital;
} }
animal_hospital_zones.clear(); animal_hospital_zones.clear();
} }
@ -474,8 +414,6 @@ void tickHandler(color_ostream& out, void* data) {
return; return;
CoreSuspender suspend; CoreSuspender suspend;
int32_t own_race_id = df::global::ui->race_id; 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 * Generate a list of animal hospitals on the map
@ -496,8 +434,7 @@ void tickHandler(color_ostream& out, void* data) {
// Walk the building tree, and generate a list of animal hospitals on the map // 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++) { for (df::building* building : df::building::get_vector()) {
df::building* building = world->buildings.all[b];
if (isActiveAnimalHospital(building)) { if (isActiveAnimalHospital(building)) {
hospitals_on_map.push_back(building); hospitals_on_map.push_back(building);
} }
@ -531,7 +468,7 @@ void tickHandler(color_ostream& out, void* data) {
// Now walk our list of known hospitals, do a bit of checking, then compare // Now walk our list of known hospitals, do a bit of checking, then compare
// TODO: this doesn't handle zone resizes at all // TODO: this doesn't handle zone resizes at all
for (vector<AnimalHospital*>::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. // 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 // Each AnimalHospital has a "to_be_deleted" bool. We're going to set that to true, and clear it if we can't
@ -540,13 +477,13 @@ void tickHandler(color_ostream& out, void* data) {
// //
// Surviving hospitals will be copied to scratch which will become the new AHZ vector // Surviving hospitals will be copied to scratch which will become the new AHZ vector
(*animal_hospital)->to_be_deleted = true; animal_hospital->to_be_deleted = true;
for (vector<df::building*>::iterator current_hospital = hospitals_on_map.begin(); current_hospital != hospitals_on_map.end(); current_hospital++) { for (df::building *current_hospital : hospitals_on_map) {
/* Keep the hospital if its still valid */ /* Keep the hospital if its still valid */
if ((*animal_hospital)->getID() == (*current_hospital)->id) { if (animal_hospital->getID() == current_hospital->id) {
ahz_scratch.push_back(*animal_hospital); ahz_scratch.push_back(animal_hospital);
(*animal_hospital)->to_be_deleted = false; animal_hospital->to_be_deleted = false;
break; break;
} }
@ -556,20 +493,20 @@ void tickHandler(color_ostream& out, void* data) {
// Report what we're deleting by checking the to_be_deleted bool. // Report what we're deleting by checking the to_be_deleted bool.
// //
// Whatsever left is added to the pending add list // Whatsever left is added to the pending add list
for (vector<AnimalHospital*>::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { for (AnimalHospital *animal_hospital : animal_hospital_zones) {
if ((*animal_hospital)->to_be_deleted) { if (animal_hospital->to_be_deleted) {
out.print("Hospital #%d removed\n", (*animal_hospital)->getID()); out.print("Hospital #%d removed\n", animal_hospital->getID());
delete *animal_hospital; delete animal_hospital;
} }
} }
/* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */ /* Now we need to walk the scratch and add anything that is a hospital and wasn't in the vector */
for (vector<df::building*>::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; bool new_hospital = true;
for (vector<AnimalHospital*>::iterator animal_hospital = ahz_scratch.begin(); animal_hospital != ahz_scratch.end(); animal_hospital++) { for (AnimalHospital *animal_hospital : ahz_scratch) {
if ((*animal_hospital)->getID() == (*current_hospital)->id) { if (animal_hospital->getID() == current_hospital->id) {
// Next if we're already here // Next if we're already here
new_hospital = false; new_hospital = false;
break; break;
@ -577,19 +514,19 @@ void tickHandler(color_ostream& out, void* data) {
} }
// Add it if its new // 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 */ /* Now add it to the scratch AHZ */
for (vector<df::building*>::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 // Add it to the vector
out.print("Adding new hospital #id: %d at x1 %d y1: %d z: %d\n", out.print("Adding new hospital #id: %d at x1 %d y1: %d z: %d\n",
(*current_hospital)->id, current_hospital->id,
(*current_hospital)->x1, current_hospital->x1,
(*current_hospital)->y1, current_hospital->y1,
(*current_hospital)->z current_hospital->z
); );
AnimalHospital * hospital = new AnimalHospital(*current_hospital, out); AnimalHospital * hospital = new AnimalHospital(current_hospital, out);
ahz_scratch.push_back(hospital); ahz_scratch.push_back(hospital);
} }
@ -621,9 +558,7 @@ void tickHandler(color_ostream& out, void* data) {
*/ */
processUnits: processUnits:
/* Code borrowed from petcapRemover.cpp */ /* Code borrowed from petcapRemover.cpp */
for ( size_t a = 0; a < units.size(); a++ ) { for (df::unit *unit : df::unit::get_vector()) {
df::unit* unit = units[a];
/* As hilarious as it would be, lets not treat FB :) */ /* 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 ) { if ( !Units::isActive(unit) || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) {
continue; continue;
@ -723,12 +658,7 @@ processUnits:
*/ */
// Now we need to find if this unit can be accepted at a hospital // Now we need to find if this unit can be accepted at a hospital
bool awareOfUnit = false; bool awareOfUnit = tracked_units.count(unit->id);
for (vector<int32_t>::iterator it = tracked_units.begin(); it != tracked_units.end(); it++) {
if ((*it) == unit->id) {
awareOfUnit = true;
}
}
// New unit for dwarfvet to be aware of! // New unit for dwarfvet to be aware of!
if (!awareOfUnit) { if (!awareOfUnit) {
// The master list handles all patients which are accepted // The master list handles all patients which are accepted
@ -737,7 +667,7 @@ processUnits:
for (auto animal_hospital : animal_hospital_zones) { for (auto animal_hospital : animal_hospital_zones) {
if (animal_hospital->acceptPatient(unit->id, out)) { if (animal_hospital->acceptPatient(unit->id, out)) {
out.print("Accepted patient %d at hospital %d\n", unit->id, animal_hospital->getID()); 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; break;
} }
} }
@ -746,9 +676,9 @@ processUnits:
} }
// The final step, process all patients! // The final step, process all patients!
for (vector<AnimalHospital*>::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { for (AnimalHospital *animal_hospital : animal_hospital_zones) {
(*animal_hospital)->calculateHospital(true, out); animal_hospital->calculateHospital(true, out);
(*animal_hospital)->processPatients(out); animal_hospital->processPatients(out);
} }
cleanup: cleanup:
@ -772,8 +702,7 @@ command_result dwarfvet (color_ostream &out, std::vector <std::string> & paramet
} }
if ( parameters[a] == "report") { if ( parameters[a] == "report") {
out.print("Current animal hospitals are:\n"); out.print("Current animal hospitals are:\n");
for (size_t b =0 ; b < world->buildings.all.size(); b++) { for (df::building *building : df::building::get_vector()) {
df::building* building = world->buildings.all[b];
if (isActiveAnimalHospital(building)) { 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); out.print(" at x1: %d, x2: %d, y1: %d, y2: %d, z: %d\n", building->x1, building->x2, building->y1, building->y2, building->z);
} }
@ -782,9 +711,9 @@ command_result dwarfvet (color_ostream &out, std::vector <std::string> & paramet
} }
if ( parameters[a] == "report-usage") { if ( parameters[a] == "report-usage") {
out.print("Current animal hospitals are:\n"); out.print("Current animal hospitals are:\n");
for (vector<AnimalHospital*>::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) { for (AnimalHospital *animal_hospital : animal_hospital_zones) {
(*animal_hospital)->calculateHospital(true, out); animal_hospital->calculateHospital(true, out);
(*animal_hospital)->reportUsage(out); animal_hospital->reportUsage(out);
} }
return CR_OK; return CR_OK;
} }

@ -316,7 +316,7 @@ std::ostream &operator<<(std::ostream &stream, const TileType &paint)
needSpace = false; needSpace = false;
} }
stream << (paint.dig ? "DESIGNATED" : "UNDESIGATNED"); stream << (paint.dig ? "DESIGNATED" : "UNDESIGNATED");
used = true; used = true;
needSpace = true; needSpace = true;
} }

@ -1 +1 @@
Subproject commit f1f26d1a9aea3a5dd940ac2a8e768ade9590a86f Subproject commit 50f4bd7819372b12c8b887e3649f226fd553c3c5

@ -1,6 +1,4 @@
config = { config.mode = 'title'
mode = 'title',
}
local function clean_vec(vec) local function clean_vec(vec)
while #vec > 0 do while #vec > 0 do

@ -73,7 +73,7 @@ with open(test_init_file, 'w') as f:
f.write(''' f.write('''
devel/dump-rpc dfhack-rpc.txt devel/dump-rpc dfhack-rpc.txt
:lua dfhack.internal.addScriptPath(dfhack.getHackPath()) :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')) ''' % ('NONE' if args.no_quit else 'QUIT'))
test_config_file = 'test_config.json' test_config_file = 'test_config.json'