From 17017bb526d7b0ffdbe51e0831c81104693fc379 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Jul 2021 13:39:20 -0700 Subject: [PATCH 01/47] update changelog --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7d9eb9265..6963ec61f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `tiletypes-here`, `tiletypes-here-point`: add --cursor and --quiet options to support non-interactive use cases +## Documentation +- `quickfort-library-guide`: updated dreamfort documentation and added screenshots + # 0.47.05-r2 ## Fixes From b8ccd6e40be5ca99c5ebf4d7d6c6e1540514b36f Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Jul 2021 13:39:34 -0700 Subject: [PATCH 02/47] add dreamfort screenshots to library guide --- docs/guides/quickfort-library-guide.rst | 81 ++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 0eb04f73f..43856c8b4 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -25,20 +25,83 @@ Dreamfort Dreamfort is a fully functional, self-sustaining fortress with defenses, farming, a complete set of workshops, self-managing quantum stockpiles, a grand -dining hall, hospital, jail, fresh water well system, guildhalls, and bedrooms -for hundreds of dwarves. It also comes with manager work orders to automate -basic fort needs, such as food and booze production. It can function by itself -or as the core of a larger, more ambitious fortress. Read the high-level +dining hall, hospital, jail, fresh water well system, guildhalls, noble suites, +and bedrooms for hundreds of dwarves. It also comes with manager work orders to +automate basic fort needs, such as food and booze production. It can function by +itself or as the core of a larger, more ambitious fortress. Read the high-level walkthrough by running ``quickfort run library/dreamfort.csv`` and list the -walkthroughs for the individual levels by running ``quickfort list dreamfort -l --m notes`` or by opening the ``quickfort gui`` dialog, enabling the library -with :kbd:`Alt`:kbd:`l`, and setting the filter to ``dreamfort notes``. +walkthroughs for the individual levels by running ``quickfort list -l dreamfort +-m notes`` or ``quickfort gui -l dreamfort notes``. Dreamfort blueprints are also available for easy viewing and copying `online `__. -The Quick Fortresses -~~~~~~~~~~~~~~~~~~~~ +The online spreadsheets also include `embark profile suggestions +`__ +and a complete `example embark profile +`__. + +Visual overview +``````````````` + +Here are some annotated screenshots of the major levels (or click `here +`__ +for the slideshow interface). + +Surface level +\\\\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP + :alt: Annotated screenshot of the dreamfort surface level + :target: https://drive.google.com/file/d/1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP + +Farming level +\\\\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=1fBC3G5Y888l4tVe5REAyAd_zeojADVme + :alt: Annotated screenshot of the dreamfort farming level + :target: https://drive.google.com/file/d/1fBC3G5Y888l4tVe5REAyAd_zeojADVme + +Industry level +\\\\\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=1emMaHHCaUPcdRbkLQqvr-0ZCs2tdM5X7 + :alt: Annotated screenshot of the dreamfort industry level + :target: https://drive.google.com/file/d/1emMaHHCaUPcdRbkLQqvr-0ZCs2tdM5X7 + +Services level +\\\\\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=13vDIkTVOZGkM84tYf4O5nmRs4VZdE1gh + :alt: Annotated screenshot of the dreamfort services level + :target: https://drive.google.com/file/d/13vDIkTVOZGkM84tYf4O5nmRs4VZdE1gh +.. image:: https://drive.google.com/uc?export=download&id=1jlGr6tAhS8i-XFTz8gowTZBhXcfjfL_L + :alt: Annotated screenshot of the dreamfort cistern + :target: https://drive.google.com/file/d/1jlGr6tAhS8i-XFTz8gowTZBhXcfjfL_L + +Guildhall level +\\\\\\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ + :alt: Annotated screenshot of the dreamfort guildhall level + :target: https://drive.google.com/file/d/17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ + +Noble suites +\\\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=1IBqCf6fF3lw7sHiBE_15Euubysl5AAiS + :alt: Annotated screenshot of the dreamfort noble suites + :target: https://drive.google.com/file/d/1IBqCf6fF3lw7sHiBE_15Euubysl5AAiS + +Apartments +\\\\\\\\\\ + +.. image:: https://drive.google.com/uc?export=download&id=1mDQQXG8BnXqasRGFC9R5N6xNALiswEyr + :alt: Annotated screenshot of the dreamfort apartments + :target: https://drive.google.com/file/d/1mDQQXG8BnXqasRGFC9R5N6xNALiswEyr + +The Quick Fortress +~~~~~~~~~~~~~~~~~~ The Quick Fortress is an updated version of the example fortress that came with `Python Quickfort 2.0 `__ (the program From a5c329cfb2dd5ed01200635f6f95adcea392800d Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 13 Jul 2021 21:37:47 -0700 Subject: [PATCH 03/47] add plumbing example and center images --- docs/guides/quickfort-library-guide.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 43856c8b4..d0b308747 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -54,6 +54,7 @@ Surface level .. image:: https://drive.google.com/uc?export=download&id=1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP :alt: Annotated screenshot of the dreamfort surface level :target: https://drive.google.com/file/d/1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP + :align: center Farming level \\\\\\\\\\\\\ @@ -61,6 +62,7 @@ Farming level .. image:: https://drive.google.com/uc?export=download&id=1fBC3G5Y888l4tVe5REAyAd_zeojADVme :alt: Annotated screenshot of the dreamfort farming level :target: https://drive.google.com/file/d/1fBC3G5Y888l4tVe5REAyAd_zeojADVme + :align: center Industry level \\\\\\\\\\\\\\ @@ -75,9 +77,19 @@ Services level .. image:: https://drive.google.com/uc?export=download&id=13vDIkTVOZGkM84tYf4O5nmRs4VZdE1gh :alt: Annotated screenshot of the dreamfort services level :target: https://drive.google.com/file/d/13vDIkTVOZGkM84tYf4O5nmRs4VZdE1gh + :align: center .. image:: https://drive.google.com/uc?export=download&id=1jlGr6tAhS8i-XFTz8gowTZBhXcfjfL_L :alt: Annotated screenshot of the dreamfort cistern :target: https://drive.google.com/file/d/1jlGr6tAhS8i-XFTz8gowTZBhXcfjfL_L + :align: center + +Example plumbing to fill cisterns +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +.. image:: https://drive.google.com/uc?export=download&id=1GvhX_pVDOlmqTi2OujoBqCG_qX36ExAv + :alt: Annotated screenshot of an example aqueduct addition to the dreamfort cistern + :target: https://drive.google.com/file/d/1GvhX_pVDOlmqTi2OujoBqCG_qX36ExAv + :align: center Guildhall level \\\\\\\\\\\\\\\ @@ -85,6 +97,7 @@ Guildhall level .. image:: https://drive.google.com/uc?export=download&id=17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ :alt: Annotated screenshot of the dreamfort guildhall level :target: https://drive.google.com/file/d/17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ + :align: center Noble suites \\\\\\\\\\\\ @@ -92,6 +105,7 @@ Noble suites .. image:: https://drive.google.com/uc?export=download&id=1IBqCf6fF3lw7sHiBE_15Euubysl5AAiS :alt: Annotated screenshot of the dreamfort noble suites :target: https://drive.google.com/file/d/1IBqCf6fF3lw7sHiBE_15Euubysl5AAiS + :align: center Apartments \\\\\\\\\\ @@ -99,6 +113,7 @@ Apartments .. image:: https://drive.google.com/uc?export=download&id=1mDQQXG8BnXqasRGFC9R5N6xNALiswEyr :alt: Annotated screenshot of the dreamfort apartments :target: https://drive.google.com/file/d/1mDQQXG8BnXqasRGFC9R5N6xNALiswEyr + :align: center The Quick Fortress ~~~~~~~~~~~~~~~~~~ From 5b960b97bca1ffdc06a015a48b74555886c64828 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 16 May 2021 14:15:19 -0700 Subject: [PATCH 04/47] fix comment for Maps::getSize(); add getTileSize() --- library/include/modules/Maps.h | 4 +++- library/modules/Maps.cpp | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 50aca8e9d..bc6298601 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -260,8 +260,10 @@ extern DFHACK_EXPORT bool GetGlobalFeature(t_feature &feature, int32_t index); * BLOCK DATA */ -/// get size of the map in tiles +/// get size of the map in blocks extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z); +/// get size of the map in tiles +extern DFHACK_EXPORT void getTileSize(uint32_t& x, uint32_t& y, uint32_t& z); /// get the position of the map on world map extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index b4289ddf6..da1a775f3 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -106,7 +106,7 @@ bool Maps::IsValid () return (world->map.block_index != NULL); } -// getter for map size +// getter for map size in blocks void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) { if (!IsValid()) @@ -119,6 +119,14 @@ void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) z = world->map.z_count_block; } +// getter for map size in tiles +void Maps::getTileSize (uint32_t& x, uint32_t& y, uint32_t& z) +{ + getSize(x, y, z); + x *= 16; + y *= 16; +} + // getter for map position void Maps::getPosition (int32_t& x, int32_t& y, int32_t& z) { From d3d5a6edd7899fc4508f63b17901312c474d4f7a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 16 May 2021 14:16:21 -0700 Subject: [PATCH 05/47] initial implementation of the dig-dug plugin for discussion --- plugins/CMakeLists.txt | 1 + plugins/dig-dug.cpp | 223 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 plugins/dig-dug.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 92e2e9dbd..53546f3ce 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -111,6 +111,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) dfhack_plugin(dig dig.cpp) + dfhack_plugin(dig-dug dig-dug.cpp) dfhack_plugin(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) dfhack_plugin(dwarfvet dwarfvet.cpp) diff --git a/plugins/dig-dug.cpp b/plugins/dig-dug.cpp new file mode 100644 index 000000000..1d9b5680d --- /dev/null +++ b/plugins/dig-dug.cpp @@ -0,0 +1,223 @@ +/* + * Simulates completion of dig designations. + */ + +#include "DataFuncs.h" +#include "PluginManager.h" +#include "TileTypes.h" + +#include "modules/Maps.h" + +#include +#include +#include +#include + +DFHACK_PLUGIN("dig-dug"); +REQUIRE_GLOBAL(world); + +using namespace DFHack; + +// returns true iff tile is in map bounds and was hidden before this function +// unhid it. +static bool unhide(int32_t x, int32_t y, int32_t z) { + // ensures coords are in map bounds and ensures that the map block exists + // so we can unhide the tiles + if (!Maps::ensureTileBlock(x, y, z)) + return false; + + df::tile_designation &td = *Maps::getTileDesignation(x, y, z); + if (!td.bits.hidden) + return false; + + td.bits.hidden = false; + return true; +} + +// unhide self and adjacent tiles if hidden and flood fill unhidden state +static void flood_unhide(int32_t x, int32_t y, int32_t z) { + df::tiletype tt = *Maps::getTileType(x, y, z); + if (tileShape(tt) == df::tiletype_shape::WALL + && tileMaterial(tt) != df::tiletype_material::TREE) + return; + + for (int32_t xoff = -1; xoff <= 1; ++xoff) { + for (int32_t yoff = -1; yoff <= 1; ++yoff) { + if (xoff == 0 && yoff == 0) + continue; + if (unhide(x + xoff, y + yoff, z)) + flood_unhide(x + xoff, y + yoff, z); + } + } + + if (LowPassable(tt) && unhide(x, y, z-1)) + flood_unhide(x, y, z-1); + + // note that checking HighPassable for the current tile gives false + // positives. You have to check LowPassable for the tile above. + if (Maps::ensureTileBlock(x, y, z+1) + && LowPassable(*Maps::getTileType(x, y, z+1)) + && unhide(x, y, z+1)) { + flood_unhide(x, y, z+1); + } +} + +// inherit flags from passable tiles above and propagate to passable tiles below +static void propagate_vertical_flags(int32_t x, int32_t y, int32_t z) { + df::tile_designation &td = *Maps::getTileDesignation(x, y, z); + + if (!Maps::isValidTilePos(x, y, z-1)) { + // only the sky above + td.bits.light = true; + td.bits.outside = true; + td.bits.subterranean = false; + } + + int32_t zlevel = z; + df::tiletype_shape shape = tileShape(*Maps::getTileType(x, y, zlevel)); + while ((shape == df::tiletype_shape::EMPTY + || shape == df::tiletype_shape::RAMP_TOP) + && Maps::ensureTileBlock(x, y, --zlevel)) { + df::tile_designation *td_below = Maps::getTileDesignation(x, y, zlevel); + if (td_below->bits.light == td.bits.light + && td_below->bits.outside == td.bits.outside + && td_below->bits.subterranean == td.bits.subterranean) + break; + td_below->bits.light = td.bits.light; + td_below->bits.outside = td.bits.outside; + td_below->bits.subterranean = td.bits.subterranean; + shape = tileShape(*Maps::getTileType(x, y, zlevel)); + } +} + +static void set_tile_type(int32_t x, int32_t y, int32_t z, + df::tiletype target_type) { + if (target_type == df::tiletype::Void) + return; + df::map_block *block = Maps::getTileBlock(x, y, z); + block->tiletype[x&15][y&15] = target_type; +} + +// TODO: if requested, create boulders +static void dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, + df::tile_dig_designation designation) { + df::tiletype tt = *Maps::getTileType(x, y, z); + df::tiletype target_type = df::tiletype::Void; + switch(designation) { + case df::tile_dig_designation::Default: + target_type = findSimilarTileType(tt, df::tiletype_shape::FLOOR); + break; + case df::tile_dig_designation::UpDownStair: + target_type = + findSimilarTileType(tt, df::tiletype_shape::STAIR_UPDOWN); + break; + case df::tile_dig_designation::Channel: + { + if (Maps::ensureTileBlock(x, y, z+1)) { + df::tiletype tt_above = *Maps::getTileType(x, y, z+1); + if (tileShape(tt_above) == df::tiletype_shape::RAMP_TOP) + set_tile_type(x, y, z+1, tiletype::OpenSpace); + } + target_type = tiletype::OpenSpace; + if (Maps::ensureTileBlock(x, y, z-1)) { + df::tiletype tt_below = *Maps::getTileType(x, y, z-1); + if (tileShape(tt_below) == df::tiletype_shape::WALL + && isGroundMaterial(tileMaterial(tt_below))) { + dig_tile(out, x, y, z-1, df::tile_dig_designation::Ramp); + target_type = + findSimilarTileType(tt, df::tiletype_shape::RAMP_TOP); + } + } + break; + } + case df::tile_dig_designation::Ramp: + target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); + break; + case df::tile_dig_designation::DownStair: + target_type = findSimilarTileType(tt, df::tiletype_shape::STAIR_DOWN); + break; + case df::tile_dig_designation::UpStair: + target_type = findSimilarTileType(tt, df::tiletype_shape::STAIR_UP); + break; + case df::tile_dig_designation::No: + default: + out.printerr( + "unhandled dig designation for tile (%d, %d, %d): %d\n", + x, y, z, designation); + return; + } + + // TODO: set tile to use layer material + set_tile_type(x, y, z, target_type); + + // set flags for current and adjacent tiles + unhide(x, y, z); + flood_unhide(x, y, z); // in case we breached a cavern + propagate_vertical_flags(x, y, z); // for new channels +} + +static void smooth_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, + bool engrave) { + // TODO +} + +static void carve_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, + df::tile_occupancy &to) { + // TODO +} + +command_result dig_dug(color_ostream &out, std::vector &) { + CoreSuspender suspend; + + if (!Maps::IsValid()) { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + + // scan the whole map for now. we can add in boundaries later. + uint32_t endx, endy, endz; + Maps::getTileSize(endx, endy, endz); + + for (uint32_t z = 0; z <= endz; ++z) { + for (uint32_t y = 0; y <= endy; ++y) { + for (uint32_t x = 0; x <= endx; ++x) { + // this will return NULL if the map block hasn't been allocated + // yet, but that means there aren't any designations anyway. + if (!Maps::getTileBlock(x, y, z)) + continue; + + df::tile_designation &td = *Maps::getTileDesignation(x, y, z); + df::tile_occupancy &to = *Maps::getTileOccupancy(x, y, z); + if (td.bits.dig != df::tile_dig_designation::No) { + dig_tile(out, x, y, z, td.bits.dig); + td.bits.dig = df::tile_dig_designation::No; + } else if (td.bits.smooth > 0) { + bool want_engrave = td.bits.smooth == 2; + smooth_tile(out, x, y, z, want_engrave); + td.bits.smooth = 0; + } else if (to.bits.carve_track_north == 1 + || to.bits.carve_track_east == 1 + || to.bits.carve_track_south == 1 + || to.bits.carve_track_west == 1) { + carve_tile(out, x, y, z, to); + } + } + } + } + + // Force the game to recompute its walkability cache + world->reindex_pathfinding = true; + + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &, + std::vector &commands) { + commands.push_back(PluginCommand( + "dig-dug", "Simulate completion of dig designations", dig_dug, false)); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &) { + return CR_OK; +} From a4c86770a40eea2fbcc218bba7f27d23dcddff7a Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 1 Jun 2021 11:58:32 -0700 Subject: [PATCH 06/47] follow digging rules when converting tiles --- plugins/dig-dug.cpp | 187 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 31 deletions(-) diff --git a/plugins/dig-dug.cpp b/plugins/dig-dug.cpp index 1d9b5680d..ce2663915 100644 --- a/plugins/dig-dug.cpp +++ b/plugins/dig-dug.cpp @@ -34,7 +34,7 @@ static bool unhide(int32_t x, int32_t y, int32_t z) { return true; } -// unhide self and adjacent tiles if hidden and flood fill unhidden state +// unhide adjacent tiles if hidden and flood fill unhidden state static void flood_unhide(int32_t x, int32_t y, int32_t z) { df::tiletype tt = *Maps::getTileType(x, y, z); if (tileShape(tt) == df::tiletype_shape::WALL @@ -90,63 +90,186 @@ static void propagate_vertical_flags(int32_t x, int32_t y, int32_t z) { } } +static bool can_dig_default(df::tiletype tt) { + df::tiletype_shape shape = tileShape(tt); + return shape == df::tiletype_shape::WALL || + shape == df::tiletype_shape::FORTIFICATION || + shape == df::tiletype_shape::RAMP || + shape == df::tiletype_shape::STAIR_UP || + shape == df::tiletype_shape::STAIR_UPDOWN; +} + +static bool can_dig_channel(df::tiletype tt) { + df::tiletype_shape shape = tileShape(tt); + return shape != df::tiletype_shape::EMPTY && + shape != df::tiletype_shape::ENDLESS_PIT && + shape != df::tiletype_shape::NONE && + shape != df::tiletype_shape::RAMP_TOP && + shape != df::tiletype_shape::TRUNK_BRANCH; +} + +static bool can_dig_up_stair(df::tiletype tt) { + df::tiletype_shape shape = tileShape(tt); + return shape == df::tiletype_shape::WALL || + shape == df::tiletype_shape::FORTIFICATION; +} + +static bool can_dig_down_stair(df::tiletype tt) { + df::tiletype_shape shape = tileShape(tt); + return shape == df::tiletype_shape::BOULDER || + shape == df::tiletype_shape::BROOK_BED || + shape == df::tiletype_shape::BROOK_TOP || + shape == df::tiletype_shape::FLOOR || + shape == df::tiletype_shape::FORTIFICATION || + shape == df::tiletype_shape::PEBBLES || + shape == df::tiletype_shape::RAMP || + shape == df::tiletype_shape::SAPLING || + shape == df::tiletype_shape::SHRUB || + shape == df::tiletype_shape::TWIG || + shape == df::tiletype_shape::WALL; +} + +static bool can_dig_up_down_stair(df::tiletype tt) { + df::tiletype_shape shape = tileShape(tt); + return shape == df::tiletype_shape::WALL || + shape == df::tiletype_shape::FORTIFICATION || + shape == df::tiletype_shape::STAIR_UP; +} + +static bool can_dig_ramp(df::tiletype tt) { + df::tiletype_shape shape = tileShape(tt); + return shape == df::tiletype_shape::WALL || + shape == df::tiletype_shape::FORTIFICATION; +} + static void set_tile_type(int32_t x, int32_t y, int32_t z, df::tiletype target_type) { - if (target_type == df::tiletype::Void) - return; df::map_block *block = Maps::getTileBlock(x, y, z); block->tiletype[x&15][y&15] = target_type; } +static void remove_ramp_top(int32_t x, int32_t y, int32_t z) { + if (!Maps::ensureTileBlock(x, y, z)) + return; + + if (tileShape(*Maps::getTileType(x, y, z)) == df::tiletype_shape::RAMP_TOP) + set_tile_type(x, y, z, df::tiletype::OpenSpace); +} + +static bool is_wall(int32_t x, int32_t y, int32_t z) { + if (!Maps::ensureTileBlock(x, y, z)) + return false; + return tileShape(*Maps::getTileType(x, y, z)) == df::tiletype_shape::WALL; +} + +static void clean_ramp(int32_t x, int32_t y, int32_t z) { + if (!Maps::ensureTileBlock(x, y, z)) + return; + + df::tiletype tt = *Maps::getTileType(x, y, z); + if (tileShape(tt) != df::tiletype_shape::RAMP) + return; + + if (is_wall(x+1, y, z) || is_wall(x-1, y, z) || + is_wall(x, y+1, z) || is_wall(x, y-1, z)) + return; + + remove_ramp_top(x, y, z+1); + set_tile_type(x, y, z, findSimilarTileType(tt, df::tiletype_shape::FLOOR)); +} + +// removes self and/or orthogonally adjacent ramps that are no longer adjacent +// to a wall +static void clean_ramps(int32_t x, int32_t y, int32_t z) { + clean_ramp(x, y, z); + clean_ramp(x-1, y, z); + clean_ramp(x+1, y, z); + clean_ramp(x, y-1, z); + clean_ramp(x, y+1, z); +} + // TODO: if requested, create boulders -static void dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, +static bool dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, df::tile_dig_designation designation) { df::tiletype tt = *Maps::getTileType(x, y, z); + + // TODO: handle trees + if (!isGroundMaterial(tileMaterial(tt))) + return false; + df::tiletype target_type = df::tiletype::Void; switch(designation) { case df::tile_dig_designation::Default: - target_type = findSimilarTileType(tt, df::tiletype_shape::FLOOR); - break; - case df::tile_dig_designation::UpDownStair: - target_type = - findSimilarTileType(tt, df::tiletype_shape::STAIR_UPDOWN); + // TODO: should not leave a smooth floor when removing stairs/ramps + if (can_dig_default(tt)) { + df::tiletype_shape shape = tileShape(tt); + df::tiletype_shape target_shape = df::tiletype_shape::FLOOR; + if (shape == df::tiletype_shape::STAIR_UPDOWN) + target_shape = df::tiletype_shape::STAIR_DOWN; + else if (shape == df::tiletype_shape::RAMP) + remove_ramp_top(x, y, z+1); + target_type = findSimilarTileType(tt, target_shape); + } break; case df::tile_dig_designation::Channel: - { - if (Maps::ensureTileBlock(x, y, z+1)) { - df::tiletype tt_above = *Maps::getTileType(x, y, z+1); - if (tileShape(tt_above) == df::tiletype_shape::RAMP_TOP) - set_tile_type(x, y, z+1, tiletype::OpenSpace); - } - target_type = tiletype::OpenSpace; - if (Maps::ensureTileBlock(x, y, z-1)) { - df::tiletype tt_below = *Maps::getTileType(x, y, z-1); - if (tileShape(tt_below) == df::tiletype_shape::WALL - && isGroundMaterial(tileMaterial(tt_below))) { - dig_tile(out, x, y, z-1, df::tile_dig_designation::Ramp); - target_type = - findSimilarTileType(tt, df::tiletype_shape::RAMP_TOP); + if (can_dig_channel(tt)) { + remove_ramp_top(x, y, z+1); + target_type = df::tiletype::OpenSpace; + if (Maps::ensureTileBlock(x, y, z-1) && + dig_tile(out, x, y, z-1, + df::tile_dig_designation::Ramp)) { + // if we successfully dug out the ramp below, that took care + // of the ramp top here + target_type = df::tiletype::Void; + clean_ramps(x, y, z-1); } + break; } - break; - } - case df::tile_dig_designation::Ramp: - target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); + case df::tile_dig_designation::UpStair: + if (can_dig_up_stair(tt)) + target_type = + findSimilarTileType(tt, df::tiletype_shape::STAIR_UP); break; case df::tile_dig_designation::DownStair: - target_type = findSimilarTileType(tt, df::tiletype_shape::STAIR_DOWN); + if (can_dig_down_stair(tt)) { + target_type = + findSimilarTileType(tt, df::tiletype_shape::STAIR_DOWN); + + } break; - case df::tile_dig_designation::UpStair: - target_type = findSimilarTileType(tt, df::tiletype_shape::STAIR_UP); + case df::tile_dig_designation::UpDownStair: + if (can_dig_up_down_stair(tt)) { + target_type = + findSimilarTileType(tt, + df::tiletype_shape::STAIR_UPDOWN); + } break; + case df::tile_dig_designation::Ramp: + { + if (can_dig_ramp(tt)) { + target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); + if (target_type != tt && Maps::ensureTileBlock(x, y, z+1)) { + // set tile type directly instead of calling dig_tile + // because we need to use *this* tile's material, not the + // material of the tile above + set_tile_type(x, y, z+1, + findSimilarTileType(tt, + df::tiletype_shape::RAMP_TOP)); + } + } + break; + } case df::tile_dig_designation::No: default: out.printerr( "unhandled dig designation for tile (%d, %d, %d): %d\n", x, y, z, designation); - return; } + // fail if no change to tile + if (target_type == df::tiletype::Void || target_type == tt) + return false; + // TODO: set tile to use layer material set_tile_type(x, y, z, target_type); @@ -154,6 +277,8 @@ static void dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, unhide(x, y, z); flood_unhide(x, y, z); // in case we breached a cavern propagate_vertical_flags(x, y, z); // for new channels + + return true; } static void smooth_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, From 6cf40857e4218f6e2478c8be3938f2cad4a84c6b Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 1 Jun 2021 17:38:04 -0700 Subject: [PATCH 07/47] convert material to layer mat when digging also ensure dug tiles aren't generated already smoothed --- plugins/dig-dug.cpp | 226 +++++++++++++++++++++++++++----------------- 1 file changed, 138 insertions(+), 88 deletions(-) diff --git a/plugins/dig-dug.cpp b/plugins/dig-dug.cpp index ce2663915..863af2f54 100644 --- a/plugins/dig-dug.cpp +++ b/plugins/dig-dug.cpp @@ -6,7 +6,7 @@ #include "PluginManager.h" #include "TileTypes.h" -#include "modules/Maps.h" +#include "modules/MapCache.h" #include #include @@ -20,23 +20,23 @@ using namespace DFHack; // returns true iff tile is in map bounds and was hidden before this function // unhid it. -static bool unhide(int32_t x, int32_t y, int32_t z) { +static bool unhide(MapExtras::MapCache &map, const DFCoord &pos) { // ensures coords are in map bounds and ensures that the map block exists // so we can unhide the tiles - if (!Maps::ensureTileBlock(x, y, z)) + if (!map.ensureBlockAt(pos)) return false; - df::tile_designation &td = *Maps::getTileDesignation(x, y, z); + df::tile_designation td = map.designationAt(pos); if (!td.bits.hidden) return false; td.bits.hidden = false; - return true; + return map.setDesignationAt(pos, td); } // unhide adjacent tiles if hidden and flood fill unhidden state -static void flood_unhide(int32_t x, int32_t y, int32_t z) { - df::tiletype tt = *Maps::getTileType(x, y, z); +static void flood_unhide(MapExtras::MapCache &map, const DFCoord &pos) { + df::tiletype tt = map.tiletypeAt(pos); if (tileShape(tt) == df::tiletype_shape::WALL && tileMaterial(tt) != df::tiletype_material::TREE) return; @@ -45,48 +45,54 @@ static void flood_unhide(int32_t x, int32_t y, int32_t z) { for (int32_t yoff = -1; yoff <= 1; ++yoff) { if (xoff == 0 && yoff == 0) continue; - if (unhide(x + xoff, y + yoff, z)) - flood_unhide(x + xoff, y + yoff, z); + if (unhide(map, DFCoord(pos.x+xoff, pos.y+yoff, pos.z))) + flood_unhide(map, DFCoord(pos.x+xoff, pos.y+yoff, pos.z)); } } - if (LowPassable(tt) && unhide(x, y, z-1)) - flood_unhide(x, y, z-1); + DFCoord pos_below(pos.x, pos.y, pos.z-1); + if (LowPassable(tt) && unhide(map, pos_below)) + flood_unhide(map, pos_below); // note that checking HighPassable for the current tile gives false // positives. You have to check LowPassable for the tile above. - if (Maps::ensureTileBlock(x, y, z+1) - && LowPassable(*Maps::getTileType(x, y, z+1)) - && unhide(x, y, z+1)) { - flood_unhide(x, y, z+1); + DFCoord pos_above(pos.x, pos.y, pos.z+1); + if (map.ensureBlockAt(pos_above) + && LowPassable(map.tiletypeAt(pos_above)) + && unhide(map, pos_above)) { + flood_unhide(map, pos_above); } } // inherit flags from passable tiles above and propagate to passable tiles below -static void propagate_vertical_flags(int32_t x, int32_t y, int32_t z) { - df::tile_designation &td = *Maps::getTileDesignation(x, y, z); +static void propagate_vertical_flags(MapExtras::MapCache &map, + const DFCoord &pos) { + df::tile_designation td = map.designationAt(pos); - if (!Maps::isValidTilePos(x, y, z-1)) { + if (!map.ensureBlockAt(DFCoord(pos.x, pos.y, pos.z-1))) { // only the sky above td.bits.light = true; td.bits.outside = true; td.bits.subterranean = false; } - int32_t zlevel = z; - df::tiletype_shape shape = tileShape(*Maps::getTileType(x, y, zlevel)); + int32_t zlevel = pos.z; + df::tiletype_shape shape = + tileShape(map.tiletypeAt(DFCoord(pos.x, pos.y, zlevel))); while ((shape == df::tiletype_shape::EMPTY || shape == df::tiletype_shape::RAMP_TOP) - && Maps::ensureTileBlock(x, y, --zlevel)) { - df::tile_designation *td_below = Maps::getTileDesignation(x, y, zlevel); - if (td_below->bits.light == td.bits.light - && td_below->bits.outside == td.bits.outside - && td_below->bits.subterranean == td.bits.subterranean) + && map.ensureBlockAt(DFCoord(pos.x, pos.y, --zlevel))) { + DFCoord pos_below(pos.x, pos.y, zlevel); + df::tile_designation td_below = map.designationAt(pos_below); + if (td_below.bits.light == td.bits.light + && td_below.bits.outside == td.bits.outside + && td_below.bits.subterranean == td.bits.subterranean) break; - td_below->bits.light = td.bits.light; - td_below->bits.outside = td.bits.outside; - td_below->bits.subterranean = td.bits.subterranean; - shape = tileShape(*Maps::getTileType(x, y, zlevel)); + td_below.bits.light = td.bits.light; + td_below.bits.outside = td.bits.outside; + td_below.bits.subterranean = td.bits.subterranean; + map.setDesignationAt(pos_below, td_below); + shape = tileShape(map.tiletypeAt(pos_below)); } } @@ -142,58 +148,80 @@ static bool can_dig_ramp(df::tiletype tt) { shape == df::tiletype_shape::FORTIFICATION; } -static void set_tile_type(int32_t x, int32_t y, int32_t z, - df::tiletype target_type) { - df::map_block *block = Maps::getTileBlock(x, y, z); - block->tiletype[x&15][y&15] = target_type; +static void dig_type(MapExtras::MapCache &map, const DFCoord &pos, + df::tiletype tt) { + auto blk = map.BlockAtTile(pos); + if (!blk) + return; + + // ensure we run this even if one of the later steps fails (e.g. OpenSpace) + map.setTiletypeAt(pos, tt); + + // digging a tile reverts it to the layer soil/stone material + if (!blk->setStoneAt(pos, tt, map.layerMaterialAt(pos)) && + !blk->setSoilAt(pos, tt, map.layerMaterialAt(pos))) + return; + + // un-smooth dug tiles + tt = map.tiletypeAt(pos); + tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), + df::tiletype_special::NORMAL, tileDirection(tt)); + map.setTiletypeAt(pos, tt); +} + +static void dig_shape(MapExtras::MapCache &map, const DFCoord &pos, + df::tiletype tt, df::tiletype_shape shape) { + dig_type(map, pos, findSimilarTileType(tt, shape)); } -static void remove_ramp_top(int32_t x, int32_t y, int32_t z) { - if (!Maps::ensureTileBlock(x, y, z)) +static void remove_ramp_top(MapExtras::MapCache &map, const DFCoord &pos) { + if (!map.ensureBlockAt(pos)) return; - if (tileShape(*Maps::getTileType(x, y, z)) == df::tiletype_shape::RAMP_TOP) - set_tile_type(x, y, z, df::tiletype::OpenSpace); + if (tileShape(map.tiletypeAt(pos)) == df::tiletype_shape::RAMP_TOP) + dig_type(map, pos, df::tiletype::OpenSpace); } -static bool is_wall(int32_t x, int32_t y, int32_t z) { - if (!Maps::ensureTileBlock(x, y, z)) +static bool is_wall(MapExtras::MapCache &map, const DFCoord &pos) { + if (!map.ensureBlockAt(pos)) return false; - return tileShape(*Maps::getTileType(x, y, z)) == df::tiletype_shape::WALL; + return tileShape(map.tiletypeAt(pos)) == df::tiletype_shape::WALL; } -static void clean_ramp(int32_t x, int32_t y, int32_t z) { - if (!Maps::ensureTileBlock(x, y, z)) +static void clean_ramp(MapExtras::MapCache &map, const DFCoord &pos) { + if (!map.ensureBlockAt(pos)) return; - df::tiletype tt = *Maps::getTileType(x, y, z); + df::tiletype tt = map.tiletypeAt(pos); if (tileShape(tt) != df::tiletype_shape::RAMP) return; - if (is_wall(x+1, y, z) || is_wall(x-1, y, z) || - is_wall(x, y+1, z) || is_wall(x, y-1, z)) + if (is_wall(map, DFCoord(pos.x-1, pos.y, pos.z)) || + is_wall(map, DFCoord(pos.x+1, pos.y, pos.z)) || + is_wall(map, DFCoord(pos.x, pos.y-1, pos.z)) || + is_wall(map, DFCoord(pos.x, pos.y+1, pos.z))) return; - remove_ramp_top(x, y, z+1); - set_tile_type(x, y, z, findSimilarTileType(tt, df::tiletype_shape::FLOOR)); + remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+1)); + dig_shape(map,pos, tt, df::tiletype_shape::FLOOR); } // removes self and/or orthogonally adjacent ramps that are no longer adjacent // to a wall -static void clean_ramps(int32_t x, int32_t y, int32_t z) { - clean_ramp(x, y, z); - clean_ramp(x-1, y, z); - clean_ramp(x+1, y, z); - clean_ramp(x, y-1, z); - clean_ramp(x, y+1, z); +static void clean_ramps(MapExtras::MapCache &map, const DFCoord &pos) { + clean_ramp(map, pos); + clean_ramp(map, DFCoord(pos.x-1, pos.y, pos.z)); + clean_ramp(map, DFCoord(pos.x+1, pos.y, pos.z)); + clean_ramp(map, DFCoord(pos.x, pos.y-1, pos.z)); + clean_ramp(map, DFCoord(pos.x, pos.y+1, pos.z)); } // TODO: if requested, create boulders -static bool dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, - df::tile_dig_designation designation) { - df::tiletype tt = *Maps::getTileType(x, y, z); +static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, + const DFCoord &pos, df::tile_dig_designation designation) { + df::tiletype tt = map.tiletypeAt(pos); - // TODO: handle trees + // TODO: handle tree trunks, roots, and surface tiles if (!isGroundMaterial(tileMaterial(tt))) return false; @@ -207,21 +235,23 @@ static bool dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, if (shape == df::tiletype_shape::STAIR_UPDOWN) target_shape = df::tiletype_shape::STAIR_DOWN; else if (shape == df::tiletype_shape::RAMP) - remove_ramp_top(x, y, z+1); + remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+1)); target_type = findSimilarTileType(tt, target_shape); } break; case df::tile_dig_designation::Channel: if (can_dig_channel(tt)) { - remove_ramp_top(x, y, z+1); + remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+1)); target_type = df::tiletype::OpenSpace; - if (Maps::ensureTileBlock(x, y, z-1) && - dig_tile(out, x, y, z-1, + DFCoord pos_below(pos.x, pos.y, pos.z-1); + if (map.ensureBlockAt(pos_below) && + dig_tile(out, map, pos_below, df::tile_dig_designation::Ramp)) { + clean_ramps(map, pos_below); + flood_unhide(map, pos); // if we successfully dug out the ramp below, that took care // of the ramp top here - target_type = df::tiletype::Void; - clean_ramps(x, y, z-1); + return true; } break; } @@ -248,13 +278,13 @@ static bool dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, { if (can_dig_ramp(tt)) { target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); - if (target_type != tt && Maps::ensureTileBlock(x, y, z+1)) { - // set tile type directly instead of calling dig_tile + DFCoord pos_above(pos.x, pos.y, pos.z+1); + if (target_type != tt && map.ensureBlockAt(pos_above)) { + // set tile type directly instead of calling dig_shape // because we need to use *this* tile's material, not the // material of the tile above - set_tile_type(x, y, z+1, - findSimilarTileType(tt, - df::tiletype_shape::RAMP_TOP)); + map.setTiletypeAt(pos_above, + findSimilarTileType(tt, df::tiletype_shape::RAMP_TOP)); } } break; @@ -263,32 +293,33 @@ static bool dig_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, default: out.printerr( "unhandled dig designation for tile (%d, %d, %d): %d\n", - x, y, z, designation); + pos.x, pos.y, pos.z, designation); } // fail if no change to tile if (target_type == df::tiletype::Void || target_type == tt) return false; - // TODO: set tile to use layer material - set_tile_type(x, y, z, target_type); + dig_type(map, pos, target_type); // set flags for current and adjacent tiles - unhide(x, y, z); - flood_unhide(x, y, z); // in case we breached a cavern - propagate_vertical_flags(x, y, z); // for new channels + unhide(map, pos); + flood_unhide(map, pos); // in case we breached a cavern + propagate_vertical_flags(map, pos); // for new channels return true; } -static void smooth_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, - bool engrave) { +static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, + const DFCoord &pos, bool engrave) { // TODO + return false; } -static void carve_tile(color_ostream &out, int32_t x, int32_t y, int32_t z, - df::tile_occupancy &to) { +static bool carve_tile(color_ostream &out, MapExtras::MapCache &map, + const DFCoord &pos, df::tile_occupancy &to) { // TODO + return false; } command_result dig_dug(color_ostream &out, std::vector &) { @@ -303,6 +334,9 @@ command_result dig_dug(color_ostream &out, std::vector &) { uint32_t endx, endy, endz; Maps::getTileSize(endx, endy, endz); + // use the proxy layer for the layer material-setting ease-of-use functions + MapExtras::MapCache map; + for (uint32_t z = 0; z <= endz; ++z) { for (uint32_t y = 0; y <= endy; ++y) { for (uint32_t x = 0; x <= endx; ++x) { @@ -311,25 +345,41 @@ command_result dig_dug(color_ostream &out, std::vector &) { if (!Maps::getTileBlock(x, y, z)) continue; - df::tile_designation &td = *Maps::getTileDesignation(x, y, z); - df::tile_occupancy &to = *Maps::getTileOccupancy(x, y, z); + DFCoord pos(x, y, z); + df::tile_designation td = map.designationAt(pos); + df::tile_occupancy to = map.occupancyAt(pos); if (td.bits.dig != df::tile_dig_designation::No) { - dig_tile(out, x, y, z, td.bits.dig); - td.bits.dig = df::tile_dig_designation::No; + if (dig_tile(out, map, pos, td.bits.dig)) { + td = map.designationAt(pos); + td.bits.dig = df::tile_dig_designation::No; + map.setDesignationAt(pos, td); + } } else if (td.bits.smooth > 0) { bool want_engrave = td.bits.smooth == 2; - smooth_tile(out, x, y, z, want_engrave); - td.bits.smooth = 0; + if (smooth_tile(out, map, pos, want_engrave)) { + to = map.occupancyAt(pos); + td.bits.smooth = 0; + map.setDesignationAt(pos, td); + } } else if (to.bits.carve_track_north == 1 - || to.bits.carve_track_east == 1 - || to.bits.carve_track_south == 1 - || to.bits.carve_track_west == 1) { - carve_tile(out, x, y, z, to); + || to.bits.carve_track_east == 1 + || to.bits.carve_track_south == 1 + || to.bits.carve_track_west == 1) { + if (carve_tile(out, map, pos, to)) { + to = map.occupancyAt(pos); + to.bits.carve_track_north = 0; + to.bits.carve_track_east = 0; + to.bits.carve_track_south = 0; + to.bits.carve_track_west = 0; + map.setOccupancyAt(pos, to); + } } } } } + map.WriteAll(); + // Force the game to recompute its walkability cache world->reindex_pathfinding = true; From a5b70ba1635f73ae3a6d367810f89bfbf032421f Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 2 Jun 2021 13:31:15 -0700 Subject: [PATCH 08/47] rename dig-dug to dig-now --- plugins/CMakeLists.txt | 2 +- plugins/{dig-dug.cpp => dig-now.cpp} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename plugins/{dig-dug.cpp => dig-now.cpp} (99%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 53546f3ce..f0871de4b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -111,7 +111,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) dfhack_plugin(dig dig.cpp) - dfhack_plugin(dig-dug dig-dug.cpp) + dfhack_plugin(dig-now dig-now.cpp) dfhack_plugin(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) dfhack_plugin(dwarfvet dwarfvet.cpp) diff --git a/plugins/dig-dug.cpp b/plugins/dig-now.cpp similarity index 99% rename from plugins/dig-dug.cpp rename to plugins/dig-now.cpp index 863af2f54..c19bf1263 100644 --- a/plugins/dig-dug.cpp +++ b/plugins/dig-now.cpp @@ -13,7 +13,7 @@ #include #include -DFHACK_PLUGIN("dig-dug"); +DFHACK_PLUGIN("dig-now"); REQUIRE_GLOBAL(world); using namespace DFHack; @@ -389,7 +389,7 @@ command_result dig_dug(color_ostream &out, std::vector &) { DFhackCExport command_result plugin_init(color_ostream &, std::vector &commands) { commands.push_back(PluginCommand( - "dig-dug", "Simulate completion of dig designations", dig_dug, false)); + "dig-now", "Simulate completion of dig designations", dig_dug, false)); return CR_OK; } From fc932d2850781f438b18314399cf4ffbdd4a7964 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 2 Jun 2021 22:18:42 -0700 Subject: [PATCH 09/47] use reveal.unhideFlood instead of our dup impl --- plugins/CMakeLists.txt | 2 +- plugins/dig-now.cpp | 75 +++++++++++++++--------------------------- 2 files changed, 27 insertions(+), 50 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f0871de4b..25ea7d150 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -111,7 +111,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) dfhack_plugin(dig dig.cpp) - dfhack_plugin(dig-now dig-now.cpp) + dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) dfhack_plugin(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) dfhack_plugin(dwarfvet dwarfvet.cpp) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index c19bf1263..1da6780b5 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -5,7 +5,9 @@ #include "DataFuncs.h" #include "PluginManager.h" #include "TileTypes.h" +#include "LuaTools.h" +#include "modules/Maps.h" #include "modules/MapCache.h" #include @@ -18,50 +20,16 @@ REQUIRE_GLOBAL(world); using namespace DFHack; -// returns true iff tile is in map bounds and was hidden before this function -// unhid it. -static bool unhide(MapExtras::MapCache &map, const DFCoord &pos) { - // ensures coords are in map bounds and ensures that the map block exists - // so we can unhide the tiles - if (!map.ensureBlockAt(pos)) - return false; - - df::tile_designation td = map.designationAt(pos); - if (!td.bits.hidden) - return false; - - td.bits.hidden = false; - return map.setDesignationAt(pos, td); -} +static void flood_unhide(color_ostream &out, const DFCoord &pos) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); -// unhide adjacent tiles if hidden and flood fill unhidden state -static void flood_unhide(MapExtras::MapCache &map, const DFCoord &pos) { - df::tiletype tt = map.tiletypeAt(pos); - if (tileShape(tt) == df::tiletype_shape::WALL - && tileMaterial(tt) != df::tiletype_material::TREE) + if (!lua_checkstack(L, 2) + || !Lua::PushModulePublic(out, L, "plugins.reveal", "unhideFlood")) return; - for (int32_t xoff = -1; xoff <= 1; ++xoff) { - for (int32_t yoff = -1; yoff <= 1; ++yoff) { - if (xoff == 0 && yoff == 0) - continue; - if (unhide(map, DFCoord(pos.x+xoff, pos.y+yoff, pos.z))) - flood_unhide(map, DFCoord(pos.x+xoff, pos.y+yoff, pos.z)); - } - } - - DFCoord pos_below(pos.x, pos.y, pos.z-1); - if (LowPassable(tt) && unhide(map, pos_below)) - flood_unhide(map, pos_below); - - // note that checking HighPassable for the current tile gives false - // positives. You have to check LowPassable for the tile above. - DFCoord pos_above(pos.x, pos.y, pos.z+1); - if (map.ensureBlockAt(pos_above) - && LowPassable(map.tiletypeAt(pos_above)) - && unhide(map, pos_above)) { - flood_unhide(map, pos_above); - } + Lua::Push(L, pos); + Lua::SafeCall(out, L, 1, 0); } // inherit flags from passable tiles above and propagate to passable tiles below @@ -69,7 +37,7 @@ static void propagate_vertical_flags(MapExtras::MapCache &map, const DFCoord &pos) { df::tile_designation td = map.designationAt(pos); - if (!map.ensureBlockAt(DFCoord(pos.x, pos.y, pos.z-1))) { + if (!map.ensureBlockAt(DFCoord(pos.x, pos.y, pos.z+1))) { // only the sky above td.bits.light = true; td.bits.outside = true; @@ -248,7 +216,6 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, dig_tile(out, map, pos_below, df::tile_dig_designation::Ramp)) { clean_ramps(map, pos_below); - flood_unhide(map, pos); // if we successfully dug out the ramp below, that took care // of the ramp top here return true; @@ -296,16 +263,14 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, pos.x, pos.y, pos.z, designation); } - // fail if no change to tile + // fail if unhandled or no change to tile if (target_type == df::tiletype::Void || target_type == tt) return false; dig_type(map, pos, target_type); - // set flags for current and adjacent tiles - unhide(map, pos); - flood_unhide(map, pos); // in case we breached a cavern - propagate_vertical_flags(map, pos); // for new channels + // let light filter down to newly exposed tiles + propagate_vertical_flags(map, pos); return true; } @@ -334,9 +299,12 @@ command_result dig_dug(color_ostream &out, std::vector &) { uint32_t endx, endy, endz; Maps::getTileSize(endx, endy, endz); - // use the proxy layer for the layer material-setting ease-of-use functions + // use the proxy layer for the layer material-setting ease-of-use functions. MapExtras::MapCache map; + // tracks which positions to unhide + std::vector dug_coords; + for (uint32_t z = 0; z <= endz; ++z) { for (uint32_t y = 0; y <= endy; ++y) { for (uint32_t x = 0; x <= endx; ++x) { @@ -353,6 +321,7 @@ command_result dig_dug(color_ostream &out, std::vector &) { td = map.designationAt(pos); td.bits.dig = df::tile_dig_designation::No; map.setDesignationAt(pos, td); + dug_coords.push_back(pos); } } else if (td.bits.smooth > 0) { bool want_engrave = td.bits.smooth == 2; @@ -380,6 +349,14 @@ command_result dig_dug(color_ostream &out, std::vector &) { map.WriteAll(); + // unhide newly dug tiles. we can't do this in the loop above since our + // MapCache wouldn't detect the changes made by reveal.unhideFlood() without + // invalidating and reinitializing on every call. + for (DFCoord pos : dug_coords) { + if (Maps::getTileDesignation(pos)->bits.hidden) + flood_unhide(out, pos); + } + // Force the game to recompute its walkability cache world->reindex_pathfinding = true; From 02d7d4c5b6864806588cfd6df8501ba09a5d116b Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 2 Jun 2021 22:43:23 -0700 Subject: [PATCH 10/47] clean up top-level logic --- plugins/dig-now.cpp | 53 +++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 1da6780b5..0b4f3d2b5 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -287,27 +287,14 @@ static bool carve_tile(color_ostream &out, MapExtras::MapCache &map, return false; } -command_result dig_dug(color_ostream &out, std::vector &) { - CoreSuspender suspend; - - if (!Maps::IsValid()) { - out.printerr("Map is not available!\n"); - return CR_FAILURE; - } - - // scan the whole map for now. we can add in boundaries later. - uint32_t endx, endy, endz; - Maps::getTileSize(endx, endy, endz); - +static void do_dig(color_ostream &out, std::vector &dug_coords, + const DFCoord &start, const DFCoord &end) { // use the proxy layer for the layer material-setting ease-of-use functions. MapExtras::MapCache map; - // tracks which positions to unhide - std::vector dug_coords; - - for (uint32_t z = 0; z <= endz; ++z) { - for (uint32_t y = 0; y <= endy; ++y) { - for (uint32_t x = 0; x <= endx; ++x) { + for (uint32_t z = start.z; z <= end.z; ++z) { + for (uint32_t y = start.y; y <= end.y; ++y) { + for (uint32_t x = start.x; x <= end.x; ++x) { // this will return NULL if the map block hasn't been allocated // yet, but that means there aren't any designations anyway. if (!Maps::getTileBlock(x, y, z)) @@ -348,16 +335,36 @@ command_result dig_dug(color_ostream &out, std::vector &) { } map.WriteAll(); +} + +command_result dig_now(color_ostream &out, std::vector &) { + CoreSuspender suspend; + + if (!Maps::IsValid()) { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + + // tracks which positions to unhide + std::vector dug_coords; + + // scan the whole map for now. we can add in configurable boundaries later + DFCoord start(0, 0, 0); + uint32_t endx, endy, endz; + Maps::getTileSize(endx, endy, endz); + DFCoord end(endx, endy, endz); + + do_dig(out, dug_coords, start, end); - // unhide newly dug tiles. we can't do this in the loop above since our - // MapCache wouldn't detect the changes made by reveal.unhideFlood() without - // invalidating and reinitializing on every call. + // unhide newly dug tiles. we can't do this in do_dig() since our MapCache + // wouldn't detect the changes made by reveal.unhideFlood() without + // invalidating and reinitializing on every call for (DFCoord pos : dug_coords) { if (Maps::getTileDesignation(pos)->bits.hidden) flood_unhide(out, pos); } - // Force the game to recompute its walkability cache + // force the game to recompute its walkability cache world->reindex_pathfinding = true; return CR_OK; @@ -366,7 +373,7 @@ command_result dig_dug(color_ostream &out, std::vector &) { DFhackCExport command_result plugin_init(color_ostream &, std::vector &commands) { commands.push_back(PluginCommand( - "dig-now", "Simulate completion of dig designations", dig_dug, false)); + "dig-now", "Instantly complete dig designations", dig_now, false)); return CR_OK; } From 52e666a45a067eca2dbd9b9784a498c7d7371185 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 4 Jun 2021 12:15:15 -0700 Subject: [PATCH 11/47] produce boulders from digging as per game rules also add options struct for later configurability --- plugins/dig-now.cpp | 235 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 208 insertions(+), 27 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 0b4f3d2b5..3acf26e29 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -9,17 +9,65 @@ #include "modules/Maps.h" #include "modules/MapCache.h" +#include "modules/Random.h" +#include "modules/World.h" +#include +#include +#include #include #include +#include +#include #include -#include +#include DFHACK_PLUGIN("dig-now"); +REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(world); using namespace DFHack; +struct dig_now_options { + DFCoord start; // upper-left coordinate, min z-level + DFCoord end; // lower-right coordinate, max z-level + + // whether to unhide dug regions that are unreachable from the main fortress + bool unhide_unreachable; + + // percent chance ([0..100]) for creating a boulder for the given rock type + uint32_t boulder_percent_layer; + uint32_t boulder_percent_vein; + uint32_t boulder_percent_small_cluster; + uint32_t boulder_percent_deep; + + // whether to generate boulders at the cursor position instead of at their + // dig locations + bool dump_boulders; + + // if set to the pos of a walkable tile, will dump at this position instead + // of the in-game cursor + DFCoord cursor; + + static DFCoord getMapSize() { + uint32_t endx, endy, endz; + Maps::getTileSize(endx, endy, endz); + return DFCoord(endx, endy, endz); + } + + // boulder percentage defaults from + // https://dwarffortresswiki.org/index.php/DF2014:Mining + dig_now_options() : + start(0, 0, 0), + end(getMapSize()), + unhide_unreachable(false), + boulder_percent_layer(25), + boulder_percent_vein(33), + boulder_percent_small_cluster(100), + boulder_percent_deep(100), + dump_boulders(false) { } +}; + static void flood_unhide(color_ostream &out, const DFCoord &pos) { auto L = Lua::Core::State; Lua::StackUnwinder top(L); @@ -184,19 +232,37 @@ static void clean_ramps(MapExtras::MapCache &map, const DFCoord &pos) { clean_ramp(map, DFCoord(pos.x, pos.y+1, pos.z)); } -// TODO: if requested, create boulders +// returns material type of walls at the given position, or -1 if not a wall +static int16_t get_wall_mat(MapExtras::MapCache &map, const DFCoord &pos) { + df::tiletype tt = map.tiletypeAt(pos); + df::tiletype_shape shape = tileShape(tt); + + if (shape != df::tiletype_shape::WALL) + return -1; + + t_matpair matpair; + if (map.isVeinAt(pos)) + matpair = map.veinMaterialAt(pos); + else if (map.isLayerAt(pos)) + matpair = map.layerMaterialAt(pos); + return matpair.mat_type; +} + static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, - const DFCoord &pos, df::tile_dig_designation designation) { + const DFCoord &pos, df::tile_dig_designation designation, + int16_t *wall_mat) { df::tiletype tt = map.tiletypeAt(pos); // TODO: handle tree trunks, roots, and surface tiles - if (!isGroundMaterial(tileMaterial(tt))) + df::tiletype_material tile_mat = tileMaterial(tt); + if (!isGroundMaterial(tile_mat)) return false; + *wall_mat = get_wall_mat(map, pos); + df::tiletype target_type = df::tiletype::Void; switch(designation) { case df::tile_dig_designation::Default: - // TODO: should not leave a smooth floor when removing stairs/ramps if (can_dig_default(tt)) { df::tiletype_shape shape = tileShape(tt); df::tiletype_shape target_shape = df::tiletype_shape::FLOOR; @@ -214,7 +280,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, DFCoord pos_below(pos.x, pos.y, pos.z-1); if (map.ensureBlockAt(pos_below) && dig_tile(out, map, pos_below, - df::tile_dig_designation::Ramp)) { + df::tile_dig_designation::Ramp, wall_mat)) { clean_ramps(map, pos_below); // if we successfully dug out the ramp below, that took care // of the ramp top here @@ -246,6 +312,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (can_dig_ramp(tt)) { target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); DFCoord pos_above(pos.x, pos.y, pos.z+1); + wall_mat[1] = get_wall_mat(map, pos_above); if (target_type != tt && map.ensureBlockAt(pos_above)) { // set tile type directly instead of calling dig_shape // because we need to use *this* tile's material, not the @@ -287,14 +354,38 @@ static bool carve_tile(color_ostream &out, MapExtras::MapCache &map, return false; } +static bool produces_boulder(const dig_now_options &options, + Random::MersenneRNG &rng, + df::inclusion_type vein_type) { + uint32_t probability; + switch (vein_type) { + case df::inclusion_type::CLUSTER: + case df::inclusion_type::VEIN: + probability = options.boulder_percent_vein; + break; + case df::inclusion_type::CLUSTER_ONE: + case df::inclusion_type::CLUSTER_SMALL: + probability = options.boulder_percent_small_cluster; + break; + default: + probability = options.boulder_percent_layer; + break; + } + + return rng.random(100) < probability; +} + static void do_dig(color_ostream &out, std::vector &dug_coords, - const DFCoord &start, const DFCoord &end) { - // use the proxy layer for the layer material-setting ease-of-use functions. + std::map> &boulder_coords, + const dig_now_options &options) { MapExtras::MapCache map; + Random::MersenneRNG rng; + + rng.init(); - for (uint32_t z = start.z; z <= end.z; ++z) { - for (uint32_t y = start.y; y <= end.y; ++y) { - for (uint32_t x = start.x; x <= end.x; ++x) { + for (uint32_t z = options.start.z; z <= options.end.z; ++z) { + for (uint32_t y = options.start.y; y <= options.end.y; ++y) { + for (uint32_t x = options.start.x; x <= options.end.x; ++x) { // this will return NULL if the map block hasn't been allocated // yet, but that means there aren't any designations anyway. if (!Maps::getTileBlock(x, y, z)) @@ -304,11 +395,21 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, df::tile_designation td = map.designationAt(pos); df::tile_occupancy to = map.occupancyAt(pos); if (td.bits.dig != df::tile_dig_designation::No) { - if (dig_tile(out, map, pos, td.bits.dig)) { + int16_t wall_mat[2]; + memset(wall_mat, -1, sizeof(wall_mat)); + if (dig_tile(out, map, pos, td.bits.dig, wall_mat)) { td = map.designationAt(pos); td.bits.dig = df::tile_dig_designation::No; map.setDesignationAt(pos, td); dug_coords.push_back(pos); + for (size_t i = 0; i < 2; ++i) { + if (wall_mat[i] < 0) + continue; + if (produces_boulder(options, rng, + map.BlockAtTile(pos)->veinTypeAt(pos))) { + boulder_coords[wall_mat[i]].push_back(pos); + } + } } } else if (td.bits.smooth > 0) { bool want_engrave = td.bits.smooth == 2; @@ -337,6 +438,89 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, map.WriteAll(); } +// if pos is empty space, teleport to floor below +// if we fall out of the world, z coordinate will be negative (invalid) +static DFCoord simulate_fall(const DFCoord &pos) { + DFCoord resting_pos(pos); + + while (resting_pos.z >= 0) { + df::tiletype tt = *Maps::getTileType(resting_pos); + df::tiletype_shape_basic basic_shape = tileShapeBasic(tileShape(tt)); + if (isWalkable(tt) && basic_shape != df::tiletype_shape_basic::Open) + break; + --resting_pos.z; + } + + return resting_pos; +} + +static void create_boulders(color_ostream &out, + const std::map> &boulder_coords, + const dig_now_options &options) { + df::unit *unit = world->units.active[0]; + df::historical_entity *civ = df::historical_entity::find(unit->civ_id); + df::world_site *site = World::isFortressMode() ? + df::world_site::find(ui->site_id) : NULL; + + std::vector in_reag; + std::vector in_items; + + // TODO: if options.dump_boulders is set, define and use dump coordinates + + for (auto entry : boulder_coords) { + df::reaction_product_itemst *prod = + df::allocate(); + const std::vector &coords = entry.second; + + prod->item_type = df::item_type::BOULDER; + prod->item_subtype = -1; + prod->mat_type = 0; + prod->mat_index = entry.first; + prod->probability = 100; + prod->count = coords.size(); + prod->product_dimension = 1; + + std::vector out_products; + std::vector out_items; + + prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, + job_skill::NONE, 0, civ, site, NULL); + + size_t num_items = out_items.size(); + if (num_items != coords.size()) { + out.printerr("unexpected number of boulders produced; " + "some boulders may be missing.\n"); + num_items = min(num_items, entry.second.size()); + } + + for (size_t i = 0; i < num_items; ++i) { + DFCoord pos = simulate_fall(coords[i]); + if (pos.z < 0) { + out.printerr("unable to place boulder at (%d, %d, %d)\n", + coords[i].x, coords[i].y, coords[i].z); + continue; + } + out_items[i]->moveToGround(pos.x, pos.y, pos.z); + } + + delete(prod); + } +} + +static void unhide_dug_tiles(color_ostream &out, + const std::vector &dug_coords, + const dig_now_options &options) { + if (options.unhide_unreachable) { + for (DFCoord pos : dug_coords) { + if (Maps::getTileDesignation(pos)->bits.hidden) + flood_unhide(out, pos); + } + return; + } + + // TODO: hide all then flood unreveal starting from an active fortress unit +} + command_result dig_now(color_ostream &out, std::vector &) { CoreSuspender suspend; @@ -345,24 +529,21 @@ command_result dig_now(color_ostream &out, std::vector &) { return CR_FAILURE; } - // tracks which positions to unhide - std::vector dug_coords; + if (world->units.active.size() == 0) { + out.printerr("At least one unit must be alive!\n"); + return CR_FAILURE; + } - // scan the whole map for now. we can add in configurable boundaries later - DFCoord start(0, 0, 0); - uint32_t endx, endy, endz; - Maps::getTileSize(endx, endy, endz); - DFCoord end(endx, endy, endz); + dig_now_options options; - do_dig(out, dug_coords, start, end); + // track which positions were modified and where to produce boulders of a + // give material + std::vector dug_coords; + std::map> boulder_coords; - // unhide newly dug tiles. we can't do this in do_dig() since our MapCache - // wouldn't detect the changes made by reveal.unhideFlood() without - // invalidating and reinitializing on every call - for (DFCoord pos : dug_coords) { - if (Maps::getTileDesignation(pos)->bits.hidden) - flood_unhide(out, pos); - } + do_dig(out, dug_coords, boulder_coords, options); + create_boulders(out, boulder_coords, options); + unhide_dug_tiles(out, dug_coords, options); // force the game to recompute its walkability cache world->reindex_pathfinding = true; From 5f7dc86c5c4094bdc40b2353e25cb9d4baf63a70 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 4 Jun 2021 17:40:40 -0700 Subject: [PATCH 12/47] optionally dump generated boulders at pos --- plugins/dig-now.cpp | 86 +++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 3acf26e29..caffa80fc 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -7,6 +7,7 @@ #include "TileTypes.h" #include "LuaTools.h" +#include "modules/Gui.h" #include "modules/Maps.h" #include "modules/MapCache.h" #include "modules/Random.h" @@ -32,9 +33,6 @@ struct dig_now_options { DFCoord start; // upper-left coordinate, min z-level DFCoord end; // lower-right coordinate, max z-level - // whether to unhide dug regions that are unreachable from the main fortress - bool unhide_unreachable; - // percent chance ([0..100]) for creating a boulder for the given rock type uint32_t boulder_percent_layer; uint32_t boulder_percent_vein; @@ -60,7 +58,6 @@ struct dig_now_options { dig_now_options() : start(0, 0, 0), end(getMapSize()), - unhide_unreachable(false), boulder_percent_layer(25), boulder_percent_vein(33), boulder_percent_small_cluster(100), @@ -68,18 +65,6 @@ struct dig_now_options { dump_boulders(false) { } }; -static void flood_unhide(color_ostream &out, const DFCoord &pos) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 2) - || !Lua::PushModulePublic(out, L, "plugins.reveal", "unhideFlood")) - return; - - Lua::Push(L, pos); - Lua::SafeCall(out, L, 1, 0); -} - // inherit flags from passable tiles above and propagate to passable tiles below static void propagate_vertical_flags(MapExtras::MapCache &map, const DFCoord &pos) { @@ -343,7 +328,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, } static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, - const DFCoord &pos, bool engrave) { + const DFCoord &pos) { // TODO return false; } @@ -411,9 +396,8 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, } } } - } else if (td.bits.smooth > 0) { - bool want_engrave = td.bits.smooth == 2; - if (smooth_tile(out, map, pos, want_engrave)) { + } else if (td.bits.smooth == 1) { + if (smooth_tile(out, map, pos)) { to = map.occupancyAt(pos); td.bits.smooth = 0; map.setDesignationAt(pos, td); @@ -439,11 +423,11 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, } // if pos is empty space, teleport to floor below -// if we fall out of the world, z coordinate will be negative (invalid) +// if we fall out of the world, returned position will be invalid static DFCoord simulate_fall(const DFCoord &pos) { DFCoord resting_pos(pos); - while (resting_pos.z >= 0) { + while (Maps::ensureTileBlock(resting_pos)) { df::tiletype tt = *Maps::getTileType(resting_pos); df::tiletype_shape_basic basic_shape = tileShapeBasic(tileShape(tt)); if (isWalkable(tt) && basic_shape != df::tiletype_shape_basic::Open) @@ -465,7 +449,25 @@ static void create_boulders(color_ostream &out, std::vector in_reag; std::vector in_items; - // TODO: if options.dump_boulders is set, define and use dump coordinates + DFCoord dump_pos; + if (options.dump_boulders) { + if (Maps::isValidTilePos(options.cursor)) + dump_pos = simulate_fall(options.cursor); + else { + DFCoord cursor; + if (!Gui::getCursorCoords(cursor)) { + out.printerr( + "Can't get dump tile coordinates! Make sure you specify the" + " --cursor parameter or have an active cursor in DF.\n"); + return; + } + dump_pos = simulate_fall(cursor); + } + if (!Maps::isValidTilePos(dump_pos)) + out.printerr("Invalid dump tile coordinates! Ensure the --cursor" + " option or the in-game cursor specifies an open, non-wall" + " tile."); + } for (auto entry : boulder_coords) { df::reaction_product_itemst *prod = @@ -494,10 +496,12 @@ static void create_boulders(color_ostream &out, } for (size_t i = 0; i < num_items; ++i) { - DFCoord pos = simulate_fall(coords[i]); - if (pos.z < 0) { - out.printerr("unable to place boulder at (%d, %d, %d)\n", - coords[i].x, coords[i].y, coords[i].z); + DFCoord pos = Maps::isValidTilePos(dump_pos) ? + dump_pos : simulate_fall(coords[i]); + if (!Maps::isValidTilePos(pos)) { + out.printerr( + "unable to place boulder generated at (%d, %d, %d)\n", + coords[i].x, coords[i].y, coords[i].z); continue; } out_items[i]->moveToGround(pos.x, pos.y, pos.z); @@ -507,18 +511,24 @@ static void create_boulders(color_ostream &out, } } -static void unhide_dug_tiles(color_ostream &out, - const std::vector &dug_coords, - const dig_now_options &options) { - if (options.unhide_unreachable) { - for (DFCoord pos : dug_coords) { - if (Maps::getTileDesignation(pos)->bits.hidden) - flood_unhide(out, pos); - } +static void flood_unhide(color_ostream &out, const DFCoord &pos) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) + || !Lua::PushModulePublic(out, L, "plugins.reveal", "unhideFlood")) return; - } - // TODO: hide all then flood unreveal starting from an active fortress unit + Lua::Push(L, pos); + Lua::SafeCall(out, L, 1, 0); +} + +static void unhide_dug_tiles(color_ostream &out, + const std::vector &dug_coords) { + for (DFCoord pos : dug_coords) { + if (Maps::getTileDesignation(pos)->bits.hidden) + flood_unhide(out, pos); + } } command_result dig_now(color_ostream &out, std::vector &) { @@ -543,7 +553,7 @@ command_result dig_now(color_ostream &out, std::vector &) { do_dig(out, dug_coords, boulder_coords, options); create_boulders(out, boulder_coords, options); - unhide_dug_tiles(out, dug_coords, options); + unhide_dug_tiles(out, dug_coords); // force the game to recompute its walkability cache world->reindex_pathfinding = true; From d7f417051dd2eeb7f789e3bf02a3dbadc1f596e5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 4 Jun 2021 21:42:49 -0700 Subject: [PATCH 13/47] implement track carving --- plugins/dig-now.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index caffa80fc..6d89f1d50 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -333,10 +333,27 @@ static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, return false; } -static bool carve_tile(color_ostream &out, MapExtras::MapCache &map, +static bool carve_tile(MapExtras::MapCache &map, const DFCoord &pos, df::tile_occupancy &to) { - // TODO - return false; + df::tiletype tt = map.tiletypeAt(pos); + TileDirection tdir = tileDirection(tt); + + if (to.bits.carve_track_north) + tdir.north = 1; + if (to.bits.carve_track_east) + tdir.east = 1; + if (to.bits.carve_track_south) + tdir.south = 1; + if (to.bits.carve_track_west) + tdir.west = 1; + + tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), + df::tiletype_special::TRACK, tdir); + if (tt == df::tiletype::Void) + return false; + + map.setTiletypeAt(pos, tt); + return true; } static bool produces_boulder(const dig_now_options &options, @@ -406,7 +423,7 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, || to.bits.carve_track_east == 1 || to.bits.carve_track_south == 1 || to.bits.carve_track_west == 1) { - if (carve_tile(out, map, pos, to)) { + if (carve_tile(map, pos, to)) { to = map.occupancyAt(pos); to.bits.carve_track_north = 0; to.bits.carve_track_east = 0; From 6bc2a2bdad29ecaa24fc13219b7e3020af552720 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 5 Jun 2021 06:49:51 -0700 Subject: [PATCH 14/47] implement tile smoothing --- plugins/dig-now.cpp | 80 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 6d89f1d50..1cde42970 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -217,12 +217,16 @@ static void clean_ramps(MapExtras::MapCache &map, const DFCoord &pos) { clean_ramp(map, DFCoord(pos.x, pos.y+1, pos.z)); } -// returns material type of walls at the given position, or -1 if not a wall -static int16_t get_wall_mat(MapExtras::MapCache &map, const DFCoord &pos) { +// returns material type of prospective boulders generated by the wall tile at +// the given position, or -1 if not a boulderable wall +static int16_t get_boulder_mat(MapExtras::MapCache &map, const DFCoord &pos) { df::tiletype tt = map.tiletypeAt(pos); df::tiletype_shape shape = tileShape(tt); + df::tiletype_material tmat = tileMaterial(tt); - if (shape != df::tiletype_shape::WALL) + if (shape != df::tiletype_shape::WALL + || (tmat != df::tiletype_material::STONE + && tmat != df::tiletype_material::MINERAL)) return -1; t_matpair matpair; @@ -235,7 +239,7 @@ static int16_t get_wall_mat(MapExtras::MapCache &map, const DFCoord &pos) { static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, const DFCoord &pos, df::tile_dig_designation designation, - int16_t *wall_mat) { + int16_t *boulder_mat) { df::tiletype tt = map.tiletypeAt(pos); // TODO: handle tree trunks, roots, and surface tiles @@ -243,7 +247,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (!isGroundMaterial(tile_mat)) return false; - *wall_mat = get_wall_mat(map, pos); + *boulder_mat = get_boulder_mat(map, pos); df::tiletype target_type = df::tiletype::Void; switch(designation) { @@ -265,7 +269,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, DFCoord pos_below(pos.x, pos.y, pos.z-1); if (map.ensureBlockAt(pos_below) && dig_tile(out, map, pos_below, - df::tile_dig_designation::Ramp, wall_mat)) { + df::tile_dig_designation::Ramp, boulder_mat)) { clean_ramps(map, pos_below); // if we successfully dug out the ramp below, that took care // of the ramp top here @@ -297,7 +301,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (can_dig_ramp(tt)) { target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); DFCoord pos_above(pos.x, pos.y, pos.z+1); - wall_mat[1] = get_wall_mat(map, pos_above); + boulder_mat[1] = get_boulder_mat(map, pos_above); if (target_type != tt && map.ensureBlockAt(pos_above)) { // set tile type directly instead of calling dig_shape // because we need to use *this* tile's material, not the @@ -327,12 +331,60 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, return true; } +// connects adjacent smooth walls to our new smooth wall +static bool adjust_smooth_wall_dir(MapExtras::MapCache &map, + const DFCoord &pos, + TileDirection adjacent_tdir) { + df::tiletype tt = map.tiletypeAt(pos); + if (tileSpecial(tt) != df::tiletype_special::SMOOTH + || tileShape(tt) != df::tiletype_shape::WALL) + return false; + + TileDirection tdir = tileDirection(tt); + tdir.whole |= adjacent_tdir.whole; + + tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), + tileSpecial(tt), tdir); + if (tt == df::tiletype::Void) + return false; + + map.setTiletypeAt(pos, tt); + return true; +} + +// assumes that if the game let you designate a tile for smoothing, it must be +// valid to do so. static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, const DFCoord &pos) { - // TODO - return false; + df::tiletype tt = map.tiletypeAt(pos); + + TileDirection tdir; + if (tileShape(tt) == df::tiletype_shape::WALL) { + if (adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y-1, pos.z), + TileDirection(1, 1, 0, 0))) + tdir.north = tdir.south = 1; + if (adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y+1, pos.z), + TileDirection(1, 1, 0, 0))) + tdir.north = tdir.south = 1; + if (adjust_smooth_wall_dir(map, DFCoord(pos.x-1, pos.y, pos.z), + TileDirection(0, 0, 1, 1))) + tdir.east = tdir.west = 1; + if (adjust_smooth_wall_dir(map, DFCoord(pos.x+1, pos.y, pos.z), + TileDirection(0, 0, 1, 1))) + tdir.east = tdir.west = 1; + } + + tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), + df::tiletype_special::SMOOTH, tdir); + if (tt == df::tiletype::Void) + return false; + + map.setTiletypeAt(pos, tt); + return true; } +// assumes that if the game let you designate a tile for track carving, it must +// be valid to do so. static bool carve_tile(MapExtras::MapCache &map, const DFCoord &pos, df::tile_occupancy &to) { df::tiletype tt = map.tiletypeAt(pos); @@ -397,19 +449,19 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, df::tile_designation td = map.designationAt(pos); df::tile_occupancy to = map.occupancyAt(pos); if (td.bits.dig != df::tile_dig_designation::No) { - int16_t wall_mat[2]; - memset(wall_mat, -1, sizeof(wall_mat)); - if (dig_tile(out, map, pos, td.bits.dig, wall_mat)) { + int16_t boulder_mat[2]; + memset(boulder_mat, -1, sizeof(boulder_mat)); + if (dig_tile(out, map, pos, td.bits.dig, boulder_mat)) { td = map.designationAt(pos); td.bits.dig = df::tile_dig_designation::No; map.setDesignationAt(pos, td); dug_coords.push_back(pos); for (size_t i = 0; i < 2; ++i) { - if (wall_mat[i] < 0) + if (boulder_mat[i] < 0) continue; if (produces_boulder(options, rng, map.BlockAtTile(pos)->veinTypeAt(pos))) { - boulder_coords[wall_mat[i]].push_back(pos); + boulder_coords[boulder_mat[i]].push_back(pos); } } } From f1b38fb6f608273bd4dcffaba3fb1e3c9432a9bc Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 5 Jun 2021 07:02:46 -0700 Subject: [PATCH 15/47] fix up comments --- plugins/dig-now.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 1cde42970..6a3ee4df9 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -43,8 +43,8 @@ struct dig_now_options { // dig locations bool dump_boulders; - // if set to the pos of a walkable tile, will dump at this position instead - // of the in-game cursor + // if set to the pos of a walkable tile (or somewhere above such a tile), + // will dump at this position instead of the in-game cursor DFCoord cursor; static DFCoord getMapSize() { @@ -65,7 +65,7 @@ struct dig_now_options { dump_boulders(false) { } }; -// inherit flags from passable tiles above and propagate to passable tiles below +// propagate light, outside, and subterranean flags to open tiles below this one static void propagate_vertical_flags(MapExtras::MapCache &map, const DFCoord &pos) { df::tile_designation td = map.designationAt(pos); @@ -491,8 +491,9 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, map.WriteAll(); } -// if pos is empty space, teleport to floor below -// if we fall out of the world, returned position will be invalid +// if pos is empty space, teleport to a floor somewhere below +// if we fall out of the world (e.g. empty space or walls all the way down), +// returned position will be invalid static DFCoord simulate_fall(const DFCoord &pos) { DFCoord resting_pos(pos); @@ -594,10 +595,9 @@ static void flood_unhide(color_ostream &out, const DFCoord &pos) { static void unhide_dug_tiles(color_ostream &out, const std::vector &dug_coords) { - for (DFCoord pos : dug_coords) { + for (DFCoord pos : dug_coords) if (Maps::getTileDesignation(pos)->bits.hidden) flood_unhide(out, pos); - } } command_result dig_now(color_ostream &out, std::vector &) { @@ -608,6 +608,7 @@ command_result dig_now(color_ostream &out, std::vector &) { return CR_FAILURE; } + // required for boulder generation if (world->units.active.size() == 0) { out.printerr("At least one unit must be alive!\n"); return CR_FAILURE; @@ -616,7 +617,7 @@ command_result dig_now(color_ostream &out, std::vector &) { dig_now_options options; // track which positions were modified and where to produce boulders of a - // give material + // given material std::vector dug_coords; std::map> boulder_coords; From 71c605afd41ff8abf9f8dd1a456499f131242967 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 5 Jun 2021 17:26:23 -0700 Subject: [PATCH 16/47] make options configurable and document --- docs/Plugins.rst | 60 +++++++++++++++++ plugins/dig-now.cpp | 146 +++++++++++++++++++++++++++------------- plugins/lua/dig-now.lua | 115 +++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 46 deletions(-) create mode 100644 plugins/lua/dig-now.lua diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 4d6316245..b0621a373 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2835,6 +2835,66 @@ Options: :building: Subsequent items will become part of the currently selected building. Good for loading traps; do not use with workshops (or deconstruct to use the item). +dig-now +======= + +Instantly completes dig designations, modifying map tiles and creating boulders, +ores, and gems as if a miner were doing the mining or engraving. By default, all +dig designations on the map are completed and boulder generation follows +standard game rules, but the behavior is configurable. + +Note that no units will get mining or engraving experience for the dug/engraved +tiles. + +Currently, trees are not chopped down and any designated trees or roots will be +ignored. Engravings are also not automatically generated by this plugin as they +depend on the skill and creative choices of individual engravers. + +Usage:: + + dig-now [ ] [] + +Where the optional ```` pair can be used to specify the coordinate bounds +within which ``dig-now`` will operate. If they are not specified, ``dig-now`` +will scan the entire map. + +Any ```` parameters can either be an ``,,`` triple (e.g. +``35,12,150``) or the string ``here``, which means the position of the active +game cursor should be used. + +Examples: + +``dig-now`` + Dig designated tiles according to standard game rules. + +``dig-now --clean`` + Dig designated tiles, but don't generate any boulders, ores, or gems. + +``dig-now --dump here`` + Dig tiles and dump all generated boulders, ores, and gems at the tile under + the game cursor. + +Options: + +:``-c``, ``--clean``: + Don't generate any boulders, ores, or gems. Equivalent to + ``--percentages 0,0,0,0``. +:``-d``, ``--dump ``: + Dump any generated items at the specified coordinates. If the tile at those + coordinates is open space or is a wall, items will be generated on the + closest walkable tile below. +:``-e``, ``--everywhere``: + Generate a boulder, ore, or gem for every tile that can produce one. + Equivalent to ``--percentages 100,100,100,100``. +:``-h``, ``--help``: + Show quick usage help text. +:``-p``, ``--percentages ,,,``: + Set item generation percentages for each of the tile categories. The + ``vein`` category includes both the large oval clusters and the long stringy + mineral veins. Default is ``25,33,100,100``. +:``-z``, ``--cur-zlevel``: + Restricts the bounds to the currently visible z-level. + .. _diggingInvaders: diggingInvaders diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 6a3ee4df9..c9a218aae 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -29,23 +29,41 @@ REQUIRE_GLOBAL(world); using namespace DFHack; +struct boulder_percent_options { + // percent chance ([0..100]) for creating a boulder for the given rock type + uint32_t layer; + uint32_t vein; + uint32_t small_cluster; + uint32_t deep; + + // defaults from + // https://dwarffortresswiki.org/index.php/DF2014:Mining + boulder_percent_options() : + layer(25), vein(33), small_cluster(100), deep(100) { } + + static struct_identity _identity; +}; +static const struct_field_info boulder_percent_options_fields[] = { + { struct_field_info::PRIMITIVE, "layer", offsetof(boulder_percent_options, layer), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "vein", offsetof(boulder_percent_options, vein), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "small_cluster", offsetof(boulder_percent_options, small_cluster), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "deep", offsetof(boulder_percent_options, deep), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity boulder_percent_options::_identity(sizeof(boulder_percent_options), &df::allocator_fn, NULL, "boulder_percents", NULL, boulder_percent_options_fields); + struct dig_now_options { + bool help; // whether to show the short help + DFCoord start; // upper-left coordinate, min z-level DFCoord end; // lower-right coordinate, max z-level - // percent chance ([0..100]) for creating a boulder for the given rock type - uint32_t boulder_percent_layer; - uint32_t boulder_percent_vein; - uint32_t boulder_percent_small_cluster; - uint32_t boulder_percent_deep; - - // whether to generate boulders at the cursor position instead of at their - // dig locations - bool dump_boulders; + boulder_percent_options boulder_percents; // if set to the pos of a walkable tile (or somewhere above such a tile), - // will dump at this position instead of the in-game cursor - DFCoord cursor; + // will dump generated boulders at this position instead of at their dig + // locations + DFCoord dump_pos; static DFCoord getMapSize() { uint32_t endx, endy, endz; @@ -53,17 +71,19 @@ struct dig_now_options { return DFCoord(endx, endy, endz); } - // boulder percentage defaults from - // https://dwarffortresswiki.org/index.php/DF2014:Mining - dig_now_options() : - start(0, 0, 0), - end(getMapSize()), - boulder_percent_layer(25), - boulder_percent_vein(33), - boulder_percent_small_cluster(100), - boulder_percent_deep(100), - dump_boulders(false) { } + dig_now_options() : help(false), start(0, 0, 0), end(getMapSize()) { } + + static struct_identity _identity; +}; +static const struct_field_info dig_now_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(dig_now_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::SUBSTRUCT, "start", offsetof(dig_now_options, start), &df::coord::_identity, 0, 0 }, + { struct_field_info::SUBSTRUCT, "end", offsetof(dig_now_options, end), &df::coord::_identity, 0, 0 }, + { struct_field_info::SUBSTRUCT, "boulder_percents", offsetof(dig_now_options, boulder_percents), &boulder_percent_options::_identity, 0, 0 }, + { struct_field_info::SUBSTRUCT, "dump_pos", offsetof(dig_now_options, dump_pos), &df::coord::_identity, 0, 0 }, + { struct_field_info::END } }; +struct_identity dig_now_options::_identity(sizeof(dig_now_options), &df::allocator_fn, NULL, "dig_now_options", NULL, dig_now_options_fields); // propagate light, outside, and subterranean flags to open tiles below this one static void propagate_vertical_flags(MapExtras::MapCache &map, @@ -408,24 +428,28 @@ static bool carve_tile(MapExtras::MapCache &map, return true; } -static bool produces_boulder(const dig_now_options &options, +static bool produces_boulder(const boulder_percent_options &options, Random::MersenneRNG &rng, + df::tiletype_material tmat, df::inclusion_type vein_type) { uint32_t probability; switch (vein_type) { case df::inclusion_type::CLUSTER: case df::inclusion_type::VEIN: - probability = options.boulder_percent_vein; + probability = options.vein; break; case df::inclusion_type::CLUSTER_ONE: case df::inclusion_type::CLUSTER_SMALL: - probability = options.boulder_percent_small_cluster; + probability = options.small_cluster; break; default: - probability = options.boulder_percent_layer; + probability = options.layer; break; } + if (tmat == df::tiletype_material::FEATURE) + probability = options.deep; + return rng.random(100) < probability; } @@ -451,6 +475,8 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, if (td.bits.dig != df::tile_dig_designation::No) { int16_t boulder_mat[2]; memset(boulder_mat, -1, sizeof(boulder_mat)); + df::tiletype_material tmat = + tileMaterial(map.tiletypeAt(pos)); if (dig_tile(out, map, pos, td.bits.dig, boulder_mat)) { td = map.designationAt(pos); td.bits.dig = df::tile_dig_designation::No; @@ -459,8 +485,8 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, for (size_t i = 0; i < 2; ++i) { if (boulder_mat[i] < 0) continue; - if (produces_boulder(options, rng, - map.BlockAtTile(pos)->veinTypeAt(pos))) { + if (produces_boulder(options.boulder_percents, rng, + tmat, map.BlockAtTile(pos)->veinTypeAt(pos))) { boulder_coords[boulder_mat[i]].push_back(pos); } } @@ -520,23 +546,11 @@ static void create_boulders(color_ostream &out, std::vector in_items; DFCoord dump_pos; - if (options.dump_boulders) { - if (Maps::isValidTilePos(options.cursor)) - dump_pos = simulate_fall(options.cursor); - else { - DFCoord cursor; - if (!Gui::getCursorCoords(cursor)) { - out.printerr( - "Can't get dump tile coordinates! Make sure you specify the" - " --cursor parameter or have an active cursor in DF.\n"); - return; - } - dump_pos = simulate_fall(cursor); - } + if (Maps::isValidTilePos(options.dump_pos)) { + dump_pos = simulate_fall(options.dump_pos); if (!Maps::isValidTilePos(dump_pos)) - out.printerr("Invalid dump tile coordinates! Ensure the --cursor" - " option or the in-game cursor specifies an open, non-wall" - " tile."); + out.printerr("Invalid dump tile coordinates! Ensure the --dump" + " option specifies an open, non-wall tile."); } for (auto entry : boulder_coords) { @@ -600,9 +614,51 @@ static void unhide_dug_tiles(color_ostream &out, flood_unhide(out, pos); } -command_result dig_now(color_ostream &out, std::vector &) { +static bool get_options(color_ostream &out, + dig_now_options &opts, + const std::vector ¶meters) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.dig-now", "parse_commandline")) { + out.printerr("Failed to load dig-now Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + + for (const std::string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static void print_help(color_ostream &out) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(out, L, "plugins.dig-now", "print_help") || + !Lua::SafeCall(out, L, 0, 0)) { + out.printerr("Failed to load dig-now Lua code\n"); + } +} + +command_result dig_now(color_ostream &out, std::vector ¶ms) { CoreSuspender suspend; + dig_now_options options; + if (!get_options(out, options, params) || options.help) + { + print_help(out); + return options.help ? CR_OK : CR_FAILURE; + } + if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; @@ -614,8 +670,6 @@ command_result dig_now(color_ostream &out, std::vector &) { return CR_FAILURE; } - dig_now_options options; - // track which positions were modified and where to produce boulders of a // given material std::vector dug_coords; diff --git a/plugins/lua/dig-now.lua b/plugins/lua/dig-now.lua new file mode 100644 index 000000000..e13f16aff --- /dev/null +++ b/plugins/lua/dig-now.lua @@ -0,0 +1,115 @@ +local _ENV = mkmodule('plugins.dig-now') + +local utils = require('utils') +local guidm = require('gui.dwarfmode') + +local short_help_text = [=[ + +dig-now +======= + +Instantly completes dig designations, modifying map tiles and creating boulders, +ores, and gems as if a miner were doing the mining or engraving. By default, all +dig designations on the map are completed and boulder generation follows +standard game rules, but the behavior is configurable. + +Usage: + + dig-now [ ] [] + +Examples: + +dig-now + Dig designated tiles according to standard game rules. + +dig-now --clean + Dig designated tiles, but don't generate any boulders, ores, or gems. + +dig-now --dump here + Dig tiles and dump all generated boulders, ores, and gems at the tile under + the game cursor. + +See the online DFHack documentation for details on all options. +]=] + +function print_help() print(short_help_text) end + +local function parse_coords(opts, configname, arg) + local x, y, z + if arg == 'here' then + local cursor = guidm.getCursorPos() + if not cursor then + qerror('"here" specified for position, but no game cursor found!') + end + x, y, z = cursor.x, cursor.y, cursor.z + else + _, _, x, y, z = arg:find('^%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*$') + if not x then + qerror(('invalid position: "%s"; expected "here" or' .. + ' ",," triple (e.g. "30,60,150")'):format(arg)) + end + end + opts[configname].x = tonumber(x) + opts[configname].y = tonumber(y) + opts[configname].z = tonumber(z) +end + +local function out_of_bounds(percentage) + percentage = tonumber(percentage) + return percentage < 0 or percentage > 100 +end + +local function parse_percentages(opts, arg) + _, _, layer, vein, small, deep = + arg:find('^%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*$') + if not layer or out_of_bounds(layer) or out_of_bounds(vein) + or out_of_bounds(small) or out_of_bounds(deep) then + qerror(('invalid percentages: "%s"; expected format is ",' .. + ',,", where each number is between'.. + ' 0 and 100, inclusive (e.g. "0,33,100,100")'):format(arg)) + end + local config = opts.boulder_percents + config.layer, config.vein, config.small_cluster, config.deep = + tonumber(layer), tonumber(vein), tonumber(small), tonumber(deep) +end + +local function min_to_max(...) + local args = {...} + table.sort(args, function(a, b) return a < b end) + return table.unpack(args) +end + +function parse_commandline(opts, ...) + local use_zlevel = false + local positionals = utils.processArgsGetopt({...}, { + {'c', 'clean', + handler=function() parse_percentages(opts, '0,0,0,0') end}, + {'d', 'dump', hasArg=true, + handler=function(arg) parse_coords(opts, 'dump_pos', arg) end}, + {'e', 'everywhere', + handler=function() parse_percentages(opts, '100,100,100,100') end}, + {'h', 'help', handler=function() opts.help = true end}, + {'p', 'percentages', hasArg=true, + handler=function(arg) parse_percentages(opts, arg) end}, + {'z', 'cur-zlevel', handler=function() use_zlevel = true end}, + }) + + if positionals[1] == 'help' then opts.help = true end + if opts.help then return end + + if use_zlevel then + local x, y, z = df.global.world.map.x_count - 1, + df.global.world.map.y_count - 1, + df.global.window_z + parse_coords(opts, 'start', ('0,0,%d'):format(z)) + parse_coords(opts, 'end', ('%d,%d,%d'):format(x, y, z)) + elseif #positionals >= 2 then + parse_coords(opts, 'start', positionals[1]) + parse_coords(opts, 'end', positionals[2]) + opts.start.x, opts['end'].x = min_to_max(opts.start.x, opts['end'].x) + opts.start.y, opts['end'].y = min_to_max(opts.start.y, opts['end'].y) + opts.start.z, opts['end'].z = min_to_max(opts.start.z, opts['end'].z) + end +end + +return _ENV From c1dcaa43786422b9c18cbc525e5a9c623e05a7ad Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 6 Jun 2021 05:52:20 -0700 Subject: [PATCH 17/47] generate gems as ROUGH, not BOULDER also fix missing dug_tile indices when channels are dug also fix second boulder/gem being generated with a potentially incorrect probability when digging channels and ramps --- plugins/dig-now.cpp | 152 ++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 70 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index c9a218aae..2841a9888 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -237,29 +237,39 @@ static void clean_ramps(MapExtras::MapCache &map, const DFCoord &pos) { clean_ramp(map, DFCoord(pos.x, pos.y+1, pos.z)); } -// returns material type of prospective boulders generated by the wall tile at -// the given position, or -1 if not a boulderable wall -static int16_t get_boulder_mat(MapExtras::MapCache &map, const DFCoord &pos) { - df::tiletype tt = map.tiletypeAt(pos); - df::tiletype_shape shape = tileShape(tt); - df::tiletype_material tmat = tileMaterial(tt); - - if (shape != df::tiletype_shape::WALL - || (tmat != df::tiletype_material::STONE - && tmat != df::tiletype_material::MINERAL)) - return -1; - - t_matpair matpair; - if (map.isVeinAt(pos)) - matpair = map.veinMaterialAt(pos); - else if (map.isLayerAt(pos)) - matpair = map.layerMaterialAt(pos); - return matpair.mat_type; -} +struct dug_tile_info { + DFCoord pos; + df::tiletype_material tmat; + df::item_type itype; + int32_t imat; // mat idx of boulder/gem potentially generated at this pos + + dug_tile_info(MapExtras::MapCache &map, const DFCoord &pos) { + this->pos = pos; + + df::tiletype tt = map.tiletypeAt(pos); + tmat = tileMaterial(tt); + + switch (map.BlockAtTile(pos)->veinTypeAt(pos)) { + case df::inclusion_type::CLUSTER_ONE: + case df::inclusion_type::CLUSTER_SMALL: + itype = df::item_type::ROUGH; + break; + default: + itype = df::item_type::BOULDER; + } + + imat = -1; + if (tileShape(tt) == df::tiletype_shape::WALL + && (tmat == df::tiletype_material::STONE + || tmat == df::tiletype_material::MINERAL + || tmat == df::tiletype_material::FEATURE)) + imat = map.baseMaterialAt(pos).mat_index; + } +}; static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, const DFCoord &pos, df::tile_dig_designation designation, - int16_t *boulder_mat) { + std::vector &dug_tiles) { df::tiletype tt = map.tiletypeAt(pos); // TODO: handle tree trunks, roots, and surface tiles @@ -267,8 +277,6 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (!isGroundMaterial(tile_mat)) return false; - *boulder_mat = get_boulder_mat(map, pos); - df::tiletype target_type = df::tiletype::Void; switch(designation) { case df::tile_dig_designation::Default: @@ -284,15 +292,14 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, break; case df::tile_dig_designation::Channel: if (can_dig_channel(tt)) { - remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+1)); target_type = df::tiletype::OpenSpace; DFCoord pos_below(pos.x, pos.y, pos.z-1); if (map.ensureBlockAt(pos_below) && dig_tile(out, map, pos_below, - df::tile_dig_designation::Ramp, boulder_mat)) { + df::tile_dig_designation::Ramp, dug_tiles)) { clean_ramps(map, pos_below); // if we successfully dug out the ramp below, that took care - // of the ramp top here + // of adding the ramp top here return true; } break; @@ -321,13 +328,17 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (can_dig_ramp(tt)) { target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); DFCoord pos_above(pos.x, pos.y, pos.z+1); - boulder_mat[1] = get_boulder_mat(map, pos_above); if (target_type != tt && map.ensureBlockAt(pos_above)) { + // only capture the tile info of pos_above if we didn't get + // here via the Channel case above + if (dug_tiles.size() == 0) + dug_tiles.push_back(dug_tile_info(map, pos_above)); // set tile type directly instead of calling dig_shape // because we need to use *this* tile's material, not the // material of the tile above map.setTiletypeAt(pos_above, findSimilarTileType(tt, df::tiletype_shape::RAMP_TOP)); + remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+2)); } } break; @@ -343,6 +354,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (target_type == df::tiletype::Void || target_type == tt) return false; + dug_tiles.push_back(dug_tile_info(map, pos)); dig_type(map, pos, target_type); // let light filter down to newly exposed tiles @@ -428,42 +440,45 @@ static bool carve_tile(MapExtras::MapCache &map, return true; } -static bool produces_boulder(const boulder_percent_options &options, - Random::MersenneRNG &rng, - df::tiletype_material tmat, - df::inclusion_type vein_type) { +static bool produces_item(const boulder_percent_options &options, + MapExtras::MapCache &map, Random::MersenneRNG &rng, + const dug_tile_info &info) { uint32_t probability; - switch (vein_type) { - case df::inclusion_type::CLUSTER: - case df::inclusion_type::VEIN: - probability = options.vein; - break; - case df::inclusion_type::CLUSTER_ONE: - case df::inclusion_type::CLUSTER_SMALL: - probability = options.small_cluster; - break; - default: - probability = options.layer; - break; - } - - if (tmat == df::tiletype_material::FEATURE) + if (info.tmat == df::tiletype_material::FEATURE) probability = options.deep; + else { + switch (map.BlockAtTile(info.pos)->veinTypeAt(info.pos)) { + case df::inclusion_type::CLUSTER: + case df::inclusion_type::VEIN: + probability = options.vein; + break; + case df::inclusion_type::CLUSTER_ONE: + case df::inclusion_type::CLUSTER_SMALL: + probability = options.small_cluster; + break; + default: + probability = options.layer; + break; + } + } return rng.random(100) < probability; } +typedef std::map, std::vector> + item_coords_t; + static void do_dig(color_ostream &out, std::vector &dug_coords, - std::map> &boulder_coords, - const dig_now_options &options) { + item_coords_t &item_coords, const dig_now_options &options) { MapExtras::MapCache map; Random::MersenneRNG rng; rng.init(); - for (uint32_t z = options.start.z; z <= options.end.z; ++z) { - for (uint32_t y = options.start.y; y <= options.end.y; ++y) { - for (uint32_t x = options.start.x; x <= options.end.x; ++x) { + // go down levels instead of up so stacked ramps behave as expected + for (int16_t z = options.end.z; z >= options.start.z; --z) { + for (int16_t y = options.start.y; y <= options.end.y; ++y) { + for (int16_t x = options.start.x; x <= options.end.x; ++x) { // this will return NULL if the map block hasn't been allocated // yet, but that means there aren't any designations anyway. if (!Maps::getTileBlock(x, y, z)) @@ -473,21 +488,19 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, df::tile_designation td = map.designationAt(pos); df::tile_occupancy to = map.occupancyAt(pos); if (td.bits.dig != df::tile_dig_designation::No) { - int16_t boulder_mat[2]; - memset(boulder_mat, -1, sizeof(boulder_mat)); - df::tiletype_material tmat = - tileMaterial(map.tiletypeAt(pos)); - if (dig_tile(out, map, pos, td.bits.dig, boulder_mat)) { + std::vector dug_tiles; + if (dig_tile(out, map, pos, td.bits.dig, dug_tiles)) { td = map.designationAt(pos); td.bits.dig = df::tile_dig_designation::No; map.setDesignationAt(pos, td); - dug_coords.push_back(pos); - for (size_t i = 0; i < 2; ++i) { - if (boulder_mat[i] < 0) + for (auto info : dug_tiles) { + dug_coords.push_back(info.pos); + if (info.imat < 0) continue; - if (produces_boulder(options.boulder_percents, rng, - tmat, map.BlockAtTile(pos)->veinTypeAt(pos))) { - boulder_coords[boulder_mat[i]].push_back(pos); + if (produces_item(options.boulder_percents, + map, rng, info)) { + auto k = std::make_pair(info.itype, info.imat); + item_coords[k].push_back(info.pos); } } } @@ -535,7 +548,7 @@ static DFCoord simulate_fall(const DFCoord &pos) { } static void create_boulders(color_ostream &out, - const std::map> &boulder_coords, + const item_coords_t &item_coords, const dig_now_options &options) { df::unit *unit = world->units.active[0]; df::historical_entity *civ = df::historical_entity::find(unit->civ_id); @@ -553,15 +566,15 @@ static void create_boulders(color_ostream &out, " option specifies an open, non-wall tile."); } - for (auto entry : boulder_coords) { + for (auto entry : item_coords) { df::reaction_product_itemst *prod = df::allocate(); const std::vector &coords = entry.second; - prod->item_type = df::item_type::BOULDER; + prod->item_type = entry.first.first; prod->item_subtype = -1; prod->mat_type = 0; - prod->mat_index = entry.first; + prod->mat_index = entry.first.second; prod->probability = 100; prod->count = coords.size(); prod->product_dimension = 1; @@ -670,13 +683,12 @@ command_result dig_now(color_ostream &out, std::vector ¶ms) { return CR_FAILURE; } - // track which positions were modified and where to produce boulders of a - // given material + // track which positions were modified and where to produce items std::vector dug_coords; - std::map> boulder_coords; + item_coords_t item_coords; - do_dig(out, dug_coords, boulder_coords, options); - create_boulders(out, boulder_coords, options); + do_dig(out, dug_coords, item_coords, options); + create_boulders(out, item_coords, options); unhide_dug_tiles(out, dug_coords); // force the game to recompute its walkability cache From 61a18b14beb5830e74ea63d20ccd2149f4895df3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 6 Jun 2021 06:46:59 -0700 Subject: [PATCH 18/47] fix adjacent smooth walls when smoothing --- plugins/dig-now.cpp | 60 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 2841a9888..3e293c0c7 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -363,18 +363,49 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, return true; } +static bool is_smooth_wall(MapExtras::MapCache &map, const DFCoord &pos) { + df::tiletype tt = map.tiletypeAt(pos); + return tileSpecial(tt) == df::tiletype_special::SMOOTH + && tileShape(tt) == df::tiletype_shape::WALL; +} + +// adds adjacent smooth walls to the given tdir +static TileDirection get_adjacent_smooth_walls(MapExtras::MapCache &map, + const DFCoord &pos, + TileDirection tdir) { + if (is_smooth_wall(map, DFCoord(pos.x, pos.y-1, pos.z))) + tdir.north = 1; + if (is_smooth_wall(map, DFCoord(pos.x, pos.y+1, pos.z))) + tdir.south = 1; + if (is_smooth_wall(map, DFCoord(pos.x-1, pos.y, pos.z))) + tdir.west = 1; + if (is_smooth_wall(map, DFCoord(pos.x+1, pos.y, pos.z))) + tdir.east = 1; + return tdir; +} + +// ensure we have at least two directions enabled so we can find a matching +// tiletype +static TileDirection ensure_valid_tdir(TileDirection tdir) { + if (tdir.sum() < 2) { + if (tdir.north) tdir.south = 1; + else if (tdir.south) tdir.north = 1; + else if (tdir.east) tdir.west = 1; + else if (tdir.west) tdir.east = 1; + } + return tdir; +} + // connects adjacent smooth walls to our new smooth wall static bool adjust_smooth_wall_dir(MapExtras::MapCache &map, const DFCoord &pos, - TileDirection adjacent_tdir) { - df::tiletype tt = map.tiletypeAt(pos); - if (tileSpecial(tt) != df::tiletype_special::SMOOTH - || tileShape(tt) != df::tiletype_shape::WALL) + TileDirection tdir) { + if (!is_smooth_wall(map, pos)) return false; - TileDirection tdir = tileDirection(tt); - tdir.whole |= adjacent_tdir.whole; + tdir = ensure_valid_tdir(get_adjacent_smooth_walls(map, pos, tdir)); + df::tiletype tt = map.tiletypeAt(pos); tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), tileSpecial(tt), tdir); if (tt == df::tiletype::Void) @@ -393,17 +424,18 @@ static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, TileDirection tdir; if (tileShape(tt) == df::tiletype_shape::WALL) { if (adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y-1, pos.z), - TileDirection(1, 1, 0, 0))) - tdir.north = tdir.south = 1; + TileDirection(0, 1, 0, 0))) + tdir.north = 1; if (adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y+1, pos.z), - TileDirection(1, 1, 0, 0))) - tdir.north = tdir.south = 1; + TileDirection(1, 0, 0, 0))) + tdir.south = 1; if (adjust_smooth_wall_dir(map, DFCoord(pos.x-1, pos.y, pos.z), - TileDirection(0, 0, 1, 1))) - tdir.east = tdir.west = 1; + TileDirection(0, 0, 0, 1))) + tdir.west = 1; if (adjust_smooth_wall_dir(map, DFCoord(pos.x+1, pos.y, pos.z), - TileDirection(0, 0, 1, 1))) - tdir.east = tdir.west = 1; + TileDirection(0, 0, 1, 0))) + tdir.east = 1; + tdir = ensure_valid_tdir(tdir); } tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), From c645cd545456fb95fa639b4b385521169c9bba94 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 6 Jun 2021 08:31:21 -0700 Subject: [PATCH 19/47] move units and items to ground when it disappears --- docs/Plugins.rst | 15 ++++++++------- plugins/dig-now.cpp | 44 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index b0621a373..d3c0b10be 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2838,17 +2838,18 @@ Options: dig-now ======= -Instantly completes dig designations, modifying map tiles and creating boulders, -ores, and gems as if a miner were doing the mining or engraving. By default, all -dig designations on the map are completed and boulder generation follows -standard game rules, but the behavior is configurable. +Instantly completes dig designations for soil, rock, and mineral tiles, +modifying tile shapes and creating boulders, ores, and gems as if a miner were +doing the mining or engraving. By default, the entire map is processed and +boulder generation follows standard game rules, but the behavior is +configurable. Note that no units will get mining or engraving experience for the dug/engraved tiles. -Currently, trees are not chopped down and any designated trees or roots will be -ignored. Engravings are also not automatically generated by this plugin as they -depend on the skill and creative choices of individual engravers. +Surface tiles, trees, and roots will be ignored. Engravings are also not +automatically generated by this plugin as they depend on the skill and creative +choices of individual engravers. Usage:: diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 3e293c0c7..15e9edebd 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -11,6 +11,7 @@ #include "modules/Maps.h" #include "modules/MapCache.h" #include "modules/Random.h" +#include "modules/Units.h" #include "modules/World.h" #include @@ -272,7 +273,6 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, std::vector &dug_tiles) { df::tiletype tt = map.tiletypeAt(pos); - // TODO: handle tree trunks, roots, and surface tiles df::tiletype_material tile_mat = tileMaterial(tt); if (!isGroundMaterial(tile_mat)) return false; @@ -593,7 +593,7 @@ static void create_boulders(color_ostream &out, DFCoord dump_pos; if (Maps::isValidTilePos(options.dump_pos)) { dump_pos = simulate_fall(options.dump_pos); - if (!Maps::isValidTilePos(dump_pos)) + if (!Maps::ensureTileBlock(dump_pos)) out.printerr("Invalid dump tile coordinates! Ensure the --dump" " option specifies an open, non-wall tile."); } @@ -627,7 +627,7 @@ static void create_boulders(color_ostream &out, for (size_t i = 0; i < num_items; ++i) { DFCoord pos = Maps::isValidTilePos(dump_pos) ? dump_pos : simulate_fall(coords[i]); - if (!Maps::isValidTilePos(pos)) { + if (!Maps::ensureTileBlock(pos)) { out.printerr( "unable to place boulder generated at (%d, %d, %d)\n", coords[i].x, coords[i].y, coords[i].z); @@ -652,11 +652,43 @@ static void flood_unhide(color_ostream &out, const DFCoord &pos) { Lua::SafeCall(out, L, 1, 0); } -static void unhide_dug_tiles(color_ostream &out, +static void post_process_dug_tiles(color_ostream &out, const std::vector &dug_coords) { - for (DFCoord pos : dug_coords) + for (DFCoord pos : dug_coords) { if (Maps::getTileDesignation(pos)->bits.hidden) flood_unhide(out, pos); + + df::tile_occupancy &to = *Maps::getTileOccupancy(pos); + if (to.bits.unit or to.bits.item) { + DFCoord resting_pos = simulate_fall(pos); + if (resting_pos == pos) + continue; + + if (!Maps::ensureTileBlock(resting_pos)) { + out.printerr("No valid tile beneath (%d, %d, %d); can't move" + " units and items to floor", + pos.x, pos.y, pos.z); + continue; + } + + if (to.bits.unit) { + std::vector units; + Units::getUnitsInBox(units, pos.x, pos.y, pos.z, + pos.x, pos.y, pos.z); + for (auto unit : units) + Units::teleport(unit, resting_pos); + } + + if (to.bits.item) { + for (auto item : world->items.other.IN_PLAY) { + if (item->pos == pos and item->flags.bits.on_ground) + item->moveToGround( + resting_pos.x, resting_pos.y, resting_pos.z); + } + } + } + + } } static bool get_options(color_ostream &out, @@ -721,7 +753,7 @@ command_result dig_now(color_ostream &out, std::vector ¶ms) { do_dig(out, dug_coords, item_coords, options); create_boulders(out, item_coords, options); - unhide_dug_tiles(out, dug_coords); + post_process_dug_tiles (out, dug_coords); // force the game to recompute its walkability cache world->reindex_pathfinding = true; From 9e8a35d263010eebfc4c61c50239743e08a7eb9d Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 6 Jun 2021 09:15:04 -0700 Subject: [PATCH 20/47] update changelog --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6963ec61f..fab594d6f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## New Plugins +- `dig-now`: instantly completes dig designations (including smoothing and carving tracks) + ## Misc Improvements - `tiletypes-here`, `tiletypes-here-point`: add --cursor and --quiet options to support non-interactive use cases From 8349afa39bfa0b8a3557a3345034130244cf21a1 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 12 Jun 2021 10:56:15 -0700 Subject: [PATCH 21/47] destroy colonies when digging a ramp underneath --- plugins/dig-now.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 15e9edebd..aaea4d50b 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -238,6 +239,21 @@ static void clean_ramps(MapExtras::MapCache &map, const DFCoord &pos) { clean_ramp(map, DFCoord(pos.x, pos.y+1, pos.z)); } +// destroys any colonies located at pos +static void destroy_colony(const DFCoord &pos) { + auto same_pos = [&](df::vermin *colony){ return colony->pos == pos; }; + + auto &colonies = world->vermin.colonies; + auto found_colony = std::find_if(begin(colonies), end(colonies), same_pos); + if (found_colony == end(colonies)) + return; + colonies.erase(found_colony); + + auto &all_vermin = world->vermin.all; + all_vermin.erase( + std::find_if(begin(all_vermin), end(all_vermin), same_pos)); +} + struct dug_tile_info { DFCoord pos; df::tiletype_material tmat; @@ -333,6 +349,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, // here via the Channel case above if (dug_tiles.size() == 0) dug_tiles.push_back(dug_tile_info(map, pos_above)); + destroy_colony(pos_above); // set tile type directly instead of calling dig_shape // because we need to use *this* tile's material, not the // material of the tile above From 7ea8c843b1449ec077a78a292843d8c0e65d5195 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 12 Jun 2021 11:16:00 -0700 Subject: [PATCH 22/47] allow digging of surface tiles now that we handle colonies --- plugins/dig-now.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index aaea4d50b..08e53f72b 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -284,13 +284,24 @@ struct dug_tile_info { } }; +static bool is_tree(df::tiletype tt) { + df::tiletype_material mat = tileMaterial(tt); + switch (mat) { + case df::tiletype_material::TREE: + case df::tiletype_material::ROOT: + return true; + default: + return false; + } +} + static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, const DFCoord &pos, df::tile_dig_designation designation, std::vector &dug_tiles) { df::tiletype tt = map.tiletypeAt(pos); - df::tiletype_material tile_mat = tileMaterial(tt); - if (!isGroundMaterial(tile_mat)) + // TODO: handle trees, roots, and log generation + if (is_tree(tt)) return false; df::tiletype target_type = df::tiletype::Void; From 5c853644f01c295d492b33ea94c2b32aafde4353 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 12 Jun 2021 11:46:48 -0700 Subject: [PATCH 23/47] let newly uncontained water flow --- plugins/dig-now.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 08e53f72b..8afb0e889 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -716,6 +716,8 @@ static void post_process_dug_tiles(color_ostream &out, } } + // refresh block metadata and flows + Maps::enableBlockUpdates(Maps::getTileBlock(pos), true, true); } } From 92656fc5c07b79750ed2222ada878e6c622ee6a9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 12 Jun 2021 11:49:23 -0700 Subject: [PATCH 24/47] update plugin docs since surface tiles are now handled --- docs/Plugins.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index d3c0b10be..b74d920a0 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2838,18 +2838,17 @@ Options: dig-now ======= -Instantly completes dig designations for soil, rock, and mineral tiles, -modifying tile shapes and creating boulders, ores, and gems as if a miner were -doing the mining or engraving. By default, the entire map is processed and -boulder generation follows standard game rules, but the behavior is -configurable. +Instantly completes dig designations, modifying tile shapes and creating +boulders, ores, and gems as if a miner were doing the mining or engraving. By +default, the entire map is processed and boulder generation follows standard +game rules, but the behavior is configurable. Note that no units will get mining or engraving experience for the dug/engraved tiles. -Surface tiles, trees, and roots will be ignored. Engravings are also not -automatically generated by this plugin as they depend on the skill and creative -choices of individual engravers. +Trees, and roots will be ignored. Engravings are also not automatically +generated by this plugin as they depend on the skill and creative choices of +individual engravers. Usage:: From 89210a503ed2d80b0986c9b5ef21da20f740eb09 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 18 Jun 2021 22:46:15 -0700 Subject: [PATCH 25/47] add global anchor for dig-now --- docs/Plugins.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index b74d920a0..3383e5ab7 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2835,6 +2835,8 @@ Options: :building: Subsequent items will become part of the currently selected building. Good for loading traps; do not use with workshops (or deconstruct to use the item). +.. _dig-now: + dig-now ======= From 662f2cc616873b21c080838cdedd141673bdecf8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 18 Jun 2021 23:28:38 -0700 Subject: [PATCH 26/47] don't dig the undiggable --- plugins/dig-now.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 8afb0e889..10babd5c9 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -284,15 +284,32 @@ struct dug_tile_info { } }; -static bool is_tree(df::tiletype tt) { +static bool is_diggable(MapExtras::MapCache &map, const DFCoord &pos, + df::tiletype tt) { df::tiletype_material mat = tileMaterial(tt); switch (mat) { + case df::tiletype_material::CONSTRUCTION: + case df::tiletype_material::POOL: + case df::tiletype_material::RIVER: case df::tiletype_material::TREE: case df::tiletype_material::ROOT: - return true; - default: + case df::tiletype_material::LAVA_STONE: + case df::tiletype_material::MAGMA: + case df::tiletype_material::HFS: + case df::tiletype_material::UNDERWORLD_GATE: return false; + default: + break; + } + + if (mat == df::tiletype_material::FEATURE) { + // adamantine is the only is diggable feature + t_feature feature; + return map.BlockAtTile(pos)->GetLocalFeature(&feature) + && feature.type == feature_type::deep_special_tube; } + + return true; } static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, @@ -300,8 +317,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, std::vector &dug_tiles) { df::tiletype tt = map.tiletypeAt(pos); - // TODO: handle trees, roots, and log generation - if (is_tree(tt)) + if (!is_diggable(map, pos, tt)) return false; df::tiletype target_type = df::tiletype::Void; From 59a34c6d178b34b652dd195e838994567db72f29 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 19 Jun 2021 12:00:13 -0700 Subject: [PATCH 27/47] remove ramp tops when channeling we normally take care of any ramp tops above the channeled tile when we dig the ramp below the channeled tile, but that logic might not run if we channel down into empty space (or undiggable tiles) --- plugins/dig-now.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 10babd5c9..e3146bcc8 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -336,6 +336,9 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, case df::tile_dig_designation::Channel: if (can_dig_channel(tt)) { target_type = df::tiletype::OpenSpace; + DFCoord pos_above(pos.x, pos.y, pos.z+1); + if (map.ensureBlockAt(pos_above)) + remove_ramp_top(map, pos_above); DFCoord pos_below(pos.x, pos.y, pos.z-1); if (map.ensureBlockAt(pos_below) && dig_tile(out, map, pos_below, From dfc938a99d99e0006ecd15a523d14f61d3bf1eeb Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 19 Jun 2021 13:51:13 -0700 Subject: [PATCH 28/47] omg lua is ruining me on the other hand, why doesn't MSVC just support "or" to mean "||"? --- plugins/dig-now.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index e3146bcc8..b634ef2e4 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -706,7 +706,7 @@ static void post_process_dug_tiles(color_ostream &out, flood_unhide(out, pos); df::tile_occupancy &to = *Maps::getTileOccupancy(pos); - if (to.bits.unit or to.bits.item) { + if (to.bits.unit || to.bits.item) { DFCoord resting_pos = simulate_fall(pos); if (resting_pos == pos) continue; From 1670cfab05f68343f8ec8e21f879d507aa55c815 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 19 Jun 2021 13:52:07 -0700 Subject: [PATCH 29/47] also "and" -> "&&" --- plugins/dig-now.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index b634ef2e4..424ffd91c 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -728,7 +728,7 @@ static void post_process_dug_tiles(color_ostream &out, if (to.bits.item) { for (auto item : world->items.other.IN_PLAY) { - if (item->pos == pos and item->flags.bits.on_ground) + if (item->pos == pos && item->flags.bits.on_ground) item->moveToGround( resting_pos.x, resting_pos.y, resting_pos.z); } From aa1cbc95dbdd0c818ebad4a831c7baf63392eb0d Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 12:15:21 -0700 Subject: [PATCH 30/47] skip marker mode dig designations --- docs/Plugins.rst | 15 ++++++++------- plugins/dig-now.cpp | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 3383e5ab7..254060efa 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2840,17 +2840,18 @@ Options: dig-now ======= -Instantly completes dig designations, modifying tile shapes and creating -boulders, ores, and gems as if a miner were doing the mining or engraving. By -default, the entire map is processed and boulder generation follows standard -game rules, but the behavior is configurable. +Instantly completes non-marker dig designations, modifying tile shapes and +creating boulders, ores, and gems as if a miner were doing the mining or +engraving. By default, the entire map is processed and boulder generation +follows standard game rules, but the behavior is configurable. Note that no units will get mining or engraving experience for the dug/engraved tiles. -Trees, and roots will be ignored. Engravings are also not automatically -generated by this plugin as they depend on the skill and creative choices of -individual engravers. +Trees and roots are not currently handled by this plugin and will be skipped. +Requests for engravings are also skipped since they would depend on the skill +and creative choices of individual engravers. Other types of engraving (i.e. +smoothing and track carving) are handled. Usage:: diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 424ffd91c..fd3646dd3 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -566,7 +566,8 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, DFCoord pos(x, y, z); df::tile_designation td = map.designationAt(pos); df::tile_occupancy to = map.occupancyAt(pos); - if (td.bits.dig != df::tile_dig_designation::No) { + if (td.bits.dig != df::tile_dig_designation::No && + !to.bits.dig_marked) { std::vector dug_tiles; if (dig_tile(out, map, pos, td.bits.dig, dug_tiles)) { td = map.designationAt(pos); From d6c9f99c61676262ec730af603f8e0d41f2071df Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 12:46:02 -0700 Subject: [PATCH 31/47] don't overflow prod->count. it's a measly int16_t --- plugins/dig-now.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index fd3646dd3..f83896849 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -656,19 +656,30 @@ static void create_boulders(color_ostream &out, prod->mat_type = 0; prod->mat_index = entry.first.second; prod->probability = 100; - prod->count = coords.size(); prod->product_dimension = 1; std::vector out_products; std::vector out_items; - prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, - job_skill::NONE, 0, civ, site, NULL); + size_t remaining_items = coords.size(); + while (remaining_items > 0) { + int16_t batch_size = min(remaining_items, + static_cast(INT16_MAX)); + prod->count = batch_size; + remaining_items -= batch_size; + prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, + 1, job_skill::NONE, 0, civ, site, NULL); + } size_t num_items = out_items.size(); if (num_items != coords.size()) { - out.printerr("unexpected number of boulders produced; " - "some boulders may be missing.\n"); + MaterialInfo material; + material.decode(prod->mat_type, prod->mat_index); + out.printerr("unexpected number of %s %s produced: expected %zd," + " got %zd.\n", + material.toString().c_str(), + ENUM_KEY_STR(item_type, prod->item_type).c_str(), + coords.size(), num_items); num_items = min(num_items, entry.second.size()); } From 1a19c3a9440574903bccad4321f2adfedb7c335a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 14:54:41 -0700 Subject: [PATCH 32/47] ramp/channel: don't dig into undiggable tiles --- plugins/dig-now.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index f83896849..e583168fc 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -334,22 +334,24 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, } break; case df::tile_dig_designation::Channel: - if (can_dig_channel(tt)) { + { + DFCoord pos_below(pos.x, pos.y, pos.z-1); + if (can_dig_channel(tt) && map.ensureBlockAt(pos_below) + && is_diggable(map, pos_below, map.tiletypeAt(pos_below))) { target_type = df::tiletype::OpenSpace; DFCoord pos_above(pos.x, pos.y, pos.z+1); if (map.ensureBlockAt(pos_above)) remove_ramp_top(map, pos_above); - DFCoord pos_below(pos.x, pos.y, pos.z-1); - if (map.ensureBlockAt(pos_below) && - dig_tile(out, map, pos_below, - df::tile_dig_designation::Ramp, dug_tiles)) { + if (dig_tile(out, map, pos_below, + df::tile_dig_designation::Ramp, dug_tiles)) { clean_ramps(map, pos_below); // if we successfully dug out the ramp below, that took care // of adding the ramp top here return true; } - break; } + break; + } case df::tile_dig_designation::UpStair: if (can_dig_up_stair(tt)) target_type = @@ -374,7 +376,8 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (can_dig_ramp(tt)) { target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); DFCoord pos_above(pos.x, pos.y, pos.z+1); - if (target_type != tt && map.ensureBlockAt(pos_above)) { + if (target_type != tt && map.ensureBlockAt(pos_above) + && is_diggable(map, pos, map.tiletypeAt(pos_above))) { // only capture the tile info of pos_above if we didn't get // here via the Channel case above if (dug_tiles.size() == 0) From fc19fb678501a4a568f9d429a93e4c8e8252b64e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 18:00:26 -0700 Subject: [PATCH 33/47] add external api: dig_now_tile(out, pos) --- plugins/dig-now.cpp | 45 +++++++++++++++++++++++++++++++-------------- plugins/dig-now.h | 9 +++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 plugins/dig-now.h diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index e583168fc..968f35815 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -2,6 +2,8 @@ * Simulates completion of dig designations. */ +#include "dig-now.h" + #include "DataFuncs.h" #include "PluginManager.h" #include "TileTypes.h" @@ -790,25 +792,16 @@ static void print_help(color_ostream &out) { } } -command_result dig_now(color_ostream &out, std::vector ¶ms) { - CoreSuspender suspend; - - dig_now_options options; - if (!get_options(out, options, params) || options.help) - { - print_help(out); - return options.help ? CR_OK : CR_FAILURE; - } - +bool dig_now_impl(color_ostream &out, const dig_now_options &options) { if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); - return CR_FAILURE; + return false; } // required for boulder generation if (world->units.active.size() == 0) { out.printerr("At least one unit must be alive!\n"); - return CR_FAILURE; + return false; } // track which positions were modified and where to produce items @@ -817,12 +810,25 @@ command_result dig_now(color_ostream &out, std::vector ¶ms) { do_dig(out, dug_coords, item_coords, options); create_boulders(out, item_coords, options); - post_process_dug_tiles (out, dug_coords); + post_process_dug_tiles(out, dug_coords); // force the game to recompute its walkability cache world->reindex_pathfinding = true; - return CR_OK; + return true; +} + +command_result dig_now(color_ostream &out, std::vector ¶ms) { + CoreSuspender suspend; + + dig_now_options options; + if (!get_options(out, options, params) || options.help) + { + print_help(out); + return options.help ? CR_OK : CR_FAILURE; + } + + return dig_now_impl(out, options) ? CR_OK : CR_FAILURE; } DFhackCExport command_result plugin_init(color_ostream &, @@ -835,3 +841,14 @@ DFhackCExport command_result plugin_init(color_ostream &, DFhackCExport command_result plugin_shutdown(color_ostream &) { return CR_OK; } + +// External API + +// runs dig-now for the specified tile. default options apply. +bool dig_now_tile (color_ostream& out, const DFCoord& pos) +{ + dig_now_options options; + options.start = pos; + options.end = pos; + return dig_now_impl(out, options); +} diff --git a/plugins/dig-now.h b/plugins/dig-now.h new file mode 100644 index 000000000..1ed9c84e4 --- /dev/null +++ b/plugins/dig-now.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ColorText.h" +#include "modules/Maps.h" + +/** + * Runs dig-now for the specified tile. Default options apply. + */ +bool dig_now_tile(DFHack::color_ostream &out, const DFHack::DFCoord &pos); From 93d67dd51e881960bdd270a86a74607c6cd7f3c1 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 19:31:05 -0700 Subject: [PATCH 34/47] export dig_now_tile over Lua --- plugins/dig-now.cpp | 28 ++++++++++++++++++++++++---- plugins/dig-now.h | 9 --------- 2 files changed, 24 insertions(+), 13 deletions(-) delete mode 100644 plugins/dig-now.h diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 968f35815..02328066d 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -842,13 +842,33 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) { return CR_OK; } -// External API +// Lua API -// runs dig-now for the specified tile. default options apply. -bool dig_now_tile (color_ostream& out, const DFCoord& pos) +// runs dig-now for the specified tile coordinate. default options apply. +static int dig_now_tile(lua_State *L) { + DFCoord pos; + if (lua_gettop(L) <= 1) + Lua::CheckDFAssign(L, &pos, 1); + else + pos = DFCoord(luaL_checkint(L, 1), luaL_checkint(L, 2), + luaL_checkint(L, 3)); + + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + + return 1; + dig_now_options options; options.start = pos; options.end = pos; - return dig_now_impl(out, options); + lua_pushboolean(L, dig_now_impl(*out, options)); + + return 1; } + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(dig_now_tile), + DFHACK_LUA_END +}; diff --git a/plugins/dig-now.h b/plugins/dig-now.h deleted file mode 100644 index 1ed9c84e4..000000000 --- a/plugins/dig-now.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "ColorText.h" -#include "modules/Maps.h" - -/** - * Runs dig-now for the specified tile. Default options apply. - */ -bool dig_now_tile(DFHack::color_ostream &out, const DFHack::DFCoord &pos); From c807398eff16534c4daddb7b0308d41462c7d788 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 19:31:40 -0700 Subject: [PATCH 35/47] remove old reference to dig-now.h --- plugins/dig-now.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 02328066d..fa5157f94 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -2,8 +2,6 @@ * Simulates completion of dig designations. */ -#include "dig-now.h" - #include "DataFuncs.h" #include "PluginManager.h" #include "TileTypes.h" From 48f90da72e72b21abe3f0eda91afd46bc2c80ac1 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 19:34:29 -0700 Subject: [PATCH 36/47] remove extra return --- plugins/dig-now.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index fa5157f94..9b5d0828a 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -856,8 +856,6 @@ static int dig_now_tile(lua_State *L) if (!out) out = &Core::getInstance().getConsole(); - return 1; - dig_now_options options; options.start = pos; options.end = pos; From 7f0c3cc7e4564da34256623bd79af5896d177d34 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 21:34:49 -0700 Subject: [PATCH 37/47] document dig-now lua api --- docs/Lua API.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index ca54f3922..a67fe9cf6 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4043,6 +4043,16 @@ Lua plugin classes - ``shuffle()``: shuffles the sequence of numbers - ``next()``: returns next number in the sequence +dig-now +======= + +The dig-now plugin exposes the following functions to Lua: + +* ``dig_now_tile(pos)`` or ``dig_now_tile(x,y,z)``: Runs dig-now for the + specified tile coordinate. Default options apply, as if you were running the + command ``dig-now ``. See the `dig-now` documentation for details + on default settings. + .. _eventful: eventful From 0b703d98189eca6bfebb451ce60a49e4640e0f14 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 27 Jun 2021 21:58:16 -0700 Subject: [PATCH 38/47] fix adjacent smooth walls when we dig one out --- plugins/dig-now.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 9b5d0828a..56fd43bb1 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -414,6 +414,8 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, } static bool is_smooth_wall(MapExtras::MapCache &map, const DFCoord &pos) { + if (!map.ensureBlockAt(pos)) + return false; df::tiletype tt = map.tiletypeAt(pos); return tileSpecial(tt) == df::tiletype_special::SMOOTH && tileShape(tt) == df::tiletype_shape::WALL; @@ -447,9 +449,10 @@ static TileDirection ensure_valid_tdir(TileDirection tdir) { } // connects adjacent smooth walls to our new smooth wall +static TileDirection BLANK_TILE_DIRECTION; static bool adjust_smooth_wall_dir(MapExtras::MapCache &map, const DFCoord &pos, - TileDirection tdir) { + TileDirection tdir = BLANK_TILE_DIRECTION) { if (!is_smooth_wall(map, pos)) return false; @@ -465,6 +468,14 @@ static bool adjust_smooth_wall_dir(MapExtras::MapCache &map, return true; } +static void refresh_adjacent_smooth_walls(MapExtras::MapCache &map, + const DFCoord &pos) { + adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y-1, pos.z)); + adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y+1, pos.z)); + adjust_smooth_wall_dir(map, DFCoord(pos.x-1, pos.y, pos.z)); + adjust_smooth_wall_dir(map, DFCoord(pos.x+1, pos.y, pos.z)); +} + // assumes that if the game let you designate a tile for smoothing, it must be // valid to do so. static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, @@ -578,6 +589,7 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, map.setDesignationAt(pos, td); for (auto info : dug_tiles) { dug_coords.push_back(info.pos); + refresh_adjacent_smooth_walls(map, info.pos); if (info.imat < 0) continue; if (produces_item(options.boulder_percents, From a6c62a025fb531afb265cd2a814c0a0de248447f Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 28 Jun 2021 13:04:58 -0700 Subject: [PATCH 39/47] unhide properly when digging at visible edges --- plugins/dig-now.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 56fd43bb1..0bfaf2f47 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -726,11 +726,32 @@ static void flood_unhide(color_ostream &out, const DFCoord &pos) { Lua::SafeCall(out, L, 1, 0); } +static bool needs_unhide(const DFCoord &pos) { + return !Maps::ensureTileBlock(pos) + || Maps::getTileDesignation(pos)->bits.hidden; +} + +static bool needs_flood_unhide(const DFCoord &pos) { + return needs_unhide(pos) + || needs_unhide(DFCoord(pos.x-1, pos.y-1, pos.z)) + || needs_unhide(DFCoord(pos.x, pos.y-1, pos.z)) + || needs_unhide(DFCoord(pos.x+1, pos.y-1, pos.z)) + || needs_unhide(DFCoord(pos.x-1, pos.y, pos.z)) + || needs_unhide(DFCoord(pos.x+1, pos.y, pos.z)) + || needs_unhide(DFCoord(pos.x-1, pos.y+1, pos.z)) + || needs_unhide(DFCoord(pos.x, pos.y+1, pos.z)) + || needs_unhide(DFCoord(pos.x+1, pos.y+1, pos.z)); +} + static void post_process_dug_tiles(color_ostream &out, const std::vector &dug_coords) { for (DFCoord pos : dug_coords) { - if (Maps::getTileDesignation(pos)->bits.hidden) + if (needs_flood_unhide(pos)) { + // set current tile to hidden to allow flood_unhide to work on tiles + // that were already visible but that reveal hidden tiles when dug. + Maps::getTileDesignation(pos)->bits.hidden = true; flood_unhide(out, pos); + } df::tile_occupancy &to = *Maps::getTileOccupancy(pos); if (to.bits.unit || to.bits.item) { From 95fa9f3d7a72fc14134db1c5658062637b73c3c5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 3 Jul 2021 06:56:43 -0700 Subject: [PATCH 40/47] use new library functions in dig-now --- plugins/lua/dig-now.lua | 46 +++++++++++++---------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/plugins/lua/dig-now.lua b/plugins/lua/dig-now.lua index e13f16aff..5bd270833 100644 --- a/plugins/lua/dig-now.lua +++ b/plugins/lua/dig-now.lua @@ -1,7 +1,8 @@ local _ENV = mkmodule('plugins.dig-now') -local utils = require('utils') +local argparse = require('argparse') local guidm = require('gui.dwarfmode') +local utils = require('utils') local short_help_text = [=[ @@ -35,42 +36,23 @@ See the online DFHack documentation for details on all options. function print_help() print(short_help_text) end local function parse_coords(opts, configname, arg) - local x, y, z - if arg == 'here' then - local cursor = guidm.getCursorPos() - if not cursor then - qerror('"here" specified for position, but no game cursor found!') - end - x, y, z = cursor.x, cursor.y, cursor.z - else - _, _, x, y, z = arg:find('^%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*$') - if not x then - qerror(('invalid position: "%s"; expected "here" or' .. - ' ",," triple (e.g. "30,60,150")'):format(arg)) - end - end - opts[configname].x = tonumber(x) - opts[configname].y = tonumber(y) - opts[configname].z = tonumber(z) -end - -local function out_of_bounds(percentage) - percentage = tonumber(percentage) - return percentage < 0 or percentage > 100 + local cursor = argparse.coords(arg, configname) + utils.assign(opts[configname], cursor) end local function parse_percentages(opts, arg) - _, _, layer, vein, small, deep = - arg:find('^%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*$') - if not layer or out_of_bounds(layer) or out_of_bounds(vein) - or out_of_bounds(small) or out_of_bounds(deep) then - qerror(('invalid percentages: "%s"; expected format is ",' .. - ',,", where each number is between'.. - ' 0 and 100, inclusive (e.g. "0,33,100,100")'):format(arg)) + local nums = argparse.numberList(arg, 'percentages', 4) + for _,percentage in ipairs(nums) do + if percentage < 0 or percentage > 100 then + qerror(('invalid percentages: "%s"; expected format is ",' .. + ',,", where each number is'.. + ' between 0 and 100, inclusive (e.g. "0,33,100,100")') + :format(arg)) + end end local config = opts.boulder_percents config.layer, config.vein, config.small_cluster, config.deep = - tonumber(layer), tonumber(vein), tonumber(small), tonumber(deep) + nums[1], nums[2], nums[3], nums[4] end local function min_to_max(...) @@ -81,7 +63,7 @@ end function parse_commandline(opts, ...) local use_zlevel = false - local positionals = utils.processArgsGetopt({...}, { + local positionals = argparse.processArgsGetopt({...}, { {'c', 'clean', handler=function() parse_percentages(opts, '0,0,0,0') end}, {'d', 'dump', hasArg=true, From c1665f35b453c67b79759f7ef576248c8519da92 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 4 Jul 2021 07:33:31 -0700 Subject: [PATCH 41/47] fix off by one error --- plugins/dig-now.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 0bfaf2f47..9d5dcc63d 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -70,7 +70,7 @@ struct dig_now_options { static DFCoord getMapSize() { uint32_t endx, endy, endz; Maps::getTileSize(endx, endy, endz); - return DFCoord(endx, endy, endz); + return DFCoord(endx - 1, endy - 1, endz - 1); } dig_now_options() : help(false), start(0, 0, 0), end(getMapSize()) { } From 8fb456313fe9ec78b36e892c21c66c6b3c508db7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 9 Jul 2021 22:16:30 -0700 Subject: [PATCH 42/47] allow only one positional param to be specified --- docs/Plugins.rst | 5 +++-- plugins/lua/dig-now.lua | 18 +++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 254060efa..eb1ca6f1b 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2855,11 +2855,12 @@ smoothing and track carving) are handled. Usage:: - dig-now [ ] [] + dig-now [ []] [] Where the optional ```` pair can be used to specify the coordinate bounds within which ``dig-now`` will operate. If they are not specified, ``dig-now`` -will scan the entire map. +will scan the entire map. If only one ```` is specified, only the tile at +that coordinate is processed. Any ```` parameters can either be an ``,,`` triple (e.g. ``35,12,150``) or the string ``here``, which means the position of the active diff --git a/plugins/lua/dig-now.lua b/plugins/lua/dig-now.lua index 5bd270833..2d7ae40d7 100644 --- a/plugins/lua/dig-now.lua +++ b/plugins/lua/dig-now.lua @@ -16,12 +16,12 @@ standard game rules, but the behavior is configurable. Usage: - dig-now [ ] [] + dig-now [ []] [] Examples: dig-now - Dig designated tiles according to standard game rules. + Dig all designated tiles according to standard game rules. dig-now --clean Dig designated tiles, but don't generate any boulders, ores, or gems. @@ -85,12 +85,16 @@ function parse_commandline(opts, ...) df.global.window_z parse_coords(opts, 'start', ('0,0,%d'):format(z)) parse_coords(opts, 'end', ('%d,%d,%d'):format(x, y, z)) - elseif #positionals >= 2 then + elseif #positionals >= 1 then parse_coords(opts, 'start', positionals[1]) - parse_coords(opts, 'end', positionals[2]) - opts.start.x, opts['end'].x = min_to_max(opts.start.x, opts['end'].x) - opts.start.y, opts['end'].y = min_to_max(opts.start.y, opts['end'].y) - opts.start.z, opts['end'].z = min_to_max(opts.start.z, opts['end'].z) + if #positionals >= 2 then + parse_coords(opts, 'end', positionals[2]) + opts.start.x, opts['end'].x = min_to_max(opts.start.x,opts['end'].x) + opts.start.y, opts['end'].y = min_to_max(opts.start.y,opts['end'].y) + opts.start.z, opts['end'].z = min_to_max(opts.start.z,opts['end'].z) + else + utils.assign(opts['end'], opts.start) + end end end From 79bdf36ed41150343c2feb2cb4919bc3ca395af4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 10 Jul 2021 14:51:21 -0700 Subject: [PATCH 43/47] expose link_adjacent_smooth_walls to lua so build-now can use it --- plugins/dig-now.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 9d5dcc63d..c4ed9acbd 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -897,7 +897,24 @@ static int dig_now_tile(lua_State *L) return 1; } +static int link_adjacent_smooth_walls(lua_State *L) +{ + DFCoord pos; + if (lua_gettop(L) <= 1) + Lua::CheckDFAssign(L, &pos, 1); + else + pos = DFCoord(luaL_checkint(L, 1), luaL_checkint(L, 2), + luaL_checkint(L, 3)); + + MapExtras::MapCache map; + adjust_smooth_wall_dir(map, pos); + refresh_adjacent_smooth_walls(map, pos); + map.WriteAll(); + return 0; +} + DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(dig_now_tile), + DFHACK_LUA_COMMAND(link_adjacent_smooth_walls), DFHACK_LUA_END }; From 3c8a174a68dfae223c68bd0843db0a8544bef585 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 17 Jul 2021 11:45:16 -0700 Subject: [PATCH 44/47] use pillars for wall ends instead of NS or EW --- plugins/dig-now.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index c4ed9acbd..678490b45 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -436,15 +436,18 @@ static TileDirection get_adjacent_smooth_walls(MapExtras::MapCache &map, return tdir; } -// ensure we have at least two directions enabled so we can find a matching -// tiletype +// ensure we have at least two directions enabled (or 0) so we can find a +// matching tiletype. The game chooses to curve "end piece" walls into +// orthogonally adjacent hidden tiles, or uses a pillar if there are no such +// tiles. we take the easier, but not quite conformant, path here and always use +// a pillar for end pieces. If we want to become faithful to how the game does +// it, this code should be moved to the post-processing phase after hidden tiles +// have been revealed. We would also have to scan for wall ends that are no +// longer adjacent to hidden tiles and convert them to pillars when we dig two +// tiles away from such a wall end and reveal their adjacent hidden tile. static TileDirection ensure_valid_tdir(TileDirection tdir) { - if (tdir.sum() < 2) { - if (tdir.north) tdir.south = 1; - else if (tdir.south) tdir.north = 1; - else if (tdir.east) tdir.west = 1; - else if (tdir.west) tdir.east = 1; - } + if (tdir.sum() < 2) + tdir.whole = 0; return tdir; } From 92701d6da7830a4904f861bd69206cdc33bde25e Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 20 Jul 2021 15:53:03 -0700 Subject: [PATCH 45/47] connect smooth walls to doors --- plugins/dig-now.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 678490b45..2f5b154f6 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -7,6 +7,7 @@ #include "TileTypes.h" #include "LuaTools.h" +#include "modules/Buildings.h" #include "modules/Gui.h" #include "modules/Maps.h" #include "modules/MapCache.h" @@ -421,17 +422,26 @@ static bool is_smooth_wall(MapExtras::MapCache &map, const DFCoord &pos) { && tileShape(tt) == df::tiletype_shape::WALL; } -// adds adjacent smooth walls to the given tdir +static bool is_smooth_wall_or_door(MapExtras::MapCache &map, + const DFCoord &pos) { + if (is_smooth_wall(map, pos)) + return true; + + df::building *bld = Buildings::findAtTile(pos); + return bld && bld->getType() == df::building_type::Door; +} + +// adds adjacent smooth walls and doors to the given tdir static TileDirection get_adjacent_smooth_walls(MapExtras::MapCache &map, const DFCoord &pos, TileDirection tdir) { - if (is_smooth_wall(map, DFCoord(pos.x, pos.y-1, pos.z))) + if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y-1, pos.z))) tdir.north = 1; - if (is_smooth_wall(map, DFCoord(pos.x, pos.y+1, pos.z))) + if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y+1, pos.z))) tdir.south = 1; - if (is_smooth_wall(map, DFCoord(pos.x-1, pos.y, pos.z))) + if (is_smooth_wall_or_door(map, DFCoord(pos.x-1, pos.y, pos.z))) tdir.west = 1; - if (is_smooth_wall(map, DFCoord(pos.x+1, pos.y, pos.z))) + if (is_smooth_wall_or_door(map, DFCoord(pos.x+1, pos.y, pos.z))) tdir.east = 1; return tdir; } From 98fd6effdc5595eb3ad7bcebcf84d393ceb24e71 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 1 Aug 2021 07:02:00 -0700 Subject: [PATCH 46/47] generate random time variants where applicable --- plugins/dig-now.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 2f5b154f6..1c0e849d9 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -193,9 +193,13 @@ static void dig_type(MapExtras::MapCache &map, const DFCoord &pos, map.setTiletypeAt(pos, tt); } +static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { + return findRandomVariant(findSimilarTileType(tt, shape)); +} + static void dig_shape(MapExtras::MapCache &map, const DFCoord &pos, df::tiletype tt, df::tiletype_shape shape) { - dig_type(map, pos, findSimilarTileType(tt, shape)); + dig_type(map, pos, get_target_type(tt, shape)); } static void remove_ramp_top(MapExtras::MapCache &map, const DFCoord &pos) { @@ -331,7 +335,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, target_shape = df::tiletype_shape::STAIR_DOWN; else if (shape == df::tiletype_shape::RAMP) remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+1)); - target_type = findSimilarTileType(tt, target_shape); + target_type = get_target_type(tt, target_shape); } break; case df::tile_dig_designation::Channel: @@ -355,27 +359,25 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, } case df::tile_dig_designation::UpStair: if (can_dig_up_stair(tt)) - target_type = - findSimilarTileType(tt, df::tiletype_shape::STAIR_UP); + target_type = get_target_type(tt, df::tiletype_shape::STAIR_UP); break; case df::tile_dig_designation::DownStair: if (can_dig_down_stair(tt)) { target_type = - findSimilarTileType(tt, df::tiletype_shape::STAIR_DOWN); + get_target_type(tt, df::tiletype_shape::STAIR_DOWN); } break; case df::tile_dig_designation::UpDownStair: if (can_dig_up_down_stair(tt)) { target_type = - findSimilarTileType(tt, - df::tiletype_shape::STAIR_UPDOWN); + get_target_type(tt, df::tiletype_shape::STAIR_UPDOWN); } break; case df::tile_dig_designation::Ramp: { if (can_dig_ramp(tt)) { - target_type = findSimilarTileType(tt, df::tiletype_shape::RAMP); + target_type = get_target_type(tt, df::tiletype_shape::RAMP); DFCoord pos_above(pos.x, pos.y, pos.z+1); if (target_type != tt && map.ensureBlockAt(pos_above) && is_diggable(map, pos, map.tiletypeAt(pos_above))) { @@ -388,7 +390,7 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, // because we need to use *this* tile's material, not the // material of the tile above map.setTiletypeAt(pos_above, - findSimilarTileType(tt, df::tiletype_shape::RAMP_TOP)); + get_target_type(tt, df::tiletype_shape::RAMP_TOP)); remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+2)); } } From d8b5ac41d9b2b5e99bc2e1458b69f962b2704620 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 1 Aug 2021 23:29:32 -0700 Subject: [PATCH 47/47] ensure soil tiles get randomized variants --- plugins/dig-now.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 1c0e849d9..0abf935a4 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -178,23 +178,21 @@ static void dig_type(MapExtras::MapCache &map, const DFCoord &pos, if (!blk) return; - // ensure we run this even if one of the later steps fails (e.g. OpenSpace) map.setTiletypeAt(pos, tt); - // digging a tile reverts it to the layer soil/stone material - if (!blk->setStoneAt(pos, tt, map.layerMaterialAt(pos)) && - !blk->setSoilAt(pos, tt, map.layerMaterialAt(pos))) - return; + // digging a tile should revert it to the layer soil/stone material + if (!blk->setStoneAt(pos, tt, map.layerMaterialAt(pos))) + blk->setSoilAt(pos, tt, map.layerMaterialAt(pos)); +} + +static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { + tt = findSimilarTileType(tt, shape); // un-smooth dug tiles - tt = map.tiletypeAt(pos); tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), df::tiletype_special::NORMAL, tileDirection(tt)); - map.setTiletypeAt(pos, tt); -} -static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { - return findRandomVariant(findSimilarTileType(tt, shape)); + return findRandomVariant(tt); } static void dig_shape(MapExtras::MapCache &map, const DFCoord &pos,