Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,19 @@ Template for new versions:
- In ``Screen`` module, Fix out-of-bounds color table access when Lua pens use ``COLOR_RESET``
- rename the ``stockpiles`` tool tag to ``stockpile`` so it no longer collides with the `stockpiles` plugin in `gui/launcher`
- ``Units::teleport``: update unit occupancy on the full 3x3 footprint of EQUIPMENT units (e.g. wagons) instead of only their center tile
- Fixed persistent site data API (``dfhack.persistent.saveSiteData``/``getSiteData``) failing on newly reclaimed fortresses until the first save
- `3dveins`: fix crash when a tile's biome cannot be resolved
- `3dveins`: fix failure on embarks that cross midmap tiles
- `aquifer`: make ``--skip-top`` and top-relative ``--levels`` take effect for the ``drain``, ``convert``, and ``add`` actions instead of being silently ignored
- `autodump`: ``destroy`` no longer leaves the contents of destroyed containers in limbo or crashes when destroying unit-held items
- `buildingplan`: fix roller material estimate asking for one chain per tile instead of one chain total
- `buildingplan`: fix "Unlink all" only unlinking some mechanisms (or crashing) when freeing mechanisms from the building
- `tiletypes`: remove plants (including trees) rooted on a tile that is painted into something that cannot host them, so cleared ground no longer regrows floating trees
- `timestream`: deal properly with units who have breathing difficulties
- `export-world-map`: fix crash exporting site data for a site whose owner has an undefined race
- `stocks`: overlay now resets scroll position when collapsing categories so the item list is no longer left blank and unscrollable
- Fixed persistent site data API (``dfhack.persistent.saveSiteData``/``getSiteData``) failing on newly reclaimed fortresses until the first save
- `strangemood`: fix crash when ``--id`` is given a non-numeric unit id
- `suspendmanager`: fix out-of-bounds parameter read when ``set`` is given too few arguments

