Merge remote-tracking branch 'myk002/myk_dig_dug' into develop

develop
lethosor 2021-08-03 19:53:01 -04:00
commit 79a22e25eb
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
9 changed files with 1214 additions and 11 deletions

@ -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 <pos> <pos>``. See the `dig-now` documentation for details
on default settings.
.. _eventful:
eventful

@ -2835,6 +2835,70 @@ 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
=======
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 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::
dig-now [<pos> [<pos>]] [<options>]
Where the optional ``<pos>`` 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. If only one ``<pos>`` is specified, only the tile at
that coordinate is processed.
Any ``<pos>`` parameters can either be an ``<x>,<y>,<z>`` 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 <pos>``:
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 <layer>,<vein>,<small cluster>,<deep>``:
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

@ -33,9 +33,15 @@ 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
## Documentation
- `quickfort-library-guide`: updated dreamfort documentation and added screenshots
# 0.47.05-r2
## Fixes

@ -25,20 +25,98 @@ 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
<https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP>`__.
The Quick Fortresses
~~~~~~~~~~~~~~~~~~~~
The online spreadsheets also include `embark profile suggestions
<https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=149144025>`__
and a complete `example embark profile
<https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=1727884387>`__.
Visual overview
```````````````
Here are some annotated screenshots of the major levels (or click `here
<https://drive.google.com/drive/folders/14KdE2E2wQKj4F_E-NAe3G3E4x1wiWtrc>`__
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
:align: center
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
\\\\\\\\\\\\\\
.. 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
: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
\\\\\\\\\\\\\\\
.. 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
\\\\\\\\\\\\
.. 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
\\\\\\\\\\
.. 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
~~~~~~~~~~~~~~~~~~
The Quick Fortress is an updated version of the example fortress that came with
`Python Quickfort 2.0 <https://github.com/joelpt/quickfort>`__ (the program

@ -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);

@ -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)
{

@ -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-now dig-now.cpp LINK_LIBRARIES lua)
dfhack_plugin(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders)
dfhack_plugin(dwarfvet dwarfvet.cpp)

