From 462ee0cba7f89d1148f885f1725c23c59cd8cab4 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 17:43:57 -0700 Subject: [PATCH 1/9] generalize mod directory scanning --- library/lua/script-manager.lua | 50 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/library/lua/script-manager.lua b/library/lua/script-manager.lua index 450012357..91b4985ac 100644 --- a/library/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -74,7 +74,7 @@ function list() end --------------------- --- mod script paths +-- mod paths -- this perhaps could/should be queried from the Steam API -- are there any installation configurations where this will be wrong, though? @@ -98,8 +98,8 @@ local function get_mod_id_and_version(path) end if not version then -- note this doesn't include the closing brace since some people put - -- non-number characters in here, and DF only reads the digits as the - -- numeric version + -- non-number characters in here, and DF only reads the leading digits + -- as the numeric version _,_,version = line:find('^%[NUMERIC_VERSION:(%d+)') end -- note that we do *not* want to break out of this loop early since @@ -108,37 +108,31 @@ local function get_mod_id_and_version(path) return id, version end -local function add_script_path(mod_script_paths, path) +local function add_mod_paths(mod_paths, id, base_path, subdir) + local sep = base_path:endswith('/') and '' or '/' + local path = ('%s%s%s'):format(base_path, sep, subdir) if dfhack.filesystem.isdir(path) then - print('indexing mod scripts: ' .. path) - table.insert(mod_script_paths, path) + print('indexing mod path: ' .. path) + table.insert(mod_paths, {id=id, path=path}) end end -local function add_script_paths(mod_script_paths, base_path, include_modactive) - if not base_path:endswith('/') then - base_path = base_path .. '/' - end - if include_modactive then - add_script_path(mod_script_paths, base_path..'scripts_modactive') - end - add_script_path(mod_script_paths, base_path..'scripts_modinstalled') -end - -function get_mod_script_paths() +function get_mod_paths(installed_subdir, active_subdir) -- ordered map of mod id -> {handled=bool, versions=map of version -> path} local mods = utils.OrderedTable() - local mod_script_paths = {} + local mod_paths = {} -- if a world is loaded, process active mods first, and lock to active version - if dfhack.isWorldLoaded() then + if active_subdir and dfhack.isWorldLoaded() then for _,path in ipairs(df.global.world.object_loader.object_load_order_src_dir) do path = tostring(path.value) + -- skip vanilla "mods" if not path:startswith(INSTALLED_MODS_PATH) then goto continue end local id = get_mod_id_and_version(path) if not id then goto continue end mods[id] = {handled=true} - add_script_paths(mod_script_paths, path, true) + add_mod_paths(mod_paths, id, path, active_subdir) + add_mod_paths(mod_paths, id, path, installed_subdir) ::continue:: end end @@ -159,8 +153,8 @@ function get_mod_script_paths() ::skip_path_root:: end - -- add script paths from most recent version of all not-yet-handled mods - for _,v in pairs(mods) do + -- add paths from most recent version of all not-yet-handled mods + for id,v in pairs(mods) do if v.handled then goto continue end local max_version, path for version,mod_path in pairs(v.versions) do @@ -169,11 +163,19 @@ function get_mod_script_paths() max_version = version end end - add_script_paths(mod_script_paths, path) + add_mod_paths(mod_paths, id, path, installed_subdir) ::continue:: end - return mod_script_paths + return mod_paths +end + +function get_mod_script_paths() + local paths = {} + for _,v in ipairs(get_mod_paths('scripts_modinstalled', 'scripts_modactive')) do + table.insert(paths, v.path) + end + return paths end return _ENV From e67df53c482555a9318dbd28eec0f34a78c4de4c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 17:44:09 -0700 Subject: [PATCH 2/9] document new blueprints dir in mods --- docs/guides/modding-guide.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 38117503c..297e8482a 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -45,6 +45,7 @@ this:: info.txt graphics/... objects/... + blueprints/... scripts_modactive/example-mod.lua scripts_modactive/internal/example-mod/... scripts_modinstalled/... @@ -58,6 +59,9 @@ Let's go through that line by line. - Modifications to the game raws (potentially with custom raw tokens) go in the :file:`graphics/` and :file:`objects/` folders. You can read more about the files that go in these directories on the :wiki:`Modding` wiki page. +- Any `quickfort` blueprints included with your mod go in the + :file:`blueprints` folder. Note that your mod can *just* be blueprints and + nothing else if you like. - A control script in :file:`scripts_modactive/` directory that handles system-level event hooks (e.g. reloading state when a world is loaded), registering `overlays `, and From 237075080924cc01cd45ad5c7d3b4939a96ab5eb Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 19:17:09 -0700 Subject: [PATCH 3/9] add warm/damp highlight overlay for ascii mode --- docs/plugins/dig.rst | 8 +++ plugins/lua/dig.lua | 29 +++++++++ plugins/pathable.cpp | 148 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 plugins/lua/dig.lua diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index a10db97df..46238add6 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -179,3 +179,11 @@ Filters: Take current designation and apply the selected pattern to it. After you have a pattern set, you can use ``expdig`` to apply it again. + +Overlay +------- + +This tool also provides an overlay that is managed by the `overlay` framework. +When the ``dig.asciiwarmdamp`` overlay is enabled and you are in non-graphics +(ASCII) mode, warm tiles will be highlighted in red and damp tiles will be +highlighted in blue. diff --git a/plugins/lua/dig.lua b/plugins/lua/dig.lua new file mode 100644 index 000000000..c9b8cf62b --- /dev/null +++ b/plugins/lua/dig.lua @@ -0,0 +1,29 @@ +local _ENV = mkmodule('plugins.dig') + +local overlay = require('plugins.overlay') +local pathable = require('plugins.pathable') + +WarmDampOverlay = defclass(WarmDampOverlay, overlay.OverlayWidget) +WarmDampOverlay.ATTRS{ + viewscreens={ + 'dwarfmode/Designate/DIG_DIG', + 'dwarfmode/Designate/DIG_REMOVE_STAIRS_RAMPS', + 'dwarfmode/Designate/DIG_STAIR_UP', + 'dwarfmode/Designate/DIG_STAIR_UPDOWN', + 'dwarfmode/Designate/DIG_STAIR_DOWN', + 'dwarfmode/Designate/DIG_RAMP', + 'dwarfmode/Designate/DIG_CHANNEL', + 'dwarfmode/Designate/DIG_FROM_MARKER', + 'dwarfmode/Designate/DIG_TO_MARKER', + }, + default_enabled=true, + overlay_only=true, +} + +function WarmDampOverlay:onRenderFrame(dc) + pathable.paintScreenWarmDamp() +end + +OVERLAY_WIDGETS = {overlay=WarmDampOverlay} + +return _ENV diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index 6ed890325..a7f6af0cd 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -1,23 +1,27 @@ +#include "Debug.h" +#include "PluginManager.h" +#include "TileTypes.h" + #include "modules/Gui.h" #include "modules/Maps.h" #include "modules/Screen.h" #include "modules/Textures.h" -#include "Debug.h" -#include "LuaTools.h" -#include "PluginManager.h" - #include "df/init.h" +#include "df/map_block.h" +#include "df/tile_designation.h" + +#include using namespace DFHack; DFHACK_PLUGIN("pathable"); -REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(init); +REQUIRE_GLOBAL(selection_rect); REQUIRE_GLOBAL(window_x); REQUIRE_GLOBAL(window_y); REQUIRE_GLOBAL(window_z); -REQUIRE_GLOBAL(world); namespace DFHack { DBG_DECLARE(pathable, log, DebugCategory::LINFO); @@ -31,7 +35,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) { return CR_OK; } -static void paintScreen(df::coord target, bool skip_unrevealed = false) { +static void paintScreenPathable(df::coord target, bool show_hidden = false) { DEBUG(log).print("entering paintScreen\n"); bool use_graphics = Screen::inGraphicsMode(); @@ -39,8 +43,8 @@ static void paintScreen(df::coord target, bool skip_unrevealed = false) { int selected_tile_texpos = 0; Screen::findGraphicsTile("CURSORS", 4, 3, &selected_tile_texpos); - long pathable_tile_texpos = df::global::init->load_bar_texpos[1]; - long unpathable_tile_texpos = df::global::init->load_bar_texpos[4]; + long pathable_tile_texpos = init->load_bar_texpos[1]; + long unpathable_tile_texpos = init->load_bar_texpos[4]; long on_off_texpos = Textures::getMapPathableTexposStart(); if (on_off_texpos > 0) { pathable_tile_texpos = on_off_texpos + 0; @@ -61,7 +65,7 @@ static void paintScreen(df::coord target, bool skip_unrevealed = false) { continue; } - if (skip_unrevealed && !Maps::isTileVisible(map_pos)) { + if (!show_hidden && !Maps::isTileVisible(map_pos)) { TRACE(log).print("skipping hidden tile\n"); continue; } @@ -110,7 +114,129 @@ static void paintScreen(df::coord target, bool skip_unrevealed = false) { } } +static bool init_mouse_selection_rect(rect2d &rect) { + df::coord mouse_pos = Gui::getMousePos(); + if (!mouse_pos.isValid()) + return false; + rect.first.x = std::min(selection_rect->start_x, (int32_t)mouse_pos.x); + rect.second.x = std::max(selection_rect->start_x, (int32_t)mouse_pos.x); + rect.first.y = std::min(selection_rect->start_y, (int32_t)mouse_pos.y); + rect.second.y = std::max(selection_rect->start_y, (int32_t)mouse_pos.y); + return true; +} + +static bool in_mouse_selection_rect(const rect2d &rect, const df::coord &pos) { + return ((pos.y == rect.first.y || pos.y == rect.second.y) && (pos.x >= rect.first.x || pos.x <= rect.second.x)) || + ((pos.x == rect.first.x || pos.x == rect.second.x) && (pos.y >= rect.first.y || pos.y <= rect.second.y)); +} + +static bool is_warm(const df::coord &pos) { + auto block = Maps::getTileBlock(pos); + if (!block) + return false; + return block->temperature_1[pos.x&15][pos.y&15] >= 10075; +} + +static bool is_rough_wall(int16_t x, int16_t y, int16_t z) { + df::tiletype *tt = Maps::getTileType(x, y, z); + if (!tt) + return false; + + return tileShape(*tt) == df::tiletype_shape::WALL && + tileSpecial(*tt) != df::tiletype_special::SMOOTH; +} + +static bool will_leak(int16_t x, int16_t y, int16_t z) { + auto des = Maps::getTileDesignation(x, y, z); + if (!des) + return false; + if (des->bits.liquid_type == df::tile_liquid::Water && des->bits.flow_size >= 1) + return true; + if (des->bits.water_table && is_rough_wall(x, y, z)) + return true; + return false; +} + +static bool is_damp(const df::coord &pos) { + return will_leak(pos.x-1, pos.y-1, pos.z) || + will_leak(pos.x, pos.y-1, pos.z) || + will_leak(pos.x+1, pos.y-1, pos.z) || + will_leak(pos.x-1, pos.y, pos.z) || + will_leak(pos.x+1, pos.y, pos.z) || + will_leak(pos.x-1, pos.y+1, pos.z) || + will_leak(pos.x, pos.y+1, pos.z) || + will_leak(pos.x+1, pos.y+1, pos.z); + will_leak(pos.x, pos.y+1, pos.z+1); +} + +static void paintScreenWarmDamp(bool show_hidden = false) { + DEBUG(log).print("entering paintScreenDampWarm\n"); + + if (Screen::inGraphicsMode()) + return; + + bool has_mouse_selection_rect = selection_rect->start_x >= 0; + rect2d mouse_sel_rect; + if (has_mouse_selection_rect) { + has_mouse_selection_rect = init_mouse_selection_rect(mouse_sel_rect); + } + + bool has_kbd_selection_rect = false; // TODO where is this info stored? + + auto dims = Gui::getDwarfmodeViewDims().map(); + for (int y = dims.first.y; y <= dims.second.y; ++y) { + for (int x = dims.first.x; x <= dims.second.x; ++x) { + df::coord map_pos(*window_x + x, *window_y + y, *window_z); + + if (!Maps::isValidTilePos(map_pos)) + continue; + + // don't overwrite selection box tiles + if (has_mouse_selection_rect && in_mouse_selection_rect(mouse_sel_rect, map_pos)) { + TRACE(log).print("skipping mouse selection box tile\n"); + continue; + } + + if (!show_hidden && !Maps::isTileVisible(map_pos)) { + TRACE(log).print("skipping hidden tile\n"); + continue; + } + + TRACE(log).print("scanning map tile at (%d, %d, %d) screen offset (%d, %d)\n", + map_pos.x, map_pos.y, map_pos.z, x, y); + + Screen::Pen cur_tile = Screen::readTile(x, y, true); + if (!cur_tile.valid()) { + DEBUG(log).print("cannot read tile at offset %d, %d\n", x, y); + continue; + } + + int color = is_warm(map_pos) ? COLOR_RED : is_damp(map_pos) ? COLOR_BLUE : COLOR_BLACK; + if (color == COLOR_BLACK) { + TRACE(log).print("skipping non-warm, non-damp tile\n"); + continue; + } + + if (cur_tile.fg && cur_tile.ch != ' ') { + cur_tile.fg = color; + cur_tile.bg = 0; + } else { + cur_tile.fg = 0; + cur_tile.bg = color; + } + + cur_tile.bold = false; + + if (cur_tile.tile) + cur_tile.tile_mode = Screen::Pen::CharColor; + + Screen::paintTile(cur_tile, x, y, true); + } + } +} + DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(paintScreen), + DFHACK_LUA_FUNCTION(paintScreenPathable), + DFHACK_LUA_FUNCTION(paintScreenWarmDamp), DFHACK_LUA_END }; From 040d2caa956796a1363fe0f0ccc95b1cae199799 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 19:22:30 -0700 Subject: [PATCH 4/9] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 97bc202fe..92d6aae4d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Plugins - `3dveins`: reinstated for v50, this plugin replaces vanilla DF's blobby vein generation with veins that flow smoothly and naturally between z-levels - `zone`: new searchable, sortable, filterable screen for assigning units to pastures +- `dig`: new ``dig.asciiwarmdamp`` overlay that highlights warm and damp tiles when in ASCII mode. there is no effect in graphics mode since the tiles are already highlighted there ## Fixes - Fix extra keys appearing in DFHack text boxes when shift (or any other modifier) is released before the other key you were pressing From a6b304d9b4e02d4ee998d52c3fc25acc8b65b5ca Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 19:28:49 -0700 Subject: [PATCH 5/9] fix box select bounds logic --- plugins/pathable.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index a7f6af0cd..f7bbe2e76 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -126,8 +126,13 @@ static bool init_mouse_selection_rect(rect2d &rect) { } static bool in_mouse_selection_rect(const rect2d &rect, const df::coord &pos) { - return ((pos.y == rect.first.y || pos.y == rect.second.y) && (pos.x >= rect.first.x || pos.x <= rect.second.x)) || - ((pos.x == rect.first.x || pos.x == rect.second.x) && (pos.y >= rect.first.y || pos.y <= rect.second.y)); + return ((pos.y == rect.first.y || pos.y == rect.second.y) && (pos.x >= rect.first.x && pos.x <= rect.second.x)) || + ((pos.x == rect.first.x || pos.x == rect.second.x) && (pos.y >= rect.first.y && pos.y <= rect.second.y)); +} + +static bool in_kbd_selection_rect(const rect2d &rect, const df::coord &pos) { + return pos.y >= rect.first.y && pos.y <= rect.second.y && + pos.x >= rect.first.x && pos.x <= rect.second.x; } static bool is_warm(const df::coord &pos) { @@ -182,6 +187,7 @@ static void paintScreenWarmDamp(bool show_hidden = false) { } bool has_kbd_selection_rect = false; // TODO where is this info stored? + rect2d kbd_sel_rect; auto dims = Gui::getDwarfmodeViewDims().map(); for (int y = dims.first.y; y <= dims.second.y; ++y) { @@ -196,6 +202,10 @@ static void paintScreenWarmDamp(bool show_hidden = false) { TRACE(log).print("skipping mouse selection box tile\n"); continue; } + if (has_kbd_selection_rect && in_kbd_selection_rect(kbd_sel_rect, map_pos)){ + TRACE(log).print("skipping keyboard selection box tile\n"); + continue; + } if (!show_hidden && !Maps::isTileVisible(map_pos)) { TRACE(log).print("skipping hidden tile\n"); From d18a1f12f73ab8a0001417b0e675595dfeb881f7 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 19:32:38 -0700 Subject: [PATCH 6/9] allow the color to override box select and cursor --- plugins/pathable.cpp | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index f7bbe2e76..b4c7f703f 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -114,27 +114,6 @@ static void paintScreenPathable(df::coord target, bool show_hidden = false) { } } -static bool init_mouse_selection_rect(rect2d &rect) { - df::coord mouse_pos = Gui::getMousePos(); - if (!mouse_pos.isValid()) - return false; - rect.first.x = std::min(selection_rect->start_x, (int32_t)mouse_pos.x); - rect.second.x = std::max(selection_rect->start_x, (int32_t)mouse_pos.x); - rect.first.y = std::min(selection_rect->start_y, (int32_t)mouse_pos.y); - rect.second.y = std::max(selection_rect->start_y, (int32_t)mouse_pos.y); - return true; -} - -static bool in_mouse_selection_rect(const rect2d &rect, const df::coord &pos) { - return ((pos.y == rect.first.y || pos.y == rect.second.y) && (pos.x >= rect.first.x && pos.x <= rect.second.x)) || - ((pos.x == rect.first.x || pos.x == rect.second.x) && (pos.y >= rect.first.y && pos.y <= rect.second.y)); -} - -static bool in_kbd_selection_rect(const rect2d &rect, const df::coord &pos) { - return pos.y >= rect.first.y && pos.y <= rect.second.y && - pos.x >= rect.first.x && pos.x <= rect.second.x; -} - static bool is_warm(const df::coord &pos) { auto block = Maps::getTileBlock(pos); if (!block) @@ -180,15 +159,6 @@ static void paintScreenWarmDamp(bool show_hidden = false) { if (Screen::inGraphicsMode()) return; - bool has_mouse_selection_rect = selection_rect->start_x >= 0; - rect2d mouse_sel_rect; - if (has_mouse_selection_rect) { - has_mouse_selection_rect = init_mouse_selection_rect(mouse_sel_rect); - } - - bool has_kbd_selection_rect = false; // TODO where is this info stored? - rect2d kbd_sel_rect; - auto dims = Gui::getDwarfmodeViewDims().map(); for (int y = dims.first.y; y <= dims.second.y; ++y) { for (int x = dims.first.x; x <= dims.second.x; ++x) { @@ -197,16 +167,6 @@ static void paintScreenWarmDamp(bool show_hidden = false) { if (!Maps::isValidTilePos(map_pos)) continue; - // don't overwrite selection box tiles - if (has_mouse_selection_rect && in_mouse_selection_rect(mouse_sel_rect, map_pos)) { - TRACE(log).print("skipping mouse selection box tile\n"); - continue; - } - if (has_kbd_selection_rect && in_kbd_selection_rect(kbd_sel_rect, map_pos)){ - TRACE(log).print("skipping keyboard selection box tile\n"); - continue; - } - if (!show_hidden && !Maps::isTileVisible(map_pos)) { TRACE(log).print("skipping hidden tile\n"); continue; @@ -227,6 +187,8 @@ static void paintScreenWarmDamp(bool show_hidden = false) { continue; } + // this will also change the color of the cursor or any selection box that overlaps + // the tile. this is intentional since it makes the UI more readable if (cur_tile.fg && cur_tile.ch != ' ') { cur_tile.fg = color; cur_tile.bg = 0; From 25301bf93e36b4056ce82d2a0cfc2a4327d26b8f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 19:35:02 -0700 Subject: [PATCH 7/9] update docs --- docs/plugins/dig.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index 46238add6..310bd1647 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -186,4 +186,5 @@ Overlay This tool also provides an overlay that is managed by the `overlay` framework. When the ``dig.asciiwarmdamp`` overlay is enabled and you are in non-graphics (ASCII) mode, warm tiles will be highlighted in red and damp tiles will be -highlighted in blue. +highlighted in blue. Box selection characters and the keyboard cursor will also +change color as appropriate when over the warm or damp tile. From a77a6b5943d36c9aadd6bed38e177b1b59a79024 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 20 Jul 2023 19:40:40 -0700 Subject: [PATCH 8/9] clean up globals --- plugins/pathable.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index b4c7f703f..f60469489 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -18,7 +18,6 @@ using namespace DFHack; DFHACK_PLUGIN("pathable"); REQUIRE_GLOBAL(init); -REQUIRE_GLOBAL(selection_rect); REQUIRE_GLOBAL(window_x); REQUIRE_GLOBAL(window_y); REQUIRE_GLOBAL(window_z); From d14054716c634580a97ae514a60fcac6630c11e1 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 21 Jul 2023 00:28:05 -0700 Subject: [PATCH 9/9] better name for overlay --- plugins/lua/dig.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/dig.lua b/plugins/lua/dig.lua index c9b8cf62b..43012d5d2 100644 --- a/plugins/lua/dig.lua +++ b/plugins/lua/dig.lua @@ -24,6 +24,6 @@ function WarmDampOverlay:onRenderFrame(dc) pathable.paintScreenWarmDamp() end -OVERLAY_WIDGETS = {overlay=WarmDampOverlay} +OVERLAY_WIDGETS = {asciiwarmdamp=WarmDampOverlay} return _ENV