## Misc Improvements
- Added ``Coord2d`` and ``Coord3d`` C++ templates, providing a standard set operations for 2-tuples and 3-tuples of any numeric type
Expand Down
4 changes: 3 additions & 1 deletion plugins/3dveins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,9 @@ bool GeoBiome::init_layers()
GeoLayer *VeinGenerator::mapLayer(Block *pb, df::coord2d tile)
{
int idx = pb->biomeIndexAt(tile);
GeoBiome *biome = biome_by_idx.at(idx);
if (idx < 0)
return NULL;
GeoBiome *biome = biome_by_idx[idx];

int lidx = pb->layerIndexAt(tile);
if (unsigned(lidx) >= biome->layers.size())
Expand Down
5 changes: 3 additions & 2 deletions plugins/export-world-map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,10 @@ static command_result export_sites(color_ostream &out)
df::creature_raw *race = nullptr;
if (owner){
race = df::creature_raw::find(owner->race);
DEBUG(warning, out).print("owner ({}) of site ({}) has undefined race ({})", owner->id, site->id, owner->race);
if (!race) {
df::creature_raw::find(civ->race);
DEBUG(warning, out).print("owner ({}) of site ({}) has undefined race ({})\n", owner->id, site->id, owner->race);
if (civ)
race = df::creature_raw::find(civ->race);
}
}

Expand Down
9 changes: 8 additions & 1 deletion plugins/strangemood.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "PluginManager.h"

#include "modules/Gui.h"
#include "MiscUtils.h"
#include "modules/Units.h"
#include "modules/Items.h"
#include "modules/Job.h"
Expand Down Expand Up @@ -172,7 +173,13 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
out.printerr("No unit id specified!\n");
return CR_WRONG_USAGE;
}
unit = df::unit::find(std::stoi(parameters[i]));
int id = string_to_int(parameters[i], -1);
if (id < 0)
{
out.printerr("Invalid unit id: '{}'\n", parameters[i]);
return CR_WRONG_USAGE;
}
unit = df::unit::find(id);
if (!unit)
return CR_FAILURE;
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/suspendmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ static command_result do_command(color_ostream &out, vector<string> &parameters)
return plugin_enable(out,true);
} else if (parameters[0] == "disable") {
return plugin_enable(out,false);
} else if (parameters[0] == "set" && parameters[1] == "preventblocking") {
} else if (parameters.size() == 3 && parameters[0] == "set" && parameters[1] == "preventblocking") {
if (parameters[2] == "true") {
suspendmanager_instance->prevent_blocking = true;
config.set_bool(CONFIG_PREVENT_BLOCKING, true);
Expand Down
18 changes: 18 additions & 0 deletions test/plugins/3dveins.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
config.mode = 'fortress'
config.target = '3dveins'

function test.rewrites_veins()
local output, status = dfhack.run_command_silent('3dveins')
expect.eq(CR_OK, status)
expect.str_find('Writing tiles', output)
end

function test.verbose_option()
local _, status = dfhack.run_command_silent('3dveins', 'verbose')
expect.eq(CR_OK, status)
end

function test.bad_option_is_wrong_usage()
local _, status = dfhack.run_command_silent('3dveins', 'bogus')
expect.eq(CR_WRONG_USAGE, status)
end
111 changes: 111 additions & 0 deletions test/plugins/autodump.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
config.mode = 'fortress'
config.target = 'autodump'

local dwarfmode = require('gui.dwarfmode')

local function find_floor_pos()
for _, block in ipairs(df.global.world.map.map_blocks) do
for x = 0, 15 do
for y = 0, 15 do
local tt = block.tiletype[x][y]
local des = block.designation[x][y]
local occ = block.occupancy[x][y]
if df.tiletype.attrs[tt].shape == df.tiletype_shape.FLOOR
and not des.hidden and occ.building == 0 then
return block.map_pos.x + x, block.map_pos.y + y,
block.map_pos.z
end
end
end
end
end

local function set_cursor(x, y, z)
dwarfmode.setCursorPos(xyz2pos(x, y, z))
end

local function clear_cursor()
dwarfmode.clearCursorPos()
end
Comment thread
SilasD marked this conversation as resolved.

local function find_dumpable_item()
for _, item in ipairs(df.global.world.items.other.IN_PLAY) do
if not item.flags.dump and not item.flags.construction
and not item.flags.in_building and not item.flags.artifact
and not item.flags.in_job and not item.flags.forbid
and not item.flags.owned and not item.flags.in_inventory then
return item
end
end
end

function test.dump_moves_item_to_cursor()
local x, y, z = find_floor_pos()
local item = find_dumpable_item()
expect.ne(nil, x, 'test needs a revealed floor tile')
expect.ne(nil, item, 'test needs a dumpable item')

return dfhack.with_finalize(function()
item.flags.dump = false
clear_cursor()
end, function()
set_cursor(x, y, z)
item.flags.dump = true

local _, status = dfhack.run_command_silent('autodump')
expect.eq(CR_OK, status)
-- item was teleported to the cursor and marked as dumped
expect.eq(x, item.pos.x)
expect.eq(y, item.pos.y)
expect.eq(z, item.pos.z)
expect.false_(item.flags.dump)
expect.true_(item.flags.forbid)
end)
end

function test.conflicting_filters_is_wrong_usage()
local output, status = dfhack.run_command_silent('autodump', 'visible', 'hidden')
expect.eq(CR_WRONG_USAGE, status)
expect.str_find("both hidden and visible", output)
end

function test.bad_option_is_wrong_usage()
local _, status = dfhack.run_command_silent('autodump', 'bogus')
expect.eq(CR_WRONG_USAGE, status)
end

function test.destroy_and_undestroy()
local item = find_dumpable_item()
expect.ne(nil, item, 'test needs a dumpable item')

local was_paused = dfhack.world.ReadPauseState()
return dfhack.with_finalize(function()
dfhack.world.SetPauseState(was_paused)
dfhack.run_command_silent('autodump', 'undestroy')
item.flags.dump = false
item.flags.garbage_collect = false
item.flags.forbid = false
item.flags.hidden = false
end, function()
item.flags.dump = true
-- undestroy only restores marks made in the same frame, so the
-- game must stay paused between destroy and undestroy
dfhack.world.SetPauseState(true)

local _, status = dfhack.run_command_silent('autodump', 'destroy')
expect.eq(CR_OK, status)
expect.true_(item.flags.garbage_collect)
expect.true_(item.flags.forbid)
expect.true_(item.flags.hidden)

local output, status2 = dfhack.run_command_silent('autodump', 'undestroy')
expect.eq(CR_OK, status2)
expect.str_find('unmarked for destruction', output)
expect.false_(item.flags.garbage_collect)
expect.false_(item.flags.forbid)
expect.false_(item.flags.hidden)
-- undestroy restores the pre-destroy flags, including the dump
-- flag we set ourselves
expect.true_(item.flags.dump)
end)
end
83 changes: 83 additions & 0 deletions test/plugins/changeitem.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
config.mode = 'fortress'
config.target = 'changeitem'

local function find_floor_item_pos()
for _, item in ipairs(df.global.world.items.other.IN_PLAY) do
if not item.flags.hidden and not item.flags.in_inventory
and not item.flags.in_job and not item.flags.construction then
local x, y, z = dfhack.items.getPosition(item)
if x then return item, x, y, z end
end
end
end

local function set_cursor(x, y, z)
df.global.cursor:assign{x=x, y=y, z=z}
end

local function clear_cursor()
df.global.cursor:assign{x=-30000, y=-30000, z=-30000}
end

function test.here_quality_changes_item()
local item, x, y, z = find_floor_item_pos()
expect.ne(nil, item, 'test needs an item on the ground')
local orig_quality = item.quality

return dfhack.with_finalize(function()
item.quality = orig_quality
clear_cursor()
end, function()
set_cursor(x, y, z)
local output, status = dfhack.run_command_silent('changeitem',
'here', 'q', '4')
expect.eq(CR_OK, status)
expect.str_find('items processed', output)
expect.eq(4, item.quality)
end)
end

function test.here_empty_tile_processes_nothing()
-- find a floor tile and put the cursor on it; if it happens to hold
-- items the count is still reported
local block = df.global.world.map.map_blocks[0]
local x, y, z = block.map_pos.x, block.map_pos.y, block.map_pos.z

return dfhack.with_finalize(clear_cursor, function()
set_cursor(x, y, z)
local output, status = dfhack.run_command_silent('changeitem', 'here')
expect.eq(CR_OK, status)
expect.str_find('items processed', output)
end)
end

function test.here_no_cursor_is_failure()
return dfhack.with_finalize(clear_cursor, function()
clear_cursor()
local output, status = dfhack.run_command_silent('changeitem', 'here')
expect.eq(CR_FAILURE, status)
expect.str_find('Cursor position not found', output)
end)
end

function test.no_selection_is_failure()
local output, status = dfhack.run_command_silent('changeitem')
expect.eq(CR_FAILURE, status)
expect.str_find('No item selected', output)
end

function test.material_missing_arg_is_wrong_usage()
local output, status = dfhack.run_command_silent('changeitem', 'm')
expect.eq(CR_WRONG_USAGE, status)
expect.str_find('no material specified', output)
end

function test.bad_quality_is_wrong_usage()
local _, status = dfhack.run_command_silent('changeitem', 'q', '9')
expect.eq(CR_WRONG_USAGE, status)
end

function test.bad_option_is_wrong_usage()
local _, status = dfhack.run_command_silent('changeitem', 'bogus')
expect.eq(CR_WRONG_USAGE, status)
end
31 changes: 31 additions & 0 deletions test/plugins/changelayer.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
config.mode = 'fortress'
config.target = 'changelayer'

function test.no_material_is_wrong_usage()
local output, status = dfhack.run_command_silent('changelayer')
expect.eq(CR_WRONG_USAGE, status)
expect.str_find('specify a material', output)
end

function test.bad_material_is_failure()
local output, status = dfhack.run_command_silent('changelayer', 'BOGUSMAT')
expect.eq(CR_FAILURE, status)
expect.str_find('No such material', output)
end

function test.no_cursor_is_failure()
return dfhack.with_finalize(function()
df.global.cursor:assign{x=-30000, y=-30000, z=-30000}
end, function()
df.global.cursor:assign{x=-30000, y=-30000, z=-30000}
local output, status = dfhack.run_command_silent('changelayer',
'GRANITE')
expect.eq(CR_FAILURE, status)
expect.str_find('No cursor', output)
end)
end

function test.help_is_wrong_usage()
local _, status = dfhack.run_command_silent('changelayer', '?')
expect.eq(CR_WRONG_USAGE, status)
end
Loading
Loading