@ -0,0 +1,933 @@
/*
* Simulates completion of dig designations.
*/
#include "DataFuncs.h"
#include "PluginManager.h"
#include "TileTypes.h"
#include "LuaTools.h"
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/Random.h"
#include "modules/Units.h"
#include "modules/World.h"
#include <df/historical_entity.h>
#include <df/map_block.h>
#include <df/reaction_product_itemst.h>
#include <df/tile_designation.h>
#include <df/tile_occupancy.h>
#include <df/ui.h>
#include <df/unit.h>
#include <df/vermin.h>
#include <df/world.h>
#include <df/world_site.h>
DFHACK_PLUGIN("dig-now");
REQUIRE_GLOBAL(ui);
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<uint32_t>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "vein", offsetof(boulder_percent_options, vein), &df::identity_traits<uint32_t>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "small_cluster", offsetof(boulder_percent_options, small_cluster), &df::identity_traits<uint32_t>::identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "deep", offsetof(boulder_percent_options, deep), &df::identity_traits<uint32_t>::identity, 0, 0 },
{ struct_field_info::END }
};
struct_identity boulder_percent_options::_identity(sizeof(boulder_percent_options), &df::allocator_fn<boulder_percent_options>, 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
boulder_percent_options boulder_percents;
// if set to the pos of a walkable tile (or somewhere above such a tile),
// will dump generated boulders at this position instead of at their dig
// locations
DFCoord dump_pos;
static DFCoord getMapSize() {
uint32_t endx, endy, endz;
Maps::getTileSize(endx, endy, endz);
return DFCoord(endx - 1, endy - 1, endz - 1);
}
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<bool>::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<dig_now_options>, 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,
const DFCoord &pos) {
df::tile_designation td = map.designationAt(pos);
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 = 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)
&& 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;
map.setDesignationAt(pos_below, td_below);
shape = tileShape(map.tiletypeAt(pos_below));
}
}
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 dig_type(MapExtras::MapCache &map, const DFCoord &pos,
df::tiletype tt) {
auto blk = map.BlockAtTile(pos);
if (!blk)
return;
map.setTiletypeAt(pos, tt);
// 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 = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt),
df::tiletype_special::NORMAL, tileDirection(tt));
return findRandomVariant(tt);
}
static void dig_shape(MapExtras::MapCache &map, const DFCoord &pos,
df::tiletype tt, df::tiletype_shape shape) {
dig_type(map, pos, get_target_type(tt, shape));
}
static void remove_ramp_top(MapExtras::MapCache &map, const DFCoord &pos) {
if (!map.ensureBlockAt(pos))
return;
if (tileShape(map.tiletypeAt(pos)) == df::tiletype_shape::RAMP_TOP)
dig_type(map, pos, df::tiletype::OpenSpace);
}
static bool is_wall(MapExtras::MapCache &map, const DFCoord &pos) {
if (!map.ensureBlockAt(pos))
return false;
return tileShape(map.tiletypeAt(pos)) == df::tiletype_shape::WALL;
}
static void clean_ramp(MapExtras::MapCache &map, const DFCoord &pos) {
if (!map.ensureBlockAt(pos))
return;
df::tiletype tt = map.tiletypeAt(pos);
if (tileShape(tt) != df::tiletype_shape::RAMP)
return;
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(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(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));
}
// 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;
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 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:
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,
const DFCoord &pos, df::tile_dig_designation designation,
std::vector<dug_tile_info> &dug_tiles) {
df::tiletype tt = map.tiletypeAt(pos);
if (!is_diggable(map, pos, tt))
return false;
df::tiletype target_type = df::tiletype::Void;
switch(designation) {
case df::tile_dig_designation::Default:
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(map, DFCoord(pos.x, pos.y, pos.z+1));
target_type = get_target_type(tt, target_shape);
}
break;
case df::tile_dig_designation::Channel:
{
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);
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;
}
case df::tile_dig_designation::UpStair:
if (can_dig_up_stair(tt))
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 =
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 =
get_target_type(tt, df::tiletype_shape::STAIR_UPDOWN);
}
break;
case df::tile_dig_designation::Ramp:
{
if (can_dig_ramp(tt)) {
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))) {
// 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));
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
map.setTiletypeAt(pos_above,
get_target_type(tt, df::tiletype_shape::RAMP_TOP));
remove_ramp_top(map, DFCoord(pos.x, pos.y, pos.z+2));
}
}
break;
}
case df::tile_dig_designation::No:
default:
out.printerr(
"unhandled dig designation for tile (%d, %d, %d): %d\n",
pos.x, pos.y, pos.z, designation);
}
// fail if unhandled or no change to tile
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
propagate_vertical_flags(map, pos);
return true;
}
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;
}
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_or_door(map, DFCoord(pos.x, pos.y-1, pos.z)))
tdir.north = 1;
if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y+1, pos.z)))
tdir.south = 1;
if (is_smooth_wall_or_door(map, DFCoord(pos.x-1, pos.y, pos.z)))
tdir.west = 1;
if (is_smooth_wall_or_door(map, DFCoord(pos.x+1, pos.y, pos.z)))
tdir.east = 1;
return tdir;
}
// 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)
tdir.whole = 0;
return 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 = BLANK_TILE_DIRECTION) {
if (!is_smooth_wall(map, pos))
return false;
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)
return false;
map.setTiletypeAt(pos, tt);
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,
const DFCoord &pos) {
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(0, 1, 0, 0)))
tdir.north = 1;
if (adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y+1, pos.z),
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, 0, 1)))
tdir.west = 1;
if (adjust_smooth_wall_dir(map, DFCoord(pos.x+1, pos.y, pos.z),
TileDirection(0, 0, 1, 0)))
tdir.east = 1;
tdir = ensure_valid_tdir(tdir);
}
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);
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_item(const boulder_percent_options &options,
MapExtras::MapCache &map, Random::MersenneRNG &rng,
const dug_tile_info &info) {
uint32_t probability;
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::pair<df::item_type, int32_t>, std::vector<DFCoord>>
item_coords_t;
static void do_dig(color_ostream &out, std::vector<DFCoord> &dug_coords,
item_coords_t &item_coords, const dig_now_options &options) {
MapExtras::MapCache map;
Random::MersenneRNG rng;
rng.init();
// 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))
continue;
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 &&
!to.bits.dig_marked) {
std::vector<dug_tile_info> 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);
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,
map, rng, info)) {
auto k = std::make_pair(info.itype, info.imat);
item_coords[k].push_back(info.pos);
}
}
}
} else if (td.bits.smooth == 1) {
if (smooth_tile(out, map, pos)) {
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) {
if (carve_tile(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();
}
// 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);
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)
break;
--resting_pos.z;
}
return resting_pos;
}
static void create_boulders(color_ostream &out,
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);
df::world_site *site = World::isFortressMode() ?
df::world_site::find(ui->site_id) : NULL;
std::vector<df::reaction_reagent *> in_reag;
std::vector<df::item *> in_items;
DFCoord dump_pos;
if (Maps::isValidTilePos(options.dump_pos)) {
dump_pos = simulate_fall(options.dump_pos);
if (!Maps::ensureTileBlock(dump_pos))
out.printerr("Invalid dump tile coordinates! Ensure the --dump"
" option specifies an open, non-wall tile.");
}
for (auto entry : item_coords) {
df::reaction_product_itemst *prod =
df::allocate<df::reaction_product_itemst>();
const std::vector<DFCoord> &coords = entry.second;
prod->item_type = entry.first.first;
prod->item_subtype = -1;
prod->mat_type = 0;
prod->mat_index = entry.first.second;
prod->probability = 100;
prod->product_dimension = 1;
std::vector<df::reaction_product*> out_products;
std::vector<df::item *> out_items;
size_t remaining_items = coords.size();
while (remaining_items > 0) {
int16_t batch_size = min(remaining_items,
static_cast<size_t>(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()) {
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());
}
for (size_t i = 0; i < num_items; ++i) {
DFCoord pos = Maps::isValidTilePos(dump_pos) ?
dump_pos : simulate_fall(coords[i]);
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);
continue;
}
out_items[i]->moveToGround(pos.x, pos.y, pos.z);
}
delete(prod);
}
}
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);
}
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<DFCoord> &dug_coords) {
for (DFCoord pos : dug_coords) {
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) {
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<df::unit*> 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 && item->flags.bits.on_ground)
item->moveToGround(
resting_pos.x, resting_pos.y, resting_pos.z);
}
}
}
// refresh block metadata and flows
Maps::enableBlockUpdates(Maps::getTileBlock(pos), true, true);
}
}
static bool get_options(color_ostream &out,
dig_now_options &opts,
const std::vector<std::string> &parameters) {
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 &param : 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");
}
}
bool dig_now_impl(color_ostream &out, const dig_now_options &options) {
if (!Maps::IsValid()) {
out.printerr("Map is not available!\n");
return false;
}
// required for boulder generation
if (world->units.active.size() == 0) {
out.printerr("At least one unit must be alive!\n");
return false;
}
// track which positions were modified and where to produce items
std::vector<DFCoord> dug_coords;
item_coords_t item_coords;
do_dig(out, dug_coords, item_coords, options);
create_boulders(out, item_coords, options);
post_process_dug_tiles(out, dug_coords);
// force the game to recompute its walkability cache
world->reindex_pathfinding = true;
return true;
}
command_result dig_now(color_ostream &out, std::vector<std::string> &params) {
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 &,
std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand(
"dig-now", "Instantly complete dig designations", dig_now, false));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &) {
return CR_OK;
}
// Lua API
// 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();
dig_now_options options;
options.start = pos;
options.end = pos;
lua_pushboolean(L, dig_now_impl(*out, options));
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
};

@ -0,0 +1,101 @@
local _ENV = mkmodule('plugins.dig-now')
local argparse = require('argparse')
local guidm = require('gui.dwarfmode')
local utils = require('utils')
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 [<pos> [<pos>]] [<options>]
Examples:
dig-now
Dig all 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 cursor = argparse.coords(arg, configname)
utils.assign(opts[configname], cursor)
end
local function parse_percentages(opts, 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 "<layer>,' ..
'<vein>,<small cluster>,<deep>", 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 =
nums[1], nums[2], nums[3], nums[4]
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 = argparse.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 >= 1 then
parse_coords(opts, 'start', positionals[1])
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
return _ENV