diff --git a/docs/changelog.txt b/docs/changelog.txt index 9c5d04a25..e8fc5725c 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: - `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 - `dwarfvet`: reinstated and updated for v50's new hospital mechanics; allow your animals to have their wounds treated at hospitals +- `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 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 diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index a10db97df..310bd1647 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -179,3 +179,12 @@ 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. Box selection characters and the keyboard cursor will also +change color as appropriate when over the warm or damp tile. 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 diff --git a/plugins/lua/dig.lua b/plugins/lua/dig.lua new file mode 100644 index 000000000..43012d5d2 --- /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 = {asciiwarmdamp=WarmDampOverlay} + +return _ENV diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index 6ed890325..f60469489 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -1,23 +1,26 @@ +#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(window_x); REQUIRE_GLOBAL(window_y); REQUIRE_GLOBAL(window_z); -REQUIRE_GLOBAL(world); namespace DFHack { DBG_DECLARE(pathable, log, DebugCategory::LINFO); @@ -31,7 +34,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 +42,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 +64,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 +113,101 @@ static void paintScreen(df::coord target, bool skip_unrevealed = false) { } } +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; + + 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; + + 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; + } + + // 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; + } 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 };