From 996b41310d8d582b94a2ef5b4c7dbeca609a4c26 Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 22 May 2026 10:33:26 -0700 Subject: [PATCH 1/7] dig-now removing dependency on obsolete MapCache module. this is intended to be a naive direct-replacement with minimal changes to algorithms. this commit is the easy stuff: remove MapExtras::MapCache &map parameter from functions change DFCoord to df::coord change MapCache::ensureBlockAt to Maps::ensureTileBlock change MapCache::BlockAtTile to Maps::getTileBlock change MapCache::tiletypeAt to *Maps::getTileType change MapCache::getDesignationAt to *Maps::getTileDesignation change MapCache::setDesignationAt to *Maps::getTileDesignation change MapCache::occupancyAt to *Maps::getTileOccupancy flag MapCache::layerMaterialAt for further work flag MapCache::baseMaterialAt for further work flag Block::veinTypeAt for further work flag Block::setStoneAt for further work flag Block::setSoilAt for further work flag DesignationJobs for further work add several now-necessary headers. --- plugins/dig-now.cpp | 365 ++++++++++++++++++++++---------------------- 1 file changed, 182 insertions(+), 183 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 259978f9bff..d539340f989 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -14,25 +14,32 @@ #include "modules/Gui.h" #include "modules/Job.h" #include "modules/Maps.h" -#include "modules/MapCache.h" +//-#include "modules/MapCache.h" #include "modules/Random.h" #include "modules/Units.h" #include "modules/World.h" +#include +#include #include "df/building.h" #include "df/builtin_mats.h" #include "df/historical_entity.h" +#include "df/inorganic_raw.h" //? #include "df/item.h" #include "df/map_block.h" #include "df/material.h" #include "df/plotinfost.h" #include "df/reaction_product_itemst.h" +#include "df/region_map_entry.h" //? #include "df/tile_designation.h" #include "df/tile_occupancy.h" #include "df/unit.h" #include "df/vermin.h" #include "df/world.h" +#include "df/world_data.h" //? +#include "df/world_geo_biome.h" //? #include "df/world_site.h" +#include "df/world_region_details.h" //? #include #include @@ -48,6 +55,7 @@ namespace DFHack { DBG_DECLARE(dignow, channels, DebugCategory::LINFO); } +using std::vector; using namespace DFHack; struct designation{ @@ -76,12 +84,12 @@ namespace std { }; } -class DesignationJobs { +class TODODesignationJobs { private: std::unordered_map designations; std::unordered_map jobs; public: - void load(MapExtras::MapCache &map) { + void load() { designations.clear(); DEBUG(general).print("DesignationJobs: reading jobs list\n"); df::job_list_link* node = df::global::world->jobs.list.next; @@ -92,8 +100,8 @@ class DesignationJobs { if(!job || !Maps::isValidTilePos(job->pos)) continue; - df::tile_designation td = map.designationAt(job->pos); - df::tile_occupancy to = map.occupancyAt(job->pos); + df::tile_designation td = *Maps::getTileDesignation(job->pos); + df::tile_occupancy to = *Maps::getTileOccupancy(job->pos); const auto ctd = td.whole; const auto cto = to.whole; switch (job->job_type){ @@ -179,20 +187,20 @@ struct_identity boulder_percent_options::_identity(sizeof(boulder_percent_option 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 + df::coord start; // upper-left coordinate, min z-level + df::coord 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; + df::coord dump_pos; - static DFCoord getMapSize() { + static df::coord getMapSize() { uint32_t endx, endy, endz; Maps::getTileSize(endx, endy, endz); - return DFCoord(endx - 1, endy - 1, endz - 1); + return df::coord(endx - 1, endy - 1, endz - 1); } dig_now_options() : help(false), start(0, 0, 0), end(getMapSize()) { } @@ -210,11 +218,10 @@ static const struct_field_info dig_now_options_fields[] = { 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, - const DFCoord &pos) { - df::tile_designation td = map.designationAt(pos); +static void propagate_vertical_flags(const df::coord &pos) { + df::tile_designation td = *Maps::getTileDesignation(pos); - if (!map.ensureBlockAt(DFCoord(pos.x, pos.y, pos.z+1))) { + if (!Maps::ensureTileBlock(df::coord(pos.x, pos.y, pos.z+1))) { // only the sky above td.bits.light = true; td.bits.outside = true; @@ -223,12 +230,12 @@ static void propagate_vertical_flags(MapExtras::MapCache &map, int32_t zlevel = pos.z; df::tiletype_shape shape = - tileShape(map.tiletypeAt(DFCoord(pos.x, pos.y, zlevel))); + tileShape(*Maps::getTileType(df::coord(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); + && Maps::ensureTileBlock(df::coord(pos.x, pos.y, --zlevel))) { + df::coord pos_below(pos.x, pos.y, zlevel); + df::tile_designation td_below = *Maps::getTileDesignation(pos_below); if (td_below.bits.light == td.bits.light && td_below.bits.outside == td.bits.outside && td_below.bits.subterranean == td.bits.subterranean) @@ -236,8 +243,8 @@ static void propagate_vertical_flags(MapExtras::MapCache &map, 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)); + *Maps::getTileDesignation(pos_below) = td_below; + shape = tileShape(*Maps::getTileType(pos_below)); } } @@ -293,17 +300,20 @@ static bool can_dig_ramp(df::tiletype tt) { shape == df::tiletype_shape::FORTIFICATION; } -static void dig_type(MapExtras::MapCache &map, const DFCoord &pos, + + + +static void dig_type(const df::coord pos, df::tiletype tt) { - auto blk = map.BlockAtTile(pos); + auto blk = Maps::getTileBlock(pos); if (!blk) return; - map.setTiletypeAt(pos, tt); + *Maps::getTileType(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)); + if (!blk->TODOsetStoneAt(pos, tt, TODOmap.layerMaterialAt(pos))) + blk->TODOsetSoilAt(pos, tt, TODOmap.layerMaterialAt(pos)); } static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { @@ -316,63 +326,63 @@ static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { return findRandomVariant(tt); } -static void dig_shape(MapExtras::MapCache &map, const DFCoord &pos, +static void dig_shape(const df::coord pos, df::tiletype tt, df::tiletype_shape shape) { - dig_type(map, pos, get_target_type(tt, shape)); + dig_type(pos, get_target_type(tt, shape)); } -static void remove_ramp_top(MapExtras::MapCache &map, const DFCoord &pos) { - if (!map.ensureBlockAt(pos)) +static void remove_ramp_top(const df::coord pos) { + if (!Maps::ensureTileBlock(pos)) return; - if (tileShape(map.tiletypeAt(pos)) == df::tiletype_shape::RAMP_TOP) - dig_type(map, pos, df::tiletype::OpenSpace); + if (tileShape(*Maps::getTileType(pos)) == df::tiletype_shape::RAMP_TOP) + dig_type(pos, df::tiletype::OpenSpace); } -static bool is_wall(MapExtras::MapCache &map, const DFCoord &pos) { - if (!map.ensureBlockAt(pos)) +static bool is_wall(const df::coord pos) { + if (!Maps::ensureTileBlock(pos)) return false; - return tileShape(map.tiletypeAt(pos)) == df::tiletype_shape::WALL; + return tileShape(*Maps::getTileType(pos)) == df::tiletype_shape::WALL; } -static void clean_ramp(MapExtras::MapCache &map, const DFCoord &pos) { - if (!map.ensureBlockAt(pos)) +static void clean_ramp(const df::coord pos) { + if (!Maps::ensureTileBlock(pos)) return; - df::tiletype tt = map.tiletypeAt(pos); + df::tiletype tt = *Maps::getTileType(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)) || - is_wall(map, DFCoord(pos.x-1, pos.y-1, pos.z)) || - is_wall(map, DFCoord(pos.x-1, pos.y+1, pos.z)) || - is_wall(map, DFCoord(pos.x+1, pos.y-1, pos.z)) || - is_wall(map, DFCoord(pos.x+1, pos.y+1, pos.z))) + if ( is_wall(df::coord(pos.x-1, pos.y, pos.z)) || + is_wall(df::coord(pos.x+1, pos.y, pos.z)) || + is_wall(df::coord(pos.x, pos.y-1, pos.z)) || + is_wall(df::coord(pos.x, pos.y+1, pos.z)) || + is_wall(df::coord(pos.x-1, pos.y-1, pos.z)) || + is_wall(df::coord(pos.x-1, pos.y+1, pos.z)) || + is_wall(df::coord(pos.x+1, pos.y-1, pos.z)) || + is_wall(df::coord(pos.x+1, 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); + remove_ramp_top(df::coord(pos.x, pos.y, pos.z+1)); + dig_shape(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)); - clean_ramp(map, DFCoord(pos.x-1, pos.y-1, pos.z)); - clean_ramp(map, DFCoord(pos.x-1, pos.y+1, pos.z)); - clean_ramp(map, DFCoord(pos.x+1, pos.y-1, pos.z)); - clean_ramp(map, DFCoord(pos.x+1, pos.y+1, pos.z)); +static void clean_ramps(const df::coord pos) { + clean_ramp(pos); + clean_ramp(df::coord(pos.x-1, pos.y, pos.z)); + clean_ramp(df::coord(pos.x+1, pos.y, pos.z)); + clean_ramp(df::coord(pos.x, pos.y-1, pos.z)); + clean_ramp(df::coord(pos.x, pos.y+1, pos.z)); + clean_ramp(df::coord(pos.x-1, pos.y-1, pos.z)); + clean_ramp(df::coord(pos.x-1, pos.y+1, pos.z)); + clean_ramp(df::coord(pos.x+1, pos.y-1, pos.z)); + clean_ramp(df::coord(pos.x+1, pos.y+1, pos.z)); } // destroys any colonies located at pos -static void destroy_colony(const DFCoord &pos) { +static void destroy_colony(const df::coord pos) { auto same_pos = [&](df::vermin *colony){ return colony->pos == pos; }; auto &colonies = world->event.vermin_colonies; @@ -387,15 +397,15 @@ static void destroy_colony(const DFCoord &pos) { } struct dug_tile_info { - DFCoord pos; + df::coord pos; df::tiletype_material tmat; df::item_type itype; t_matpair imat; // matpair of boulder/gem potentially generated at this pos - dug_tile_info(MapExtras::MapCache &map, const DFCoord &pos) { + dug_tile_info(const df::coord pos) { this->pos = pos; - df::tiletype tt = map.tiletypeAt(pos); + df::tiletype tt = *Maps::getTileType(pos); tmat = tileMaterial(tt); itype = df::item_type::BOULDER; @@ -410,7 +420,7 @@ struct dug_tile_info { case df::tiletype_material::MINERAL: case df::tiletype_material::FEATURE: case df::tiletype_material::LAVA_STONE: - imat = map.baseMaterialAt(pos); + imat = TODOmap.baseMaterialAt(pos); break; case df::tiletype_material::FROZEN_LIQUID: // assume frozen water @@ -432,7 +442,7 @@ struct dug_tile_info { } }; -static bool is_diggable(MapExtras::MapCache &map, const DFCoord &pos, +static bool is_diggable(const df::coord pos, df::tiletype tt) { df::tiletype_material mat = tileMaterial(tt); switch (mat) { @@ -448,19 +458,19 @@ static bool is_diggable(MapExtras::MapCache &map, const DFCoord &pos, } MaterialInfo mi; - mi.decode(map.baseMaterialAt(pos)); + mi.decode(TODOmap.baseMaterialAt(pos)); if (mi.material != nullptr && mi.material->flags.is_set(df::material_flags::UNDIGGABLE)) return false; return true; } -static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, - const DFCoord &pos, df::tile_dig_designation designation, +static bool dig_tile(color_ostream &out, + const df::coord pos, df::tile_dig_designation designation, std::vector &dug_tiles) { - df::tiletype tt = map.tiletypeAt(pos); + df::tiletype tt = *Maps::getTileType(pos); - if (!is_diggable(map, pos, tt)) { + if (!is_diggable(pos, tt)) { DEBUG(general).print("dig_tile: not diggable\n"); return false; } @@ -474,29 +484,29 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, 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)); + remove_ramp_top(df::coord(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))) { + df::coord pos_below(pos.x, pos.y, pos.z-1); + if (can_dig_channel(tt) && Maps::ensureTileBlock(pos_below) + && is_diggable(pos_below, *Maps::getTileType(pos_below))) { TRACE(channels).print("dig_tile: channeling at ({}) [can_dig_channel: true]\n", 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); + df::coord pos_above(pos.x, pos.y, pos.z+1); + if (Maps::ensureTileBlock(pos_above)) { + remove_ramp_top(pos_above); } - df::tile_dig_designation td_below = map.designationAt(pos_below).bits.dig; - if (dig_tile(out, map, pos_below, df::tile_dig_designation::Ramp, dug_tiles)) { - clean_ramps(map, pos_below); + df::tile_dig_designation td_below = (*Maps::getTileDesignation(pos_below)).bits.dig; + if (dig_tile(out, pos_below, df::tile_dig_designation::Ramp, dug_tiles)) { + clean_ramps(pos_below); if (td_below == df::tile_dig_designation::Default) { - dig_tile(out, map, pos_below, td_below, dug_tiles); + dig_tile(out, pos_below, td_below, dug_tiles); } - clean_ramps(map, pos); - propagate_vertical_flags(map, pos); + clean_ramps(pos); + propagate_vertical_flags(pos); return true; } } else { @@ -525,21 +535,21 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, { 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))) { + df::coord pos_above(pos.x, pos.y, pos.z+1); + if (target_type != tt && Maps::ensureTileBlock(pos_above) + && is_diggable(pos, *Maps::getTileType(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)); + dug_tiles.push_back(dug_tile_info(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)); - propagate_vertical_flags(map, DFCoord(pos.x, pos.y, pos.z + 1)); + *Maps::getTileType(pos_above) = + get_target_type(tt, df::tiletype_shape::RAMP_TOP); + remove_ramp_top(df::coord(pos.x, pos.y, pos.z+2)); + propagate_vertical_flags(df::coord(pos.x, pos.y, pos.z + 1)); } } break; @@ -555,23 +565,23 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (target_type == df::tiletype::Void || target_type == tt) return false; - dug_tiles.emplace_back(map, pos); + dug_tiles.emplace_back(pos); TRACE(general).print("dig_tile: digging the designation tile at ({})\n",pos); - dig_type(map, pos, target_type); + dig_type(pos, target_type); - clean_ramps(map, pos); + clean_ramps(pos); return true; } -static bool is_smooth_wall(MapExtras::MapCache &map, const DFCoord &pos) { - if (!map.ensureBlockAt(pos)) +static bool is_smooth_wall(const df::coord pos) { + if (!Maps::ensureTileBlock(pos)) return false; - df::tiletype tt = map.tiletypeAt(pos); + df::tiletype tt = *Maps::getTileType(pos); return tileSpecial(tt) == df::tiletype_special::SMOOTH && tileShape(tt) == df::tiletype_shape::WALL; } -static bool is_connector(MapExtras::MapCache &map, const DFCoord &pos) { +static bool is_connector(const df::coord pos) { df::building *bld = Buildings::findAtTile(pos); return bld && @@ -579,22 +589,20 @@ static bool is_connector(MapExtras::MapCache &map, const DFCoord &pos) { bld->getType() == df::building_type::Floodgate); } -static bool is_smooth_wall_or_connector(MapExtras::MapCache &map, - const DFCoord &pos) { - return is_smooth_wall(map, pos) || is_connector(map, pos); +static bool is_smooth_wall_or_connector(const df::coord pos) { + return is_smooth_wall(pos) || is_connector(pos); } // adds adjacent smooth walls and doors to the given tdir -static TileDirection get_adjacent_smooth_walls(MapExtras::MapCache &map, - const DFCoord &pos, +static TileDirection get_adjacent_smooth_walls(const df::coord pos, TileDirection tdir) { - if (is_smooth_wall_or_connector(map, DFCoord(pos.x, pos.y-1, pos.z))) + if (is_smooth_wall_or_connector(df::coord(pos.x, pos.y-1, pos.z))) tdir.north = 1; - if (is_smooth_wall_or_connector(map, DFCoord(pos.x, pos.y+1, pos.z))) + if (is_smooth_wall_or_connector(df::coord(pos.x, pos.y+1, pos.z))) tdir.south = 1; - if (is_smooth_wall_or_connector(map, DFCoord(pos.x-1, pos.y, pos.z))) + if (is_smooth_wall_or_connector(df::coord(pos.x-1, pos.y, pos.z))) tdir.west = 1; - if (is_smooth_wall_or_connector(map, DFCoord(pos.x+1, pos.y, pos.z))) + if (is_smooth_wall_or_connector(df::coord(pos.x+1, pos.y, pos.z))) tdir.east = 1; return tdir; } @@ -616,44 +624,42 @@ 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, +static bool adjust_smooth_wall_dir(const df::coord pos, TileDirection tdir = BLANK_TILE_DIRECTION) { - if (!is_smooth_wall(map, pos)) - return is_connector(map, pos); + if (!is_smooth_wall(pos)) + return is_connector(pos); - tdir = ensure_valid_tdir(get_adjacent_smooth_walls(map, pos, tdir)); + tdir = ensure_valid_tdir(get_adjacent_smooth_walls(pos, tdir)); - df::tiletype tt = map.tiletypeAt(pos); + df::tiletype tt = *Maps::getTileType(pos); tt = findTileType(tileShape(tt), tileMaterial(tt), tileVariant(tt), tileSpecial(tt), tdir); if (tt == df::tiletype::Void) return false; - map.setTiletypeAt(pos, tt); + *Maps::getTileType(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)); +static void refresh_adjacent_smooth_walls(const df::coord pos) { + adjust_smooth_wall_dir(df::coord(pos.x, pos.y-1, pos.z)); + adjust_smooth_wall_dir(df::coord(pos.x, pos.y+1, pos.z)); + adjust_smooth_wall_dir(df::coord(pos.x-1, pos.y, pos.z)); + adjust_smooth_wall_dir(df::coord(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); +static bool smooth_tile(color_ostream &out, + const df::coord pos) { + df::tiletype tt = *Maps::getTileType(pos); df::tiletype_shape shape = tileShape(tt); df::tiletype_variant variant = tileVariant(tt); df::tiletype_special special = df::tiletype_special::SMOOTH; TileDirection tdir; - if (is_smooth_wall(map, pos)) { + if (is_smooth_wall(pos)) { // engraving is filtered out at a higher level, so this is a // fortification designation shape = tiletype_shape::FORTIFICATION; @@ -661,16 +667,16 @@ static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, special = df::tiletype_special::NONE; } else if (shape == df::tiletype_shape::WALL) { - if (adjust_smooth_wall_dir(map, DFCoord(pos.x, pos.y-1, pos.z), + if (adjust_smooth_wall_dir(df::coord(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), + if (adjust_smooth_wall_dir(df::coord(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), + if (adjust_smooth_wall_dir(df::coord(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), + if (adjust_smooth_wall_dir(df::coord(pos.x+1, pos.y, pos.z), TileDirection(0, 0, 1, 0))) tdir.east = 1; tdir = ensure_valid_tdir(tdir); @@ -680,15 +686,14 @@ static bool smooth_tile(color_ostream &out, MapExtras::MapCache &map, if (tt == df::tiletype::Void) return false; - map.setTiletypeAt(pos, tt); + *Maps::getTileType(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); +static bool carve_tile(const df::coord pos, df::tile_occupancy &to) { + df::tiletype tt = *Maps::getTileType(pos); TileDirection tdir = tileDirection(tt); if (to.bits.carve_track_north) @@ -705,18 +710,18 @@ static bool carve_tile(MapExtras::MapCache &map, if (tt == df::tiletype::Void) return false; - map.setTiletypeAt(pos, tt); + *Maps::getTileType(pos) = tt; return true; } static bool produces_item(const boulder_percent_options &options, - MapExtras::MapCache &map, Random::MersenneRNG &rng, + 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)) { + switch (TODOmap.BlockAtTile(info.pos)->TODOveinTypeAt(info.pos)) { case df::inclusion_type::CLUSTER: case df::inclusion_type::VEIN: probability = options.vein; @@ -734,17 +739,16 @@ static bool produces_item(const boulder_percent_options &options, return rng.random(100) < probability; } -typedef std::map, std::vector> +typedef std::map, std::vector> item_coords_t; -static void do_dig(color_ostream &out, std::vector &dug_coords, +static void do_dig(color_ostream &out, std::vector &dug_coords, item_coords_t &item_coords, const dig_now_options &options) { - MapExtras::MapCache map; Random::MersenneRNG rng; - DesignationJobs jobs; + TODODesignationJobs jobs; DEBUG(general).print("do_dig(): starting..\n"); - jobs.load(map); + jobs.load(); rng.init(); DEBUG(general).print("do_dig(): reading map..\n"); @@ -758,9 +762,9 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, 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); + df::coord pos(x, y, z); + df::tile_designation td = *Maps::getTileDesignation(pos); + df::tile_occupancy to = *Maps::getTileOccupancy(pos); if (jobs.count(pos)) { buffer.emplace(jobs.get(pos)); jobs.remove(pos); @@ -789,53 +793,50 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, 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)) { + if (dig_tile(out, pos, td.bits.dig, dug_tiles)) { for (auto info: dug_tiles) { - td = map.designationAt(info.pos); + td = *Maps::getTileDesignation(info.pos); td.bits.dig = df::tile_dig_designation::No; - map.setDesignationAt(info.pos, td); + *Maps::getTileDesignation(info.pos) = td; dug_coords.push_back(info.pos); - refresh_adjacent_smooth_walls(map, info.pos); + refresh_adjacent_smooth_walls(info.pos); if (info.imat < 0) continue; - if (produces_item(options.boulder_percents, - map, rng, info)) { + if (produces_item(options.boulder_percents, 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)) { - td = map.designationAt(pos); + if (smooth_tile(out, pos)) { + td = *Maps::getTileDesignation(pos); td.bits.smooth = 0; - map.setDesignationAt(pos, td); + *Maps::getTileDesignation(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); + if (carve_tile(pos, to)) { + to = *Maps::getTileOccupancy(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); + *Maps::getTileOccupancy(pos) = to; } } } - - DEBUG(general).print("do_dig(): write changes to map..\n"); - 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); +static df::coord simulate_fall(const df::coord pos) { + df::coord resting_pos(pos); while (Maps::ensureTileBlock(resting_pos)) { df::tiletype tt = *Maps::getTileType(resting_pos); @@ -858,7 +859,7 @@ static void create_boulders(color_ostream &out, std::vector in_reag; std::vector in_items; - DFCoord dump_pos; + df::coord dump_pos; if (Maps::isValidTilePos(options.dump_pos)) { dump_pos = simulate_fall(options.dump_pos); if (!Maps::ensureTileBlock(dump_pos)) @@ -869,7 +870,7 @@ static void create_boulders(color_ostream &out, for (auto entry : item_coords) { df::reaction_product_itemst *prod = df::allocate(); - const std::vector &coords = entry.second; + const std::vector &coords = entry.second; prod->item_type = entry.first.first; prod->item_subtype = -1; @@ -902,7 +903,7 @@ static void create_boulders(color_ostream &out, } for (size_t i = 0; i < num_items; ++i) { - DFCoord pos = Maps::isValidTilePos(dump_pos) ? + df::coord pos = Maps::isValidTilePos(dump_pos) ? dump_pos : simulate_fall(coords[i]); if (!Maps::ensureTileBlock(pos)) { out.printerr( @@ -917,26 +918,26 @@ static void create_boulders(color_ostream &out, } } -static bool needs_unhide(const DFCoord &pos) { +static bool needs_unhide(const df::coord pos) { return !Maps::ensureTileBlock(pos) || Maps::getTileDesignation(pos)->bits.hidden; } -static bool needs_flood_unhide(const DFCoord &pos) { +static bool needs_flood_unhide(const df::coord 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)); + || needs_unhide(df::coord(pos.x-1, pos.y-1, pos.z)) + || needs_unhide(df::coord(pos.x, pos.y-1, pos.z)) + || needs_unhide(df::coord(pos.x+1, pos.y-1, pos.z)) + || needs_unhide(df::coord(pos.x-1, pos.y, pos.z)) + || needs_unhide(df::coord(pos.x+1, pos.y, pos.z)) + || needs_unhide(df::coord(pos.x-1, pos.y+1, pos.z)) + || needs_unhide(df::coord(pos.x, pos.y+1, pos.z)) + || needs_unhide(df::coord(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) { + const std::vector &dug_coords) { + for (df::coord 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. @@ -946,7 +947,7 @@ static void post_process_dug_tiles(color_ostream &out, df::tile_occupancy &to = *Maps::getTileOccupancy(pos); if (to.bits.unit || to.bits.item) { - DFCoord resting_pos = simulate_fall(pos); + df::coord resting_pos = simulate_fall(pos); if (resting_pos == pos) continue; @@ -1023,7 +1024,7 @@ bool dig_now_impl(color_ostream &out, const dig_now_options &options) { } // track which positions were modified and where to produce items - std::vector dug_coords; + std::vector dug_coords; item_coords_t item_coords; do_dig(out, dug_coords, item_coords, options); @@ -1063,11 +1064,11 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) { // runs dig-now for the specified tile coordinate. default options apply. static int dig_now_tile(lua_State *L) { - DFCoord pos; + df::coord pos; if (lua_gettop(L) <= 1) Lua::CheckDFAssign(L, &pos, 1); else - pos = DFCoord(luaL_checkint(L, 1), luaL_checkint(L, 2), + pos = df::coord(luaL_checkint(L, 1), luaL_checkint(L, 2), luaL_checkint(L, 3)); color_ostream *out = Lua::GetOutput(L); @@ -1084,17 +1085,15 @@ static int dig_now_tile(lua_State *L) static int link_adjacent_smooth_walls(lua_State *L) { - DFCoord pos; + df::coord pos; if (lua_gettop(L) <= 1) Lua::CheckDFAssign(L, &pos, 1); else - pos = DFCoord(luaL_checkint(L, 1), luaL_checkint(L, 2), + pos = df::coord(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(); + adjust_smooth_wall_dir(pos); + refresh_adjacent_smooth_walls(pos); return 0; } From eeb8a0f3949c7e08d577a6221caf61e48b8780df Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 22 May 2026 16:55:52 -0700 Subject: [PATCH 2/7] dig-now replace MapCache mineral handling. this is a naive-but-correct implementation with no caching. --- plugins/dig-now.cpp | 69 ++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index d539340f989..3a0ddb5ed71 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -20,6 +20,7 @@ #include "modules/World.h" #include +#include #include #include "df/building.h" #include "df/builtin_mats.h" @@ -303,8 +304,7 @@ static bool can_dig_ramp(df::tiletype tt) { -static void dig_type(const df::coord pos, - df::tiletype tt) { +static void dig_type(const df::coord pos, df::tiletype tt) { auto blk = Maps::getTileBlock(pos); if (!blk) return; @@ -442,8 +442,7 @@ struct dug_tile_info { } }; -static bool is_diggable(const df::coord pos, - df::tiletype tt) { +static bool is_diggable(const df::coord pos, df::tiletype tt) { df::tiletype_material mat = tileMaterial(tt); switch (mat) { case df::tiletype_material::CONSTRUCTION: @@ -519,17 +518,12 @@ static bool dig_tile(color_ostream &out, 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); - - } + 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); - } + 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: { @@ -721,18 +715,42 @@ static bool produces_item(const boulder_percent_options &options, if (info.tmat == df::tiletype_material::FEATURE) probability = options.deep; else { - switch (TODOmap.BlockAtTile(info.pos)->TODOveinTypeAt(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; + std::vector veins; + Maps::SortBlockEvents(Maps::getTileBlock(info.pos), &veins); + + // the logic in MapCache WriteVeins() and in tile-material.lua GetVeinMat() + // suggests that veins are not strictly sorted by inclusion type. + // the needed sort order is CLUSTER, VEIN, CLUSTER_SMALL, CLUSTER_ONE. + // accordingly, we can't use the inclusion_type enum; it has VEIN < CLUSTER. + // we can use the df::mineral_event_flag::Shift::shift_* enum. + uint32_t priority = 0; + probability = options.layer; + for (auto vein : veins) { + uint32_t veinpriority = -1; + uint32_t veinprobability; + + if (vein->flags.bits.cluster_one) { + veinpriority = df::mineral_event_flag::Shift::shift_cluster_one; + veinprobability = options.small_cluster; + } + else if (vein->flags.bits.cluster_small) { + veinpriority = df::mineral_event_flag::Shift::shift_cluster_small; + veinprobability = options.small_cluster; + } + else if (vein->flags.bits.vein) { + veinpriority = df::mineral_event_flag::Shift::shift_vein; + veinprobability = options.vein; + } + else if (vein->flags.bits.cluster) { + veinpriority = df::mineral_event_flag::Shift::shift_cluster; + veinprobability = options.vein; + } + + if (veinpriority >= priority + && vein->getassignment(info.pos.x & 15, info.pos.y & 15)) { + priority = veinpriority; + probability = veinprobability; + } } } @@ -804,7 +822,6 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, if (info.imat < 0) continue; if (produces_item(options.boulder_percents, rng, info)) { - auto k = std::make_pair(info.itype, info.imat); item_coords[k].push_back(info.pos); } From c9c8c004516116d87126b6b20962cc8ac53c780a Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 22 May 2026 22:35:42 -0700 Subject: [PATCH 3/7] dig-now deal with changing dug-out tiles to base stone. --- 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 3a0ddb5ed71..82a52c3a85c 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -301,19 +301,22 @@ static bool can_dig_ramp(df::tiletype tt) { shape == df::tiletype_shape::FORTIFICATION; } - - - static void dig_type(const df::coord pos, df::tiletype tt) { auto blk = Maps::getTileBlock(pos); if (!blk) return; - *Maps::getTileType(pos) = tt; - // digging a tile should revert it to the layer soil/stone material - if (!blk->TODOsetStoneAt(pos, tt, TODOmap.layerMaterialAt(pos))) - blk->TODOsetSoilAt(pos, tt, TODOmap.layerMaterialAt(pos)); + auto des = Maps::getTileDesignation(pos); + tt = matchTileMaterial(tt, (des->bits.geolayer_index <= 3) ? + df::enums::tiletype_material::SOIL : df::enums::tiletype_material::STONE); + *Maps::getTileType(pos) = tt; + for (auto event : blk->block_events) { + if (event->getType() == df::block_square_event_type::mineral) { + auto vein = (df::block_square_event_mineralst *)event; + vein->setassignment(pos, 0); + } + } } static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { @@ -746,8 +749,7 @@ static bool produces_item(const boulder_percent_options &options, veinprobability = options.vein; } - if (veinpriority >= priority - && vein->getassignment(info.pos.x & 15, info.pos.y & 15)) { + if (veinpriority >= priority && vein->getassignment(info.pos)) { priority = veinpriority; probability = veinprobability; } From 9065d360dc161eedfbc7f964341ce1f43574a3bf Mon Sep 17 00:00:00 2001 From: SilasD Date: Thu, 18 Jun 2026 09:03:45 -0700 Subject: [PATCH 4/7] dig-now. code compiles. hasn't been checked in for far too long. cherry-pick from branch dig-now-test --- compare_map.lua | 730 ++++++++++++++++++++++++++++++++++++++++++++ plugins/dig-now.cpp | 527 ++++++++++++++++++++++++++------ 2 files changed, 1160 insertions(+), 97 deletions(-) create mode 100644 compare_map.lua diff --git a/compare_map.lua b/compare_map.lua new file mode 100644 index 00000000000..e7d7aa8044d --- /dev/null +++ b/compare_map.lua @@ -0,0 +1,730 @@ +--[====[ +This developer script was created to compare the effects of the old +and new versions of the `dig-now` plugin during development. + +It works by recording interesting map blocks from the current map +when first run, then comparing those map blocks with the current +map on subsequent runs. To clear the recorded blocks, run the +script from the main menu (i.e. with no map loaded). + +Example usage: + * Load a test map that has dig-designations. + * Run the old version of the plugin: e.g. `dig-now-old`. + * Run this script to record the map state. + * Quit without saving back to the main menu. + * Reload the same test map. + * Run the new version of the plugin: e.g. `dig-now`. + * Run this script again to compare the map state. + * Analyze the results. + * Quit without saving back to the main menu. + * Run the script a third time to clear the results. + + +It will need to be modified to work with anything else. + +code possibly of interest: + ipairsN iterator, works with the `for` keyword. + each_tile_in_block iterator, works with the `for` keyword. + garbage collection of df types created by Lua: + df.map_block and df.block_square_event +]====] + +---@type df.map_block[] +record = record or setmetatable( {}, { __gc = GC_map_block_table, } ) -- global, persistant + +-------------------------------------------------------------------------------- +local addressof = require('utils').addressof + +-------------------------------------------------------------------------------- +---@param obj DFBase +---@return string +local function addressofs(obj) + assert(df.isvalid(obj)) + return(string.format("%016X", addressof(obj))) +end + +-------------------------------------------------------------------------------- +---@param s string +---@param i integer +---@return string +local function pad(s,i) + return s .. string.rep(' ', i - #s) +end + +-------------------------------------------------------------------------------- +---@param fmt string +---@param ... any +local function printf(fmt, ...) + print(string.format(fmt, ...)) +end + +-------------------------------------------------------------------------------- +--- `ipairsN()` is `ipairs()` that accepts one or more Lua list +--- or DF vector or array and iterates over them in lockstep. +--- * the lists must be of equal size. +--- * the lists must not have `nil` as the last element. +--- * really, the lists should not contain `nil` at all, +--- because such tables are not lists. +--- `Lua 5.3 Reference Manual §3.4.7` +-- +-- usage example: +-- ``` +-- t1 = { 1, 2, 3, 4, } +-- t2 = { 'a', 'b', 'c', 'd', } +-- t3 = { df.job_type[1], df.job_type[2], df.job_type[3], df.job_type[4], } +-- t4 = { df.item_barst, df.item_blocksst, df.item_barrelst, df.item_binst, } +-- +-- for i, a, b, c, d in ipairsN(t1, t2, t3, t4) do +-- print(i, a, b, c, d) +-- end +-- ``` +-- warning! currently this returns a 1-based index even for Df vectors and arrays. +-- you must subtract 1 to index the proper element of a DF type. +-- +---@generic T +---@param ... T[] DF vectors or arrays, or Lua lists. +---@return fun(nil, integer?):integer?, T?... iterator +---@return nil state +---@return integer start # always 0 +local function ipairsN(...) +-- implementation difference: this returns iterator, nil, nil +-- unlike ipairs() which returns iterator, table, 0. +-- (this is because 0 is a valid index for DF vectors.) +-- the second nil is the state. +-- this implementation captures state as upvalues, so the +-- state passed between `for` and the iterator is not used. + local is_container = require("utils").is_container + local count = select('#', ...) + assert(count >= 1) + local arrays = {...} + local length = #arrays[1] + for i = 1, count do + local array = arrays[i] + assert(type(array) == "table" or is_container(array), "parameter " .. i .. " is not an array") + assert(#arrays[i] == length, "array " .. i .. " is not the same size as array 1") + end + + ---@generic T + ---@param state nil + ---@param index integer? + ---@return integer? next-index + ---@return T?... + -- note: uses upvalues is_container, count, arrays, length + -- note: debugging uses upvalues array and debugging_verify_arraysize for asserts. + -- because of these upvalue captures, this returns a new closure every time. + -- this could be avoided by nesting these and Array inside a state table. + -- DONE: given that I'm capturing upvalues anyway, why pass state in and out? + -- just pass in and out a nil state, and use the captures. + local function iterator(state, index) + assert(state == nil) + assert((math.type(index) == "integer" and index >= 0 and index <= length), "index was bad or out-of-range: " .. tostring(index)) + for i = 1, count do + local array = arrays[i] + assert(#arrays[i] == length, "iterator: array " .. i .. " has changed length") + end + + index = index + 1 + if index > length then + return nil, table.unpack({}, 1, count) -- return all nils + end + + local list = {} + for i = 1, count do + local array = arrays[i] + local idx = is_container(array) and index-1 or index + list[i] = array[idx] + end + + return index, table.unpack(list, 1, count) -- this construct preserves trailing nils. + end + + return iterator, nil, 0 +end + +-------------------------------------------------------------------------------- +---@class (exact) block_key +---@field value unknown -- intended to be opaque. + +-------------------------------------------------------------------------------- +---@param block df.map_block +---@return block_key +local function block_key(block) + assert(df.isvalid(block)) + assert(df.map_block:is_instance(block)) + ---@diagnostic disable-next-line: return-type-mismatch + return string.format( "(%d,%d,%d)", pos2xyz(block.map_pos) ) +end + +-------------------------------------------------------------------------------- +---@param block df.map_block +function GC_map_block(block) + assert(df.isvalid(block)) + assert(df.map_block:is_instance(block)) + block.pool_id = -1 + assert(block.block_burrows.item == nil) + assert(block.block_burrows.prev == nil) + assert(block.block_burrows.next == nil) + -- ignore block.items; it's a int32_t vector so it's safe to destruct. + assert(#block.flows == 0) + block.flow_pool.reuse_idx = -1 + for i = #block.block_events-1,0,-1 do + assert(df.block_square_event:is_instance(block.block_events[i])) + block.block_events[i]:delete() + block.block_events:erase(i) + end + assert(#block.block_events == 0) + block:delete() +end + +-------------------------------------------------------------------------------- +---@param t table | df.map_block[] +function GC_map_block_table(t) + for k, block in pairs(t) do + if df.map_block:is_instance(block) then + GC_map_block(block) + end + t[k] = nil + end +end + +-------------------------------------------------------------------------------- +---@param block df.map_block +---@return df.map_block +local function copy_map_block(block) + assert(df.isvalid(block)) + assert(df.map_block:is_instance(block)) + local inb = block + local outb = inb:new() + outb.block_burrows.item = nil + outb.block_burrows.prev = nil + outb.block_burrows.next = nil + outb.block_events:resize(0) + outb.items:resize(0) + outb.flows:resize(0) + outb.flow_pool.reuse_idx = -1 + outb.flow_pool.flags.active = false + outb.pool_id = -1 + for i, inbse in ipairs(inb.block_events) do + assert(df.isvalid(inbse)) + assert(df.block_square_event:is_instance(inbse)) + assert(inbse._type ~= df.block_square_event) + local outbse = inbse:new() + assert(df.isvalid(outbse)) + assert(df.block_square_event:is_instance(outbse)) + assert(outbse._type ~= df.block_square_event) + outb.block_events:insert('#', outbse) + end + return outb +end + +if false then -- test code + collectgarbage("collect") + ---@type df.map_block? + local bc = copy_map_block(dfhack.maps.getTileBlock(16,16,135)) + printall(bc) + GC_map_block(bc) + bc = nil + collectgarbage("collect") + return +end +if false then -- test code + collectgarbage("collect") + for z = 0, xyz2pos(dfhack.maps.getSize()).z - 1 do + table.insert(record, copy_map_block(dfhack.maps.getTileBlock(16,16,z))) + local block = copy_map_block(dfhack.maps.getTileBlock(32,32,z)) + record[block_key(block)] = block + end + record = nil + collectgarbage("collect") + return +end + +-------------------------------------------------------------------------------- +---@alias coord (df.coord | { x:integer, y:integer, z:integer }) -- a true df.coord or a 'pos' table. + +-------------------------------------------------------------------------------- +---@type fun(x:integer, y:integer, z:integer):block:df.map_block?, lx:integer, ly:integer +---@overload fun(x:integer, y:integer, z:integer):block:df.map_block?, lx:integer, ly:integer -- this seems to be necessary, but I don't know why. +---@overload fun(_nil:nil):_nil:nil, lx:integer, ly:integer +---@overload fun(pos:table):block:df.map_block?, lx:integer, ly:integer +---@overload fun(pos:df.coord):block:df.map_block?, lx:integer, ly:integer +---@overload fun(c:df.construction):block:df.map_block, lx:integer, ly:integer +---@overload fun(b:df.map_block):block:df.map_block?, lx:integer, ly:integer +local function get_block_and_local(x, y, z) + if df.isvalid(x) then + ---@cast x -nil + if x._type == df.coord then + return dfhack.maps.getTileBlock(x), x.x & 15, x.y & 15 + elseif x._type == df.construction then + return dfhack.maps.getTileBlock(x.pos), x.pos.x & 15, x.pos.y & 15 + elseif x._type == df.map_block then + return x, 0, 0 + end + qerror("wasn't able to interpret parameter: " .. x._type) + elseif type(x) == "nil" then + return nil, 0, 0 + elseif math.type(x) == "integer" then + return dfhack.maps.getTileBlock(x, y, z), x & 15, y & 15 + elseif type(x) == "table" then + return dfhack.maps.getTileBlock(x), x.x & 15, x.y & 15 + end + qerror("wasn't able to interpret parameter: " .. type(x)) +end + +-------------------------------------------------------------------------------- +--- this is an iterator function. it helps the `for` keyword process a map block. +--- for each of the 256 tiles in the block, the for loop gets these variables: +--- the map block, local_x, local_y, the tile's tiletype, the tile's designation, +--- and the tile's occupancy. +--- example code: +--- ``` +--- for block, lx, ly, tt, des, occ in each_tile_in_block(12, 34, 56) do +--- print(lx, ly) +--- end +--- ``` +--- note: if the block is nil then this loops 0 times, so there is no need to check. +---@param block_or_pos_or_x df.map_block|coord|integer|nil +---@param y integer? +---@param z integer? +---@return (fun(block:df.map_block):block:df.map_block, local_x:integer, local_y:integer, tiletype:df.tiletype, designation:df.tile_designation, occupancy:df.tile_occupancy), df.map_block? +local function each_tile_in_block(block_or_pos_or_x, y, z) + + --- this nested function is the workhorse for `each_tile_in_block`. + --- it `yield`s with these parameters: `df.map_block block`, `integer local_x`, + --- `integer local_y`, `df.tiletype tiletype`, + --- `df.tile_designation designation`, `df.tile_occupancy occupancy`. + --- when it is done, it returns nil, which ends the for loop. + ---@param Block df.map_block + ---@return nil + local function block_iterator(Block) + -- note: because this function doesn't share any of `each_tile_in_block`s + -- parameters or locals, this function is not a closure. + assert(df.map_block:is_instance(Block)) + for local_x = 0,15 do + local tiletype_sheaf, designation_sheaf, occupancy_sheaf = + Block.tiletype[local_x], Block.designation[local_x], Block.occupancy[local_x] + for local_y = 0,15 do + coroutine.yield(Block, local_x, local_y, tiletype_sheaf[local_y], + designation_sheaf[local_y], occupancy_sheaf[local_y]) + end + end + return nil + end + + ---@type df.map_block? + local block = get_block_and_local(block_or_pos_or_x, y, z) + if block == nil then + -- if there is no block, use this null iterator instead. + -- this tells `for` to loop 0 times. + return(function() return nil; end), nil + end + + -- `coroutine.wrap` creates a wrapper of the iterator function. + -- the `for` keyword will invoke the wrapper with parameter `block`. + -- the wrapper will call `block_iterator` with parameter `block`. + -- `block_iterator` will call `yield` once for each tile, with relevant data. + -- the wrapper will pass this relevant data to the `for` keyword. + -- when all 256 tiles have been processed via `yield`, `block_iterator` + -- will return nil. + -- the wrapper will return this nil to the `for` keyword. + -- this terminates the for loop. + -- + -- special note! these `yield`s are not the same as the `yield`s that + -- return control to Dwarf Fortress. + return coroutine.wrap(block_iterator), block +end + +-------------------------------------------------------------------------------- +--- no longer used +---@param block df.map_block +---@param lx integer +---@param ly integer +---@param tt df.tiletype +---@param des df.tile_designation +---@param occ df.tile_occupancy +---@return boolean +local function interesting_map_block_tile(block, lx, ly, tt, des, occ) + local attrs = df.tiletype.attrs[tt] + -- ignore tiles flagged as caverns, magma, hell, adamantine pipes. + if des.feature_global or des.feature_local then return false; end + -- tiles that are normal wall aren't interesting. + if ( (attrs.shape == df.tiletype_shape.WALL and attrs.special == df.tiletype_special.NORMAL) + or (attrs.shape == df.tiletype_shape.WALL and attrs.special == df.tiletype_special.NONE) + ) then return false; end + -- tiles with oddball materials aren't interesting. + if ( attrs.material >= df.tiletype_material.FROZEN_LIQUID and attrs.material ~= df.tiletype_material.CONSTRUCTION + ) then return false; end + -- lit tiles that are normal floor, pebbles, boulders, ramps, or air aren't interesting. + if des.light and ( + (attrs.shape == df.tiletype_shape.FLOOR and attrs.special == df.tiletype_special.NORMAL) + or attrs.shape == df.tiletype_shape.PEBBLES + or attrs.shape == df.tiletype_shape.BOULDER + or attrs.shape == df.tiletype_shape.RAMP + or attrs.material == df.tiletype_material.AIR + ) then return false; end + return true +end + +-------------------------------------------------------------------------------- +--- no longer used. +---@param block df.map_block +---@return boolean +local function interesting_map_block(block) + for block, lx, ly, tt, des, occ in each_tile_in_block(block) do + local attrs = df.tiletype.attrs[tt] + if interesting_map_block_tile(block, lx, ly, tt, des, occ) then + -- give a reason why this map block is interesting. + print(block_key(block), pad(df.tiletype[tt],23), pad(df.tiletype_shape[attrs.shape],15), + pad(df.tiletype_material[attrs.material],15), df.tiletype_special[attrs.special]) + return true + end + end + return false +end + +-------------------------------------------------------------------------------- +--- for speed, this tests if a specific field in the first object is byte-for-byte +--- identical to the same field in the second object. +---@param obj1 DFStruct +---@param obj2 DFStruct +---@param field string +---@param size integer +local function identical_field(obj1, obj2, field, size) + --assert(type(field) == "string") + --assert(math.type(size) == "integer" and size > 0) + --assert(df.isvalid(obj1) and df.isvalid(obj2) and obj1._type == obj2._type) + -----@diagnostic disable-next-line: undefined-field + --assert(obj1._kind == "struct" and obj1._type._fields[field] ~= nil) + ---@diagnostic disable-next-line: undefined-field + local offset = obj1._type._fields[field].offset + local a1, a2 = addressof(obj1) + offset, addressof(obj2) + offset + return dfhack.internal.memcmp(a1, a2, size) == 0 +end + +-------------------------------------------------------------------------------- +---@param b1 df.map_block +---@param b2 df.map_block +local function co_compare_map_block(b1, b2) + local yield = coroutine.yield + if block_key(b1) ~= block_key(b2) then + yield("ERROR", "", "different blocks", block_key(b1), block_key(b2)) + return nil + end + + -- structured types with named fields, e.g. bitfields. + if b1.flags.whole ~= b2.flags.whole then + for k in pairs(b1.flags) do + if k == "update_temperature" then + -- do nothing + elseif b1.flags[k] ~= b2.flags[k] then + yield("flags", "", "." .. k, b1.flags[k], b2.flags[k]) + end + end + end + + -- df.tiletype[16][16] + if not identical_field(b1, b2, "tiletype", df.tiletype:sizeof() * 16 * 16) then + for x, ttx1, ttx2 in ipairsN(b1.tiletype, b2.tiletype) do + for y, tt1, tt2 in ipairsN(ttx1, ttx2) do + local a1, a2 = df.tiletype.attrs[tt1], df.tiletype.attrs[tt2] + -- special case, digging operations create random variant floor tiles. + if a1.variant ~= df.tiletype_variant.NONE and a2.variant ~= df.tiletype_variant.NONE then + -- findTileType() and findSimilarTileType are not exported to Lua. + tt1, tt2 = df.tiletype[tt1], df.tiletype[tt2] -- string name + --tt1, tt2 = tt1:gsub('^(.*)[1234]$', "%11"), tt2:gsub('^(.*)[1234]$', "%11") + tt1, tt2 = tt1:sub(-#tt1, -2) .. '1', tt2:sub(-#tt2, -2) .. '1' + tt1, tt2 = df.tiletype[tt1], df.tiletype[tt2] -- back to integer + end + if tt1 ~= tt2 then + -- TODO need to deal with tiletypes that have variants. + yield(x-1, y-1, "tiletype", df.tiletype[tt1], df.tiletype[tt2]) + end + end + end + end + + -- df.tile_designation[16][16] + if not identical_field(b1, b2, "designation", df.tile_designation:sizeof() * 16 * 16) then + for x, v1y, v2y in ipairsN(b1.designation, b2.designation) do + for y, v1, v2 in ipairsN(v1y, v2y) do + if v1.whole ~= v2.whole then + for field in pairs(v1) do + if v1[field] ~= v2[field] then + yield(x-1, y-1, "designation" .. '.' .. field, v1[field], v2[field]) + end end end end end end + + -- df.tile_occupancy[16][16] + if not identical_field(b1, b2, "occupancy", df.tile_occupancy:sizeof() * 16 * 16) then + for x, v1y, v2y in ipairsN(b1.occupancy, b2.occupancy) do + for y, v1, v2 in ipairsN(v1y, v2y) do + if v1.whole ~= v2.whole then + for field in pairs(v1) do + if v1[field] ~= v2[field] + and field ~= "item" and field ~= "unit" and field ~= "unit_grounded" + then + yield(x-1, y-1, "occupancy" .. '.' .. field, v1[field], v2[field]) + end end end end end end + + -- ignore fog_of_war, path_cost, path_tag, walkable, map_edge_distance, temperature_1, temperature_2, lighting. + + -- don't ignore liquid_flow[16][16] ? + if not identical_field(b1, b2, "liquid_flow", df.tile_liquid_flow:sizeof() * 16 * 16) then + for x, v1y, v2y in ipairsN(b1.liquid_flow, b2.liquid_flow) do + for y, v1, v2 in ipairsN(v1y, v2y) do + if v1.whole ~= v2.whole then + for field in pairs(v1) do + if v1[field] ~= v2[field] then + yield(x-1, y-1, "liquid_flow" .. '.' .. field, v1[field], v2[field]) + end end end end end end + + -- int8_t[9] + for i, k1, k2 in ipairsN(b1.region_offset, b2.region_offset) do + if k1 ~= k2 then + yield("index "..i-1, '', "region_offset", k1, k2) + end + end + + -- walk both block_events vectors. lockstep? but need to watch for missing events. + local bev1, bev2 = b1.block_events, b2.block_events + local i1, i2 = 0, 0 + while (i1 <= #bev1-1 and i2 <= #bev2-1) do + local be1, be2 = bev1[i1], bev2[i2] + local be1s, be2s = "event " .. i1, "event " .. i2 + assert(df.block_square_event:is_instance(be1)) + assert(df.block_square_event:is_instance(be2)) + if be1._type == be2._type then + if be1._type == df.block_square_event_mineralst then + if be1.inorganic_mat ~= be2.inorganic_mat then + yield(be1s, be2s, tostring(be1._type) .. " different inorganic_mat", + be1.inorganic_mat, be2.inorganic_mat) + end + for x = 0, 15 do + for y = 0, 15 do + local xy = x..','..y..':' + local bm1, bm2 = dfhack.maps.getTileAssignment(be1.tile_bitmask, x, y), + dfhack.maps.getTileAssignment(be2.tile_bitmask, x, y) + if bm1 ~= bm2 then + yield(be1s, be2s, tostring(be1._type) .. " bitmap different", + xy..tostring(bm1), xy..tostring(bm2)) + end + end + end + if be1.flags.whole ~= be2.flags.whole then + yield(be1s, be2s, tostring(be1._type) .. " different flags", + be1.flags.whole, be2.flags.whole) + end + elseif false and be1._type == df.block_square_event_frozen_liquidst then + -- TODO this is potentially important, check on a frozen map. + elseif be1._type == df.block_square_event_grassst then + + elseif false and be1._type == df.block_square_event_designation_priorityst then + for x = 0, 15 do + for y = 0, 15 do + local xy = x..','..y + if be1.priority[x][y] ~= be2.priority[x][y] then + yield(be1s, be2s, tostring(be1._type) .. ' ' .. xy, + be1.priority[x][y], be2.priority[x][y]) + end + end + end + elseif false then + yield(be1s, "", "ignoring block_square_event", tostring(be1._type), nil) + end + + i1, i2 = i1+1, i2+1 + -- the following is naive; it assumes the simple case instead of handling every case. + -- it could be improved somewhat by probing past the mismatched block_square_events + -- to see if a matching type is next in one of the vectors. + -- that still wouldn't handle the most complex general case. + elseif #bev1-i1 < #bev2-i2 then + yield(be1s, be2s, "missing block_square_event (1), skipping", nil, nil) + i1 = i1+1 + elseif #bev1-i1 > #bev2-i2 then + yield(be1s, be2s, "missing block_square_event (2), skipping", nil, nil) + i2 = i2+1 + else + yield(be1s, be2s, "mismatched block_square_events; aborting event comparison", be1._type, be2._type) + i1, i2 = #bev1, #bev2 + break + end + ::continue:: + end + return nil +end + +-------------------------------------------------------------------------------- +---@param b1 df.map_block +---@param b2 df.map_block +local function compare_map_block(b1, b2) + local max = 30 -- number of differences to display for this map block. + local count = 0 + local co = coroutine.create(co_compare_map_block) + + ---@alias errormessage string -- note: error messages have extra functionality in a metatable. + ---@type boolean, integer|string|errormessage, integer|string, string, string, string + local ok, x, y, desc, val1, val2 = dfhack.saferesume(co, b1, b2) + + while(ok and x and count < max) do + local xy = string.format("%s%s%s", tostring(x), (y == '' and '' or ' '), + (y ~= nil and tostring(y) or '')) + if math.type(x) == "integer" and math.type(y) == "integer" then + xy = string.format("tile %d,%d", x, y) + end + print(string.format("map block %s %s %s: %s%s", tostring(block_key(b1)), + xy, tostring(desc), tostring(val1), val2 ~= nil and (" ~= " .. tostring(val2)) or '')) + count = count + 1 + ok, x, y, desc, val1, val2 = dfhack.saferesume(co) + end + + if not ok then + local err = x + qerror(err) + end + if count >= max then + print("too many differences in block, skipping.") + end + return count +end + +-------------------------------------------------------------------------------- +-- test code for compare_map_block. +-- this was written for a map_block with six mineral veins, one type of grass, +-- and no other events. +-- it massages the first mineral vein and the grass. +if false then + compare_map_block(dfhack.maps.getTileBlock(16,16,118), + dfhack.maps.getTileBlock(16,16,119)) -- test wrong block + local total = 0 + -- this block on the test map is an underground block with two mineral veins + -- and no other map events. + local b = copy_map_block(dfhack.maps.getTileBlock(16,16,142)) + b.flags.has_magma_close = true + b.tiletype[15][15] = 15 + b.tiletype[14][14] = 14 + b.designation[13][13].light = true + b.occupancy[12][12].unit_grounded = true -- ignored + b.fog_of_war[11][11] = 11 -- ignored + b.path_cost[10][10] = 10 -- ignored + b.path_tag[9][9] = 9 -- ignored + b.walkable[8][8] = 8 -- ignored + b.map_edge_distance[7][7] = 7 -- ignored + b.temperature_1[6][6] = 6 -- ignored + b.temperature_2[5][5] = 5 -- ignored + b.lighting[4][4] = 4 + b.region_offset[3] = 9 -- out of range + if #b.block_events > 0 and b.block_events[0]._type == df.block_square_event_mineralst then + local bem = b.block_events[0] + ---@cast bem df.block_square_event_mineralst + bem.inorganic_mat = 2 + bem.tile_bitmask.bits[1] = bem.tile_bitmask.bits[1] ~ (1<<1) + bem.flags.whole = 0 + end + if #b.block_events > 0 and b.block_events[#b.block_events-1]._type == df.block_square_event_grassst then + local beg = b.block_events[#b.block_events-1] + ---@cast beg df.block_square_event_grassst + beg.plant_index = beg.plant_index - 1 + beg.amount[0][0] = 255 + end + local count = compare_map_block(b, dfhack.maps.getTileBlock(b.map_pos)) + total = total + count + print("total differences", total) + -- leak the map_block, we're not testing GC here. + return +end + +-------------------------------------------------------------------------------- +local function record_map_blocks() + printf("recording map blocks.") + local count = 0 + for _, block in ipairs(df.global.world.map.map_blocks) do + if true or interesting_map_block(block) then + count = count + 1 + table.insert(record, copy_map_block(block)) + end + end + printf("map recorded. found %d blocks.", count) +end + +-------------------------------------------------------------------------------- +local function compare_recorded_map_blocks() + printf("comparing recorded map with current map.") + printf("note: it seems normal to have a few differences in the block flags") + printf(".update_liquid and .update_liquid_twice for a couple of random blocks.") + local total = 0 + local max = 333 -- the number of differences to list before aborting. + local aborted = false + for i, block_copy in ipairs(record) do + local count = compare_map_block(block_copy, dfhack.maps.getTileBlock(block_copy.map_pos)) + total = total + count + if total > max then aborted = true; break; end + end + if aborted then printf("aborted! too many differences found."); end + printf("total differences found: %d", total) +end + +-------------------------------------------------------------------------------- +if not dfhack.isMapLoaded() then + if type(record) == "table" and next(record, nil) ~= nil then + print("discarding recorded map blocks.") + end + record = nil -- this uses the garbage collection mechanism to :delete() + -- the map blocks. + collectgarbage("collect") +elseif next(record, nil) == nil then + record_map_blocks() +else + compare_recorded_map_blocks() +end + +--[[ +[DFHack]# lua -f ./../../../compare_map.lua +comparing recorded map with current map. +map block (64,64,12) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (48,32,125) tile 2,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 2,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 3,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 3,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 4,12 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 4,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 4,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 5,12 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 5,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,125) tile 5,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (32,32,136) flags .designated: true ~= false +map block (64,64,10) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (64,64,10) tile 8,5 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 8,6 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 8,7 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 9,5 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 9,7 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 10,5 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 10,6 tiletype: StoneFloor1 ~= MineralFloor1 +map block (64,64,10) tile 10,7 tiletype: StoneFloor1 ~= LavaFloor1 +map block (32,32,123) flags .update_liquid: false ~= true +map block (32,32,123) flags .update_liquid_twice: false ~= true +map block (64,64,9) tile 6,5 tiletype: StoneRamp ~= FeatureRamp +map block (64,64,9) tile 7,5 tiletype: StoneFloor1 ~= FeatureFloor1 +map block (64,64,9) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (32,32,125) flags .update_liquid: true ~= false +map block (32,32,125) flags .update_liquid_twice: true ~= false +map block (32,48,136) flags .designated: true ~= false +map block (64,64,13) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (64,64,8) tile 7,6 tiletype: StoneStairUD ~= FeatureStairUD +map block (64,64,7) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (64,64,6) tile 7,6 tiletype: StoneStairU ~= FeatureStairU +map block (64,64,14) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (64,64,11) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD +map block (48,32,124) tile 2,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 2,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 3,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 3,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 4,12 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 4,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 4,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 5,12 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 5,13 tiletype: LavaFloor1 ~= StoneFloor1 +map block (48,32,124) tile 5,14 tiletype: LavaFloor1 ~= StoneFloor1 +map block (32,48,137) flags .designated: true ~= false +total differences found: 46 +[DFHack]# +--]] diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 82a52c3a85c..61886acb876 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -14,35 +14,35 @@ #include "modules/Gui.h" #include "modules/Job.h" #include "modules/Maps.h" -//-#include "modules/MapCache.h" +#include "modules/Materials.h" #include "modules/Random.h" #include "modules/Units.h" #include "modules/World.h" #include +#include #include -#include -#include "df/building.h" #include "df/builtin_mats.h" #include "df/historical_entity.h" -#include "df/inorganic_raw.h" //? #include "df/item.h" #include "df/map_block.h" #include "df/material.h" #include "df/plotinfost.h" #include "df/reaction_product_itemst.h" -#include "df/region_map_entry.h" //? +#include "df/region_map_entry.h" #include "df/tile_designation.h" #include "df/tile_occupancy.h" #include "df/unit.h" #include "df/vermin.h" #include "df/world.h" -#include "df/world_data.h" //? -#include "df/world_geo_biome.h" //? +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/world_region_details.h" #include "df/world_site.h" -#include "df/world_region_details.h" //? #include +#include #include #include @@ -56,9 +56,327 @@ namespace DFHack { DBG_DECLARE(dignow, channels, DebugCategory::LINFO); } +using std::min; using std::vector; using namespace DFHack; +static const df::region_map_entry* biome_at(const df::map_block &block, const df::coord2d p) +{ + auto blockptr = &static_cast(const_cast(block)); + auto biome = Maps::getRegionBiome(Maps::getBlockTileBiomeRgn(blockptr, p)); + return biome; +} + +static const df::region_map_entry* biome_at(const df::coord pos) +{ + auto block = Maps::getTileBlock(pos); + if (!block) + return nullptr; + auto blockref = static_cast(*block); + return biome_at(blockref, df::coord2d(pos) & 15); +} + +static const df::world_geo_biome* geo_biome_at(const df::map_block &block, const df::coord2d p) +{ + auto biome = biome_at(block, p); + if (!biome) + return nullptr; + if (biome->geo_index < 0) + return nullptr; + if (biome->geo_index >= df::world_geo_biome::get_vector().size()) + return nullptr; + auto geo_biome = df::world_geo_biome::get_vector()[biome->geo_index]; + return geo_biome; +} + +static const df::world_geo_biome* geo_biome_at(const df::coord pos) +{ + auto block = Maps::getTileBlock(pos); + if (!block) + return nullptr; + auto blockref = static_cast(*block); + return geo_biome_at(blockref, df::coord2d(pos) & 15); +} + +static const t_matpair layer_inorganic_n(const df::map_block &block, const df::coord2d p, size_t layer) +{ + using namespace df::enums::builtin_mats; + auto geo_biome = geo_biome_at(block, p); + if (!geo_biome) + return t_matpair(INORGANIC, -1); // can't happen, generic "rock" + layer = clip_range(layer, 0, geo_biome->layers.size() - 1); + return t_matpair(INORGANIC, geo_biome->layers[layer]->mat_index); +} + +static const t_matpair layer_inorganic_n(const df::coord pos, size_t layer) +{ + using namespace df::enums::builtin_mats; + auto block = Maps::getTileBlock(pos); + if (!block) + return t_matpair(INORGANIC, -1); // can't happen, generic "rock" + auto blockref = static_cast(*block); + return layer_inorganic_n(blockref, df::coord2d(pos) & 15, layer); +} + +static const size_t geolayer_at(const df::map_block &block, df::coord2d p) +{ + return index_tile(block.designation, p).bits.geolayer_index; +} + +static const t_matpair layer_inorganic_at(const df::map_block &block, df::coord2d p) +{ + return layer_inorganic_n(block, p, geolayer_at(block, p)); +} + +static const t_matpair layer_inorganic_at(const df::coord pos) +{ + using namespace df::enums::builtin_mats; + auto block = Maps::getTileBlock(pos); + if (!block) + return t_matpair(INORGANIC, -1); // generic "rock" + auto blockref = const_cast(static_cast(*block)); + return layer_inorganic_at(blockref, df::coord2d(pos) & 15); +} + +static const df::enums::tiletype_material::tiletype_material getGroundType(int32_t mat_index) +{ + if (isSoilInorganic(mat_index)) + return df::enums::tiletype_material::SOIL; + if (isStoneInorganic(mat_index)) + return df::enums::tiletype_material::STONE; + return df::enums::tiletype_material::NONE; +} + +static const df::enums::tiletype_material::tiletype_material getGroundType(const t_matpair matpair) +{ + using namespace df::enums::builtin_mats; + if (matpair.mat_type != INORGANIC) + return df::enums::tiletype_material::NONE; + return getGroundType(matpair.mat_index); +} + +// copied from Maps, used by getBiomeRgnPos() +static df::coord2d biome_offsets[9] = { + df::coord2d(-1,-1), df::coord2d(0,-1), df::coord2d(1,-1), + df::coord2d(-1,0), df::coord2d(0,0), df::coord2d(1,0), + df::coord2d(-1,1), df::coord2d(0,1), df::coord2d(1,1) +}; + +// copied from Maps. arguably this should be exported from Maps; it's useful. +inline df::coord2d getBiomeRgnPos(const df::coord2d base, BiomeOffset idx) +{ + auto r = base + biome_offsets[idx]; + + int world_width = world->world_data->world_width; + int world_height = world->world_data->world_height; + + return df::coord2d(clip_range(r.x, 0, world_width-1), + clip_range(r.y, 0, world_height-1)); +} + +// copied and modified from MapCache getBaseMaterial(). +// returns the material that should be used for boulders/gems, +// or the invalid material t_matpair(-1). +static const t_matpair baseMaterialAt(const df::map_block &block, df::coord2d p) +{ + using namespace df::enums::builtin_mats; + using namespace df::enums::tiletype_material; + + p = p & 15; + t_matpair rv; + auto tt = index_tile(block.tiletype, p); + switch (tileMaterial(tt)) { + // not diggable, should not drop boulders. + case df::enums::tiletype_material::NONE: + case AIR: + case CONSTRUCTION: + case HFS: + case CAMPFIRE: + case FIRE: + case MAGMA: + case POOL: + case RIVER: + case TREE: + case ROOT: // TODO this is a bug; DF can dig roots. + case MUSHROOM: + case UNDERWORLD_GATE: + rv = t_matpair(-1); + break; + + // diggable but should not drop boulders, so return the first layer, presumably soil. + // (these won't drop anyway because they are not WALL or FORTIFICATION shaped, but safety first.) + case DRIFTWOOD: + case BROOK: + case ASHES: + case PLANT: + case GRASS_LIGHT: + case GRASS_DARK: + case GRASS_DRY: + case GRASS_DEAD: + rv = layer_inorganic_n(block, p, 0); + break; + + case STONE: + { + rv = t_matpair(INORGANIC, -1); // fallback: generic "rock" + auto geo_biome = geo_biome_at(block, p); + if (!geo_biome) + break; + + // IS THIS CODE CORRECT? + // intent here is to index 0..15 elements into a vector of unknown length, + // and get the element at vector[ min(index, last_element_offset) ] . + auto geo_layer_it = begin(geo_biome->layers); + std::ranges::advance(geo_layer_it, geolayer_at(block, p), end(geo_biome->layers) - 1); + rv = t_matpair(INORGANIC, (*geo_layer_it)->mat_index); + if (getGroundType(rv) == STONE) + break; + + // fall back to the first stone layer, or the very last layer. + + // IS TNIS CODE CORRECT? + // intent here is to search a vector of unknown length for the + // first element matching a predicate, and get that element + // OR the last element in the vector, whether or not it matches. + // this is a lot of faffing around for something that's more concise + // in C style C++; see the SOIL code below. + // maybe I could make the iterators use the layer_inorganic_n function? + auto is_stone = [&](df::world_geo_layer *geo_layer) + { return getGroundType(geo_layer->mat_index) == STONE; }; + // this vector typically has 16 entries, but I have seen 13 in oceans. + auto &geo_layers = geo_biome->layers; + geo_layer_it = std::find_if(begin(geo_layers), end(geo_layers) - 1, is_stone); + rv = t_matpair(INORGANIC, (*geo_layer_it)->mat_index); + break; + } + + case SOIL: + rv = layer_inorganic_at(block, p); + if (getGroundType(rv) != SOIL) { + // fall back to the last soil layer, or the very first layer. + for (auto i = 15; i >= 0; i--) { + rv = layer_inorganic_n(block, p, i); // this call clips to vector size. + if (getGroundType(rv) == SOIL) + break; + } + } + break; + + case FEATURE: + { + // fallback: no feature flag, or no event? no drops. + rv = t_matpair(-1); + auto des = index_tile(block.designation, p); + t_feature feature; + feature.type = df::enums::feature_type::feature_type::NONE; + if (des.bits.feature_local) + Maps::ReadFeatures( + &(static_cast(const_cast(block))), + &feature, nullptr); + else if (des.bits.feature_global) + Maps::ReadFeatures( + &(static_cast(const_cast(block))), + nullptr, &feature); + if (feature.type != df::enums::feature_type::feature_type::NONE) + rv = t_matpair(feature.main_material, feature.sub_material); + break; + } + + case LAVA_STONE: + { + auto region_details = world->world_data->midmap_data.region_details; + auto des = index_tile(block.designation, p); + auto block_region_offset_idx = des.bits.biome; + if (block_region_offset_idx >= DFHack::eBiomeCount) + // "can't happen", fall back to the local region tile's biome. + block_region_offset_idx = DFHack::eHere; + auto region_details_coord2d = getBiomeRgnPos(block.map_pos, + static_cast(block_region_offset_idx)); + auto region_details_idx = linear_index(region_details, + &df::world_region_details::pos, region_details_coord2d); + if (region_details_idx == -1) + { + // "can't happen"; fall back to the first region. + DEBUG(general).print("BaseMaterialAt(block {}, tile {}, case " + "LAVA_STONE, didn't find region_details for region coord {}\n", + block.map_pos, p, region_details_coord2d); + region_details_idx = 0; + } + rv = t_matpair(INORGANIC, region_details[region_details_idx]->lava_stone); + break; + } + + case MINERAL: + // fall back to the layer material, whether SOIL or STONE. + rv = layer_inorganic_at(block, p); + for (auto event : block.block_events) { + if (event->getType() == df::block_square_event_type::mineral) { + auto vein = (df::block_square_event_mineralst *)event; + if (vein->getassignment(p)) { + rv = t_matpair(INORGANIC, vein->inorganic_mat); + // do not early-out. later mineral events can override earlier ones. + } + } + } + break; + + case FROZEN_LIQUID: + rv = t_matpair(WATER, 0); + break; + + // no default so we get a compiler warning. + } + + if (rv == t_matpair(-1)) + return t_matpair(-1); + MaterialInfo mi; + mi.decode(rv); + if (mi.isValid() && mi.material->flags.is_set(df::material_flags::UNDIGGABLE)) + return t_matpair(-1); + return rv; +} + +static const t_matpair baseMaterialAt(df::coord pos) +{ + auto block = Maps::getTileBlock(pos); + if (!block) + return t_matpair(-1); + auto blockref = const_cast(static_cast(*block)); + return baseMaterialAt(blockref, df::coord2d(pos) & 15); +} + +static const df::inclusion_type veinTypeAt(df::map_block &block, df::coord2d p) +{ + using namespace df::enums::inclusion_type; + auto veintype = TOTAL; + for (auto event : block.block_events) { + if (event->getType() == df::block_square_event_type::mineral) { + auto vein = (df::block_square_event_mineralst *)event; + if (vein->getassignment(p)) { + if (vein->flags.bits.cluster_one) + veintype = CLUSTER_ONE; + else if (vein->flags.bits.cluster_small) + veintype = CLUSTER_SMALL; + else if (vein->flags.bits.vein) + veintype = VEIN; + else if (vein->flags.bits.cluster) + veintype = CLUSTER; + // do not early-out. later mineral events can override earlier ones. + } + } + } + return veintype; +} + +static const df::inclusion_type veinTypeAt(df::coord pos) +{ + auto block = Maps::getTileBlock(pos); + if (!block) + return df::enums::inclusion_type::TOTAL; + auto blockref = const_cast(static_cast(*block)); + return veinTypeAt(blockref, df::coord2d(pos) & 15); +} + struct designation{ df::coord pos; df::tile_designation type; @@ -75,6 +393,7 @@ struct designation{ } }; +// TODO maybe: consider using the version in coord.methods.inc + DataDefs.h namespace std { template <> struct hash { @@ -85,7 +404,7 @@ namespace std { }; } -class TODODesignationJobs { +class DesignationJobs { private: std::unordered_map designations; std::unordered_map jobs; @@ -234,7 +553,7 @@ static void propagate_vertical_flags(const df::coord &pos) { tileShape(*Maps::getTileType(df::coord(pos.x, pos.y, zlevel))); while ((shape == df::tiletype_shape::EMPTY || shape == df::tiletype_shape::RAMP_TOP) - && Maps::ensureTileBlock(df::coord(pos.x, pos.y, --zlevel))) { + && Maps::ensureTileBlock(df::coord(pos.x, pos.y, --zlevel))) { df::coord pos_below(pos.x, pos.y, zlevel); df::tile_designation td_below = *Maps::getTileDesignation(pos_below); if (td_below.bits.light == td.bits.light @@ -301,22 +620,62 @@ static bool can_dig_ramp(df::tiletype tt) { shape == df::tiletype_shape::FORTIFICATION; } -static void dig_type(const df::coord pos, df::tiletype tt) { - auto blk = Maps::getTileBlock(pos); - if (!blk) +static void dig_type(const df::coord pos, df::tiletype in_tt) { + using namespace df::enums::tiletype_material; + using namespace df::enums::tiletype_shape_basic; + + auto block = Maps::getTileBlock(pos); + if (!block) return; // digging a tile should revert it to the layer soil/stone material - auto des = Maps::getTileDesignation(pos); - tt = matchTileMaterial(tt, (des->bits.geolayer_index <= 3) ? - df::enums::tiletype_material::SOIL : df::enums::tiletype_material::STONE); - *Maps::getTileType(pos) = tt; - for (auto event : blk->block_events) { - if (event->getType() == df::block_square_event_type::mineral) { - auto vein = (df::block_square_event_mineralst *)event; - vein->setassignment(pos, 0); - } + // TODO that's a bug, actually, that is not what the game does. + // this bug has been temporarily preserved to remain bug-for-bug compatible. + // this bug is NOT the same bug as the preserved bug 25 lines below. + auto tt = in_tt; + if (tileShapeBasic(tileShape(tt)) != Open) { + auto matpair = baseMaterialAt(pos); + auto ground_type = getGroundType(matpair); + if (ground_type == SOIL || ground_type == STONE) + tt = matchTileMaterial(tt, ground_type); + else + DEBUG(general).print("dig_type: getGroundType did not return SOIL or STONE." + " not updating tiletype material. {} in:{} out:{} ({},{})\n", + pos, static_cast(in_tt), static_cast(tt), + matpair.mat_type, matpair.mat_index); + if (tt == df::tiletype::Void) + DEBUG(general).print("dig_type: matchTileMaterial: tiletype is Void." + " {} in:{} out:{} ({},{})\n", + pos, static_cast(in_tt), static_cast(tt), + matpair.mat_type, matpair.mat_index); + if (tileMaterial(tt) == NONE) // true for tiletypes Void and Unused[0-9]+ + DEBUG(general).print("dig_type: matchTileMaterial: tiletype_material is NONE." + " {} in:{} out:{} ({},{})\n", + pos, static_cast(in_tt), static_cast(tt), + matpair.mat_type, matpair.mat_index); + } + + // this segment temporarily reimplements buggy behavior to remain bug-for-bug compatible. + // specifically, STONE tiles (i.e. layer stone) which are of the material type + // of the current region's .lava_stone (e.g. obsidian) are changed to LAVA_STONE. + // (later: unless they are in a local feature, apparently.) + if (true) { // TODO when bugfixing starts, remove this block. + auto matpair = baseMaterialAt(pos); + auto des = index_tile(block->designation, pos); + auto block_region_offset_idx = des.bits.biome; + auto region_details_idx = block->region_offset[block_region_offset_idx]; + if (matpair.mat_index == world->world_data->midmap_data.region_details[region_details_idx]->lava_stone + && ! des.bits.feature_local) + tt = matchTileMaterial(tt, LAVA_STONE); } + + if (tt == df::tiletype::Void) + DEBUG(general).print("dig_type: setting tile: tiletype is Void. {} in:{} out:{}\n", + pos, static_cast(in_tt), static_cast(tt)); + if (tileMaterial(tt) == NONE) // true for tiletypes Void and Unused[0-9]+ + DEBUG(general).print("dig_type: setting tile: tiletype_material is NONE. {} in:{} out:{}\n", + pos, static_cast(in_tt), static_cast(tt)); + index_tile(block->tiletype, pos) = tt; } static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { @@ -329,8 +688,7 @@ static df::tiletype get_target_type(df::tiletype tt, df::tiletype_shape shape) { return findRandomVariant(tt); } -static void dig_shape(const df::coord pos, - df::tiletype tt, df::tiletype_shape shape) { +static void dig_shape(const df::coord pos, df::tiletype tt, df::tiletype_shape shape) { dig_type(pos, get_target_type(tt, shape)); } @@ -363,7 +721,7 @@ static void clean_ramp(const df::coord pos) { is_wall(df::coord(pos.x-1, pos.y-1, pos.z)) || is_wall(df::coord(pos.x-1, pos.y+1, pos.z)) || is_wall(df::coord(pos.x+1, pos.y-1, pos.z)) || - is_wall(df::coord(pos.x+1, pos.y+1, pos.z))) + is_wall(df::coord(pos.x+1, pos.y+1, pos.z)) ) return; remove_ramp_top(df::coord(pos.x, pos.y, pos.z+1)); @@ -373,6 +731,7 @@ static void clean_ramp(const df::coord pos) { // removes self and/or orthogonally adjacent ramps that are no longer adjacent // to a wall static void clean_ramps(const df::coord pos) { + // TODO possible bug; should clean_ramp this tile *last*. clean_ramp(pos); clean_ramp(df::coord(pos.x-1, pos.y, pos.z)); clean_ramp(df::coord(pos.x+1, pos.y, pos.z)); @@ -408,59 +767,54 @@ struct dug_tile_info { dug_tile_info(const df::coord pos) { this->pos = pos; - df::tiletype tt = *Maps::getTileType(pos); + auto tt = *Maps::getTileType(pos); tmat = tileMaterial(tt); itype = df::item_type::BOULDER; - imat = -1; + imat = t_matpair(-1); df::tiletype_shape shape = tileShape(tt); if (shape != df::tiletype_shape::WALL && shape != df::tiletype_shape::FORTIFICATION) return; - switch (tmat) { - case df::tiletype_material::STONE: - case df::tiletype_material::MINERAL: - case df::tiletype_material::FEATURE: - case df::tiletype_material::LAVA_STONE: - imat = TODOmap.baseMaterialAt(pos); - break; - case df::tiletype_material::FROZEN_LIQUID: - // assume frozen water - // we can't use baseMaterialAt here because it will return the underlying river bed material - imat = t_matpair(df::builtin_mats::WATER, -1); - break; - default: - return; - } + imat = baseMaterialAt(pos); + if (imat == t_matpair(-1)) + return; MaterialInfo mi; mi.decode(imat); if (mi.type == -1 || !mi.material) return; - if (mi.material->isGem()) { + if (mi.material->isGem()) itype = df::item_type::ROUGH; - } } }; static bool is_diggable(const df::coord pos, df::tiletype tt) { + using namespace df::enums::tiletype_material; df::tiletype_material mat = tileMaterial(tt); switch (mat) { - case df::tiletype_material::CONSTRUCTION: - case df::tiletype_material::TREE: - case df::tiletype_material::ROOT: - case df::tiletype_material::MAGMA: - case df::tiletype_material::HFS: - case df::tiletype_material::UNDERWORLD_GATE: - return false; - default: - break; + case CONSTRUCTION: + case HFS: + case CAMPFIRE: + case FIRE: + case MAGMA: + case POOL: + case RIVER: + case ROOT: // TODO this is a bug; DF can dig roots. + case TREE: + case MUSHROOM: + case UNDERWORLD_GATE: + return false; + case AIR: + return true; + default: + break; } MaterialInfo mi; - mi.decode(TODOmap.baseMaterialAt(pos)); + mi.decode(baseMaterialAt(pos)); if (mi.material != nullptr && mi.material->flags.is_set(df::material_flags::UNDIGGABLE)) return false; @@ -498,9 +852,8 @@ static bool dig_tile(color_ostream &out, TRACE(channels).print("dig_tile: channeling at ({}) [can_dig_channel: true]\n", pos_below); target_type = df::tiletype::OpenSpace; df::coord pos_above(pos.x, pos.y, pos.z+1); - if (Maps::ensureTileBlock(pos_above)) { + if (Maps::ensureTileBlock(pos_above)) remove_ramp_top(pos_above); - } df::tile_dig_designation td_below = (*Maps::getTileDesignation(pos_below)).bits.dig; if (dig_tile(out, pos_below, df::tile_dig_designation::Ramp, dug_tiles)) { clean_ramps(pos_below); @@ -540,11 +893,7 @@ static bool dig_tile(color_ostream &out, if (dug_tiles.size() == 0) dug_tiles.push_back(dug_tile_info(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 - *Maps::getTileType(pos_above) = - get_target_type(tt, df::tiletype_shape::RAMP_TOP); + *Maps::getTileType(pos_above) = df::tiletype::RampTop; remove_ramp_top(df::coord(pos.x, pos.y, pos.z+2)); propagate_vertical_flags(df::coord(pos.x, pos.y, pos.z + 1)); } @@ -558,6 +907,13 @@ static bool dig_tile(color_ostream &out, pos.x, pos.y, pos.z, ENUM_AS_STR(designation)); } + if (target_type == df::tiletype::Void) + DEBUG(general).print("dig_tile: target_type: tiletype is Void. {} {}\n", + pos, static_cast(tt)); + if (tileMaterial(target_type) == df::tiletype_material::NONE) + DEBUG(general).print("dig_tile: target_type: tiletype_material is NONE. {} {}\n", + pos, static_cast(tt)); + // fail if unhandled or no change to tile if (target_type == df::tiletype::Void || target_type == tt) return false; @@ -718,41 +1074,18 @@ static bool produces_item(const boulder_percent_options &options, if (info.tmat == df::tiletype_material::FEATURE) probability = options.deep; else { - std::vector veins; - Maps::SortBlockEvents(Maps::getTileBlock(info.pos), &veins); - - // the logic in MapCache WriteVeins() and in tile-material.lua GetVeinMat() - // suggests that veins are not strictly sorted by inclusion type. - // the needed sort order is CLUSTER, VEIN, CLUSTER_SMALL, CLUSTER_ONE. - // accordingly, we can't use the inclusion_type enum; it has VEIN < CLUSTER. - // we can use the df::mineral_event_flag::Shift::shift_* enum. - uint32_t priority = 0; - probability = options.layer; - for (auto vein : veins) { - uint32_t veinpriority = -1; - uint32_t veinprobability; - - if (vein->flags.bits.cluster_one) { - veinpriority = df::mineral_event_flag::Shift::shift_cluster_one; - veinprobability = options.small_cluster; - } - else if (vein->flags.bits.cluster_small) { - veinpriority = df::mineral_event_flag::Shift::shift_cluster_small; - veinprobability = options.small_cluster; - } - else if (vein->flags.bits.vein) { - veinpriority = df::mineral_event_flag::Shift::shift_vein; - veinprobability = options.vein; - } - else if (vein->flags.bits.cluster) { - veinpriority = df::mineral_event_flag::Shift::shift_cluster; - veinprobability = options.vein; - } - - if (veinpriority >= priority && vein->getassignment(info.pos)) { - priority = veinpriority; - probability = veinprobability; - } + switch (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; } } @@ -765,7 +1098,7 @@ typedef std::map, std::vector> static void do_dig(color_ostream &out, std::vector &dug_coords, item_coords_t &item_coords, const dig_now_options &options) { Random::MersenneRNG rng; - TODODesignationJobs jobs; + DesignationJobs jobs; DEBUG(general).print("do_dig(): starting..\n"); jobs.load(); From 70fc5d8d0ec2f3d7d1d651e36299a7ca4612b506 Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 19 Jun 2026 11:21:03 -0700 Subject: [PATCH 5/7] dig-now: remove dependency on MapCache. compiles, runs, works. ready to start code review. --- compare_map.lua | 730 ---------------------------------------- plugins/dig-now.cpp | 132 +++++--- plugins/lua/dig-now.lua | 16 +- 3 files changed, 104 insertions(+), 774 deletions(-) delete mode 100644 compare_map.lua diff --git a/compare_map.lua b/compare_map.lua deleted file mode 100644 index e7d7aa8044d..00000000000 --- a/compare_map.lua +++ /dev/null @@ -1,730 +0,0 @@ ---[====[ -This developer script was created to compare the effects of the old -and new versions of the `dig-now` plugin during development. - -It works by recording interesting map blocks from the current map -when first run, then comparing those map blocks with the current -map on subsequent runs. To clear the recorded blocks, run the -script from the main menu (i.e. with no map loaded). - -Example usage: - * Load a test map that has dig-designations. - * Run the old version of the plugin: e.g. `dig-now-old`. - * Run this script to record the map state. - * Quit without saving back to the main menu. - * Reload the same test map. - * Run the new version of the plugin: e.g. `dig-now`. - * Run this script again to compare the map state. - * Analyze the results. - * Quit without saving back to the main menu. - * Run the script a third time to clear the results. - - -It will need to be modified to work with anything else. - -code possibly of interest: - ipairsN iterator, works with the `for` keyword. - each_tile_in_block iterator, works with the `for` keyword. - garbage collection of df types created by Lua: - df.map_block and df.block_square_event -]====] - ----@type df.map_block[] -record = record or setmetatable( {}, { __gc = GC_map_block_table, } ) -- global, persistant - --------------------------------------------------------------------------------- -local addressof = require('utils').addressof - --------------------------------------------------------------------------------- ----@param obj DFBase ----@return string -local function addressofs(obj) - assert(df.isvalid(obj)) - return(string.format("%016X", addressof(obj))) -end - --------------------------------------------------------------------------------- ----@param s string ----@param i integer ----@return string -local function pad(s,i) - return s .. string.rep(' ', i - #s) -end - --------------------------------------------------------------------------------- ----@param fmt string ----@param ... any -local function printf(fmt, ...) - print(string.format(fmt, ...)) -end - --------------------------------------------------------------------------------- ---- `ipairsN()` is `ipairs()` that accepts one or more Lua list ---- or DF vector or array and iterates over them in lockstep. ---- * the lists must be of equal size. ---- * the lists must not have `nil` as the last element. ---- * really, the lists should not contain `nil` at all, ---- because such tables are not lists. ---- `Lua 5.3 Reference Manual §3.4.7` --- --- usage example: --- ``` --- t1 = { 1, 2, 3, 4, } --- t2 = { 'a', 'b', 'c', 'd', } --- t3 = { df.job_type[1], df.job_type[2], df.job_type[3], df.job_type[4], } --- t4 = { df.item_barst, df.item_blocksst, df.item_barrelst, df.item_binst, } --- --- for i, a, b, c, d in ipairsN(t1, t2, t3, t4) do --- print(i, a, b, c, d) --- end --- ``` --- warning! currently this returns a 1-based index even for Df vectors and arrays. --- you must subtract 1 to index the proper element of a DF type. --- ----@generic T ----@param ... T[] DF vectors or arrays, or Lua lists. ----@return fun(nil, integer?):integer?, T?... iterator ----@return nil state ----@return integer start # always 0 -local function ipairsN(...) --- implementation difference: this returns iterator, nil, nil --- unlike ipairs() which returns iterator, table, 0. --- (this is because 0 is a valid index for DF vectors.) --- the second nil is the state. --- this implementation captures state as upvalues, so the --- state passed between `for` and the iterator is not used. - local is_container = require("utils").is_container - local count = select('#', ...) - assert(count >= 1) - local arrays = {...} - local length = #arrays[1] - for i = 1, count do - local array = arrays[i] - assert(type(array) == "table" or is_container(array), "parameter " .. i .. " is not an array") - assert(#arrays[i] == length, "array " .. i .. " is not the same size as array 1") - end - - ---@generic T - ---@param state nil - ---@param index integer? - ---@return integer? next-index - ---@return T?... - -- note: uses upvalues is_container, count, arrays, length - -- note: debugging uses upvalues array and debugging_verify_arraysize for asserts. - -- because of these upvalue captures, this returns a new closure every time. - -- this could be avoided by nesting these and Array inside a state table. - -- DONE: given that I'm capturing upvalues anyway, why pass state in and out? - -- just pass in and out a nil state, and use the captures. - local function iterator(state, index) - assert(state == nil) - assert((math.type(index) == "integer" and index >= 0 and index <= length), "index was bad or out-of-range: " .. tostring(index)) - for i = 1, count do - local array = arrays[i] - assert(#arrays[i] == length, "iterator: array " .. i .. " has changed length") - end - - index = index + 1 - if index > length then - return nil, table.unpack({}, 1, count) -- return all nils - end - - local list = {} - for i = 1, count do - local array = arrays[i] - local idx = is_container(array) and index-1 or index - list[i] = array[idx] - end - - return index, table.unpack(list, 1, count) -- this construct preserves trailing nils. - end - - return iterator, nil, 0 -end - --------------------------------------------------------------------------------- ----@class (exact) block_key ----@field value unknown -- intended to be opaque. - --------------------------------------------------------------------------------- ----@param block df.map_block ----@return block_key -local function block_key(block) - assert(df.isvalid(block)) - assert(df.map_block:is_instance(block)) - ---@diagnostic disable-next-line: return-type-mismatch - return string.format( "(%d,%d,%d)", pos2xyz(block.map_pos) ) -end - --------------------------------------------------------------------------------- ----@param block df.map_block -function GC_map_block(block) - assert(df.isvalid(block)) - assert(df.map_block:is_instance(block)) - block.pool_id = -1 - assert(block.block_burrows.item == nil) - assert(block.block_burrows.prev == nil) - assert(block.block_burrows.next == nil) - -- ignore block.items; it's a int32_t vector so it's safe to destruct. - assert(#block.flows == 0) - block.flow_pool.reuse_idx = -1 - for i = #block.block_events-1,0,-1 do - assert(df.block_square_event:is_instance(block.block_events[i])) - block.block_events[i]:delete() - block.block_events:erase(i) - end - assert(#block.block_events == 0) - block:delete() -end - --------------------------------------------------------------------------------- ----@param t table | df.map_block[] -function GC_map_block_table(t) - for k, block in pairs(t) do - if df.map_block:is_instance(block) then - GC_map_block(block) - end - t[k] = nil - end -end - --------------------------------------------------------------------------------- ----@param block df.map_block ----@return df.map_block -local function copy_map_block(block) - assert(df.isvalid(block)) - assert(df.map_block:is_instance(block)) - local inb = block - local outb = inb:new() - outb.block_burrows.item = nil - outb.block_burrows.prev = nil - outb.block_burrows.next = nil - outb.block_events:resize(0) - outb.items:resize(0) - outb.flows:resize(0) - outb.flow_pool.reuse_idx = -1 - outb.flow_pool.flags.active = false - outb.pool_id = -1 - for i, inbse in ipairs(inb.block_events) do - assert(df.isvalid(inbse)) - assert(df.block_square_event:is_instance(inbse)) - assert(inbse._type ~= df.block_square_event) - local outbse = inbse:new() - assert(df.isvalid(outbse)) - assert(df.block_square_event:is_instance(outbse)) - assert(outbse._type ~= df.block_square_event) - outb.block_events:insert('#', outbse) - end - return outb -end - -if false then -- test code - collectgarbage("collect") - ---@type df.map_block? - local bc = copy_map_block(dfhack.maps.getTileBlock(16,16,135)) - printall(bc) - GC_map_block(bc) - bc = nil - collectgarbage("collect") - return -end -if false then -- test code - collectgarbage("collect") - for z = 0, xyz2pos(dfhack.maps.getSize()).z - 1 do - table.insert(record, copy_map_block(dfhack.maps.getTileBlock(16,16,z))) - local block = copy_map_block(dfhack.maps.getTileBlock(32,32,z)) - record[block_key(block)] = block - end - record = nil - collectgarbage("collect") - return -end - --------------------------------------------------------------------------------- ----@alias coord (df.coord | { x:integer, y:integer, z:integer }) -- a true df.coord or a 'pos' table. - --------------------------------------------------------------------------------- ----@type fun(x:integer, y:integer, z:integer):block:df.map_block?, lx:integer, ly:integer ----@overload fun(x:integer, y:integer, z:integer):block:df.map_block?, lx:integer, ly:integer -- this seems to be necessary, but I don't know why. ----@overload fun(_nil:nil):_nil:nil, lx:integer, ly:integer ----@overload fun(pos:table):block:df.map_block?, lx:integer, ly:integer ----@overload fun(pos:df.coord):block:df.map_block?, lx:integer, ly:integer ----@overload fun(c:df.construction):block:df.map_block, lx:integer, ly:integer ----@overload fun(b:df.map_block):block:df.map_block?, lx:integer, ly:integer -local function get_block_and_local(x, y, z) - if df.isvalid(x) then - ---@cast x -nil - if x._type == df.coord then - return dfhack.maps.getTileBlock(x), x.x & 15, x.y & 15 - elseif x._type == df.construction then - return dfhack.maps.getTileBlock(x.pos), x.pos.x & 15, x.pos.y & 15 - elseif x._type == df.map_block then - return x, 0, 0 - end - qerror("wasn't able to interpret parameter: " .. x._type) - elseif type(x) == "nil" then - return nil, 0, 0 - elseif math.type(x) == "integer" then - return dfhack.maps.getTileBlock(x, y, z), x & 15, y & 15 - elseif type(x) == "table" then - return dfhack.maps.getTileBlock(x), x.x & 15, x.y & 15 - end - qerror("wasn't able to interpret parameter: " .. type(x)) -end - --------------------------------------------------------------------------------- ---- this is an iterator function. it helps the `for` keyword process a map block. ---- for each of the 256 tiles in the block, the for loop gets these variables: ---- the map block, local_x, local_y, the tile's tiletype, the tile's designation, ---- and the tile's occupancy. ---- example code: ---- ``` ---- for block, lx, ly, tt, des, occ in each_tile_in_block(12, 34, 56) do ---- print(lx, ly) ---- end ---- ``` ---- note: if the block is nil then this loops 0 times, so there is no need to check. ----@param block_or_pos_or_x df.map_block|coord|integer|nil ----@param y integer? ----@param z integer? ----@return (fun(block:df.map_block):block:df.map_block, local_x:integer, local_y:integer, tiletype:df.tiletype, designation:df.tile_designation, occupancy:df.tile_occupancy), df.map_block? -local function each_tile_in_block(block_or_pos_or_x, y, z) - - --- this nested function is the workhorse for `each_tile_in_block`. - --- it `yield`s with these parameters: `df.map_block block`, `integer local_x`, - --- `integer local_y`, `df.tiletype tiletype`, - --- `df.tile_designation designation`, `df.tile_occupancy occupancy`. - --- when it is done, it returns nil, which ends the for loop. - ---@param Block df.map_block - ---@return nil - local function block_iterator(Block) - -- note: because this function doesn't share any of `each_tile_in_block`s - -- parameters or locals, this function is not a closure. - assert(df.map_block:is_instance(Block)) - for local_x = 0,15 do - local tiletype_sheaf, designation_sheaf, occupancy_sheaf = - Block.tiletype[local_x], Block.designation[local_x], Block.occupancy[local_x] - for local_y = 0,15 do - coroutine.yield(Block, local_x, local_y, tiletype_sheaf[local_y], - designation_sheaf[local_y], occupancy_sheaf[local_y]) - end - end - return nil - end - - ---@type df.map_block? - local block = get_block_and_local(block_or_pos_or_x, y, z) - if block == nil then - -- if there is no block, use this null iterator instead. - -- this tells `for` to loop 0 times. - return(function() return nil; end), nil - end - - -- `coroutine.wrap` creates a wrapper of the iterator function. - -- the `for` keyword will invoke the wrapper with parameter `block`. - -- the wrapper will call `block_iterator` with parameter `block`. - -- `block_iterator` will call `yield` once for each tile, with relevant data. - -- the wrapper will pass this relevant data to the `for` keyword. - -- when all 256 tiles have been processed via `yield`, `block_iterator` - -- will return nil. - -- the wrapper will return this nil to the `for` keyword. - -- this terminates the for loop. - -- - -- special note! these `yield`s are not the same as the `yield`s that - -- return control to Dwarf Fortress. - return coroutine.wrap(block_iterator), block -end - --------------------------------------------------------------------------------- ---- no longer used ----@param block df.map_block ----@param lx integer ----@param ly integer ----@param tt df.tiletype ----@param des df.tile_designation ----@param occ df.tile_occupancy ----@return boolean -local function interesting_map_block_tile(block, lx, ly, tt, des, occ) - local attrs = df.tiletype.attrs[tt] - -- ignore tiles flagged as caverns, magma, hell, adamantine pipes. - if des.feature_global or des.feature_local then return false; end - -- tiles that are normal wall aren't interesting. - if ( (attrs.shape == df.tiletype_shape.WALL and attrs.special == df.tiletype_special.NORMAL) - or (attrs.shape == df.tiletype_shape.WALL and attrs.special == df.tiletype_special.NONE) - ) then return false; end - -- tiles with oddball materials aren't interesting. - if ( attrs.material >= df.tiletype_material.FROZEN_LIQUID and attrs.material ~= df.tiletype_material.CONSTRUCTION - ) then return false; end - -- lit tiles that are normal floor, pebbles, boulders, ramps, or air aren't interesting. - if des.light and ( - (attrs.shape == df.tiletype_shape.FLOOR and attrs.special == df.tiletype_special.NORMAL) - or attrs.shape == df.tiletype_shape.PEBBLES - or attrs.shape == df.tiletype_shape.BOULDER - or attrs.shape == df.tiletype_shape.RAMP - or attrs.material == df.tiletype_material.AIR - ) then return false; end - return true -end - --------------------------------------------------------------------------------- ---- no longer used. ----@param block df.map_block ----@return boolean -local function interesting_map_block(block) - for block, lx, ly, tt, des, occ in each_tile_in_block(block) do - local attrs = df.tiletype.attrs[tt] - if interesting_map_block_tile(block, lx, ly, tt, des, occ) then - -- give a reason why this map block is interesting. - print(block_key(block), pad(df.tiletype[tt],23), pad(df.tiletype_shape[attrs.shape],15), - pad(df.tiletype_material[attrs.material],15), df.tiletype_special[attrs.special]) - return true - end - end - return false -end - --------------------------------------------------------------------------------- ---- for speed, this tests if a specific field in the first object is byte-for-byte ---- identical to the same field in the second object. ----@param obj1 DFStruct ----@param obj2 DFStruct ----@param field string ----@param size integer -local function identical_field(obj1, obj2, field, size) - --assert(type(field) == "string") - --assert(math.type(size) == "integer" and size > 0) - --assert(df.isvalid(obj1) and df.isvalid(obj2) and obj1._type == obj2._type) - -----@diagnostic disable-next-line: undefined-field - --assert(obj1._kind == "struct" and obj1._type._fields[field] ~= nil) - ---@diagnostic disable-next-line: undefined-field - local offset = obj1._type._fields[field].offset - local a1, a2 = addressof(obj1) + offset, addressof(obj2) + offset - return dfhack.internal.memcmp(a1, a2, size) == 0 -end - --------------------------------------------------------------------------------- ----@param b1 df.map_block ----@param b2 df.map_block -local function co_compare_map_block(b1, b2) - local yield = coroutine.yield - if block_key(b1) ~= block_key(b2) then - yield("ERROR", "", "different blocks", block_key(b1), block_key(b2)) - return nil - end - - -- structured types with named fields, e.g. bitfields. - if b1.flags.whole ~= b2.flags.whole then - for k in pairs(b1.flags) do - if k == "update_temperature" then - -- do nothing - elseif b1.flags[k] ~= b2.flags[k] then - yield("flags", "", "." .. k, b1.flags[k], b2.flags[k]) - end - end - end - - -- df.tiletype[16][16] - if not identical_field(b1, b2, "tiletype", df.tiletype:sizeof() * 16 * 16) then - for x, ttx1, ttx2 in ipairsN(b1.tiletype, b2.tiletype) do - for y, tt1, tt2 in ipairsN(ttx1, ttx2) do - local a1, a2 = df.tiletype.attrs[tt1], df.tiletype.attrs[tt2] - -- special case, digging operations create random variant floor tiles. - if a1.variant ~= df.tiletype_variant.NONE and a2.variant ~= df.tiletype_variant.NONE then - -- findTileType() and findSimilarTileType are not exported to Lua. - tt1, tt2 = df.tiletype[tt1], df.tiletype[tt2] -- string name - --tt1, tt2 = tt1:gsub('^(.*)[1234]$', "%11"), tt2:gsub('^(.*)[1234]$', "%11") - tt1, tt2 = tt1:sub(-#tt1, -2) .. '1', tt2:sub(-#tt2, -2) .. '1' - tt1, tt2 = df.tiletype[tt1], df.tiletype[tt2] -- back to integer - end - if tt1 ~= tt2 then - -- TODO need to deal with tiletypes that have variants. - yield(x-1, y-1, "tiletype", df.tiletype[tt1], df.tiletype[tt2]) - end - end - end - end - - -- df.tile_designation[16][16] - if not identical_field(b1, b2, "designation", df.tile_designation:sizeof() * 16 * 16) then - for x, v1y, v2y in ipairsN(b1.designation, b2.designation) do - for y, v1, v2 in ipairsN(v1y, v2y) do - if v1.whole ~= v2.whole then - for field in pairs(v1) do - if v1[field] ~= v2[field] then - yield(x-1, y-1, "designation" .. '.' .. field, v1[field], v2[field]) - end end end end end end - - -- df.tile_occupancy[16][16] - if not identical_field(b1, b2, "occupancy", df.tile_occupancy:sizeof() * 16 * 16) then - for x, v1y, v2y in ipairsN(b1.occupancy, b2.occupancy) do - for y, v1, v2 in ipairsN(v1y, v2y) do - if v1.whole ~= v2.whole then - for field in pairs(v1) do - if v1[field] ~= v2[field] - and field ~= "item" and field ~= "unit" and field ~= "unit_grounded" - then - yield(x-1, y-1, "occupancy" .. '.' .. field, v1[field], v2[field]) - end end end end end end - - -- ignore fog_of_war, path_cost, path_tag, walkable, map_edge_distance, temperature_1, temperature_2, lighting. - - -- don't ignore liquid_flow[16][16] ? - if not identical_field(b1, b2, "liquid_flow", df.tile_liquid_flow:sizeof() * 16 * 16) then - for x, v1y, v2y in ipairsN(b1.liquid_flow, b2.liquid_flow) do - for y, v1, v2 in ipairsN(v1y, v2y) do - if v1.whole ~= v2.whole then - for field in pairs(v1) do - if v1[field] ~= v2[field] then - yield(x-1, y-1, "liquid_flow" .. '.' .. field, v1[field], v2[field]) - end end end end end end - - -- int8_t[9] - for i, k1, k2 in ipairsN(b1.region_offset, b2.region_offset) do - if k1 ~= k2 then - yield("index "..i-1, '', "region_offset", k1, k2) - end - end - - -- walk both block_events vectors. lockstep? but need to watch for missing events. - local bev1, bev2 = b1.block_events, b2.block_events - local i1, i2 = 0, 0 - while (i1 <= #bev1-1 and i2 <= #bev2-1) do - local be1, be2 = bev1[i1], bev2[i2] - local be1s, be2s = "event " .. i1, "event " .. i2 - assert(df.block_square_event:is_instance(be1)) - assert(df.block_square_event:is_instance(be2)) - if be1._type == be2._type then - if be1._type == df.block_square_event_mineralst then - if be1.inorganic_mat ~= be2.inorganic_mat then - yield(be1s, be2s, tostring(be1._type) .. " different inorganic_mat", - be1.inorganic_mat, be2.inorganic_mat) - end - for x = 0, 15 do - for y = 0, 15 do - local xy = x..','..y..':' - local bm1, bm2 = dfhack.maps.getTileAssignment(be1.tile_bitmask, x, y), - dfhack.maps.getTileAssignment(be2.tile_bitmask, x, y) - if bm1 ~= bm2 then - yield(be1s, be2s, tostring(be1._type) .. " bitmap different", - xy..tostring(bm1), xy..tostring(bm2)) - end - end - end - if be1.flags.whole ~= be2.flags.whole then - yield(be1s, be2s, tostring(be1._type) .. " different flags", - be1.flags.whole, be2.flags.whole) - end - elseif false and be1._type == df.block_square_event_frozen_liquidst then - -- TODO this is potentially important, check on a frozen map. - elseif be1._type == df.block_square_event_grassst then - - elseif false and be1._type == df.block_square_event_designation_priorityst then - for x = 0, 15 do - for y = 0, 15 do - local xy = x..','..y - if be1.priority[x][y] ~= be2.priority[x][y] then - yield(be1s, be2s, tostring(be1._type) .. ' ' .. xy, - be1.priority[x][y], be2.priority[x][y]) - end - end - end - elseif false then - yield(be1s, "", "ignoring block_square_event", tostring(be1._type), nil) - end - - i1, i2 = i1+1, i2+1 - -- the following is naive; it assumes the simple case instead of handling every case. - -- it could be improved somewhat by probing past the mismatched block_square_events - -- to see if a matching type is next in one of the vectors. - -- that still wouldn't handle the most complex general case. - elseif #bev1-i1 < #bev2-i2 then - yield(be1s, be2s, "missing block_square_event (1), skipping", nil, nil) - i1 = i1+1 - elseif #bev1-i1 > #bev2-i2 then - yield(be1s, be2s, "missing block_square_event (2), skipping", nil, nil) - i2 = i2+1 - else - yield(be1s, be2s, "mismatched block_square_events; aborting event comparison", be1._type, be2._type) - i1, i2 = #bev1, #bev2 - break - end - ::continue:: - end - return nil -end - --------------------------------------------------------------------------------- ----@param b1 df.map_block ----@param b2 df.map_block -local function compare_map_block(b1, b2) - local max = 30 -- number of differences to display for this map block. - local count = 0 - local co = coroutine.create(co_compare_map_block) - - ---@alias errormessage string -- note: error messages have extra functionality in a metatable. - ---@type boolean, integer|string|errormessage, integer|string, string, string, string - local ok, x, y, desc, val1, val2 = dfhack.saferesume(co, b1, b2) - - while(ok and x and count < max) do - local xy = string.format("%s%s%s", tostring(x), (y == '' and '' or ' '), - (y ~= nil and tostring(y) or '')) - if math.type(x) == "integer" and math.type(y) == "integer" then - xy = string.format("tile %d,%d", x, y) - end - print(string.format("map block %s %s %s: %s%s", tostring(block_key(b1)), - xy, tostring(desc), tostring(val1), val2 ~= nil and (" ~= " .. tostring(val2)) or '')) - count = count + 1 - ok, x, y, desc, val1, val2 = dfhack.saferesume(co) - end - - if not ok then - local err = x - qerror(err) - end - if count >= max then - print("too many differences in block, skipping.") - end - return count -end - --------------------------------------------------------------------------------- --- test code for compare_map_block. --- this was written for a map_block with six mineral veins, one type of grass, --- and no other events. --- it massages the first mineral vein and the grass. -if false then - compare_map_block(dfhack.maps.getTileBlock(16,16,118), - dfhack.maps.getTileBlock(16,16,119)) -- test wrong block - local total = 0 - -- this block on the test map is an underground block with two mineral veins - -- and no other map events. - local b = copy_map_block(dfhack.maps.getTileBlock(16,16,142)) - b.flags.has_magma_close = true - b.tiletype[15][15] = 15 - b.tiletype[14][14] = 14 - b.designation[13][13].light = true - b.occupancy[12][12].unit_grounded = true -- ignored - b.fog_of_war[11][11] = 11 -- ignored - b.path_cost[10][10] = 10 -- ignored - b.path_tag[9][9] = 9 -- ignored - b.walkable[8][8] = 8 -- ignored - b.map_edge_distance[7][7] = 7 -- ignored - b.temperature_1[6][6] = 6 -- ignored - b.temperature_2[5][5] = 5 -- ignored - b.lighting[4][4] = 4 - b.region_offset[3] = 9 -- out of range - if #b.block_events > 0 and b.block_events[0]._type == df.block_square_event_mineralst then - local bem = b.block_events[0] - ---@cast bem df.block_square_event_mineralst - bem.inorganic_mat = 2 - bem.tile_bitmask.bits[1] = bem.tile_bitmask.bits[1] ~ (1<<1) - bem.flags.whole = 0 - end - if #b.block_events > 0 and b.block_events[#b.block_events-1]._type == df.block_square_event_grassst then - local beg = b.block_events[#b.block_events-1] - ---@cast beg df.block_square_event_grassst - beg.plant_index = beg.plant_index - 1 - beg.amount[0][0] = 255 - end - local count = compare_map_block(b, dfhack.maps.getTileBlock(b.map_pos)) - total = total + count - print("total differences", total) - -- leak the map_block, we're not testing GC here. - return -end - --------------------------------------------------------------------------------- -local function record_map_blocks() - printf("recording map blocks.") - local count = 0 - for _, block in ipairs(df.global.world.map.map_blocks) do - if true or interesting_map_block(block) then - count = count + 1 - table.insert(record, copy_map_block(block)) - end - end - printf("map recorded. found %d blocks.", count) -end - --------------------------------------------------------------------------------- -local function compare_recorded_map_blocks() - printf("comparing recorded map with current map.") - printf("note: it seems normal to have a few differences in the block flags") - printf(".update_liquid and .update_liquid_twice for a couple of random blocks.") - local total = 0 - local max = 333 -- the number of differences to list before aborting. - local aborted = false - for i, block_copy in ipairs(record) do - local count = compare_map_block(block_copy, dfhack.maps.getTileBlock(block_copy.map_pos)) - total = total + count - if total > max then aborted = true; break; end - end - if aborted then printf("aborted! too many differences found."); end - printf("total differences found: %d", total) -end - --------------------------------------------------------------------------------- -if not dfhack.isMapLoaded() then - if type(record) == "table" and next(record, nil) ~= nil then - print("discarding recorded map blocks.") - end - record = nil -- this uses the garbage collection mechanism to :delete() - -- the map blocks. - collectgarbage("collect") -elseif next(record, nil) == nil then - record_map_blocks() -else - compare_recorded_map_blocks() -end - ---[[ -[DFHack]# lua -f ./../../../compare_map.lua -comparing recorded map with current map. -map block (64,64,12) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (48,32,125) tile 2,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 2,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 3,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 3,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 4,12 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 4,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 4,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 5,12 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 5,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,125) tile 5,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (32,32,136) flags .designated: true ~= false -map block (64,64,10) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (64,64,10) tile 8,5 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 8,6 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 8,7 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 9,5 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 9,7 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 10,5 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 10,6 tiletype: StoneFloor1 ~= MineralFloor1 -map block (64,64,10) tile 10,7 tiletype: StoneFloor1 ~= LavaFloor1 -map block (32,32,123) flags .update_liquid: false ~= true -map block (32,32,123) flags .update_liquid_twice: false ~= true -map block (64,64,9) tile 6,5 tiletype: StoneRamp ~= FeatureRamp -map block (64,64,9) tile 7,5 tiletype: StoneFloor1 ~= FeatureFloor1 -map block (64,64,9) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (32,32,125) flags .update_liquid: true ~= false -map block (32,32,125) flags .update_liquid_twice: true ~= false -map block (32,48,136) flags .designated: true ~= false -map block (64,64,13) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (64,64,8) tile 7,6 tiletype: StoneStairUD ~= FeatureStairUD -map block (64,64,7) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (64,64,6) tile 7,6 tiletype: StoneStairU ~= FeatureStairU -map block (64,64,14) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (64,64,11) tile 7,6 tiletype: StoneStairUD ~= MineralStairUD -map block (48,32,124) tile 2,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 2,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 3,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 3,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 4,12 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 4,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 4,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 5,12 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 5,13 tiletype: LavaFloor1 ~= StoneFloor1 -map block (48,32,124) tile 5,14 tiletype: LavaFloor1 ~= StoneFloor1 -map block (32,48,137) flags .designated: true ~= false -total differences found: 46 -[DFHack]# ---]] diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 61886acb876..7a3d4dafba8 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -5,6 +5,7 @@ #include "DataFuncs.h" #include "Debug.h" #include "LuaTools.h" +//#include "LuaWrapper.h" #include "PluginManager.h" #include "PluginLua.h" #include "TileTypes.h" @@ -224,8 +225,9 @@ static const t_matpair baseMaterialAt(const df::map_block &block, df::coord2d p) break; // IS THIS CODE CORRECT? - // intent here is to index 0..15 elements into a vector of unknown length, - // and get the element at vector[ min(index, last_element_offset) ] . + // intent here is to index 0..15 into a vector with 13 or 16 elements + // and get the element at min(index, vector length-1) . + // ranges::advance seems like the right tool. auto geo_layer_it = begin(geo_biome->layers); std::ranges::advance(geo_layer_it, geolayer_at(block, p), end(geo_biome->layers) - 1); rv = t_matpair(INORGANIC, (*geo_layer_it)->mat_index); @@ -235,12 +237,11 @@ static const t_matpair baseMaterialAt(const df::map_block &block, df::coord2d p) // fall back to the first stone layer, or the very last layer. // IS TNIS CODE CORRECT? - // intent here is to search a vector of unknown length for the - // first element matching a predicate, and get that element + // intent here is to search a vector with 13 or 16 elements + // for the first element matching a predicate, and get that element // OR the last element in the vector, whether or not it matches. - // this is a lot of faffing around for something that's more concise - // in C style C++; see the SOIL code below. - // maybe I could make the iterators use the layer_inorganic_n function? + // all this is a lot of faffing around for something that's more + // concise in C style C++; see the SOIL code below. auto is_stone = [&](df::world_geo_layer *geo_layer) { return getGroundType(geo_layer->mat_index) == STONE; }; // this vector typically has 16 entries, but I have seen 13 in oceans. @@ -286,17 +287,17 @@ static const t_matpair baseMaterialAt(const df::map_block &block, df::coord2d p) { auto region_details = world->world_data->midmap_data.region_details; auto des = index_tile(block.designation, p); - auto block_region_offset_idx = des.bits.biome; - if (block_region_offset_idx >= DFHack::eBiomeCount) + auto block_region_offset_idx = static_cast(des.bits.biome); + if (block_region_offset_idx >= eBiomeCount) // "can't happen", fall back to the local region tile's biome. - block_region_offset_idx = DFHack::eHere; - auto region_details_coord2d = getBiomeRgnPos(block.map_pos, - static_cast(block_region_offset_idx)); + block_region_offset_idx = eHere; + auto blockptr = &static_cast(const_cast(block)); + auto region_details_coord2d = Maps::getBlockTileBiomeRgn(blockptr, p); auto region_details_idx = linear_index(region_details, &df::world_region_details::pos, region_details_coord2d); if (region_details_idx == -1) { - // "can't happen"; fall back to the first region. + // "can't happen"; fall back to the region of the first map_block. DEBUG(general).print("BaseMaterialAt(block {}, tile {}, case " "LAVA_STONE, didn't find region_details for region coord {}\n", block.map_pos, p, region_details_coord2d); @@ -633,33 +634,37 @@ static void dig_type(const df::coord pos, df::tiletype in_tt) { // this bug has been temporarily preserved to remain bug-for-bug compatible. // this bug is NOT the same bug as the preserved bug 25 lines below. auto tt = in_tt; + if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) + TRACE(general).print("dig_type: pre-dig: {}. {} in:{} out:{}\n", + (tt == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE"), + pos, static_cast(in_tt), static_cast(tt)); + if (tileShapeBasic(tileShape(tt)) == None || tileShapeBasic(tileShape(tt)) == Open) + TRACE(general).print("dig_type: pre-dig: tiletype_shape_basic is {}. {} in:{} out:{}\n", + (tileShapeBasic(tileShape(tt)) == None ? "None" : "Open"), + pos, static_cast(in_tt), static_cast(tt)); + if (tileShapeBasic(tileShape(tt)) != Open) { auto matpair = baseMaterialAt(pos); auto ground_type = getGroundType(matpair); if (ground_type == SOIL || ground_type == STONE) tt = matchTileMaterial(tt, ground_type); else - DEBUG(general).print("dig_type: getGroundType did not return SOIL or STONE." - " not updating tiletype material. {} in:{} out:{} ({},{})\n", - pos, static_cast(in_tt), static_cast(tt), - matpair.mat_type, matpair.mat_index); - if (tt == df::tiletype::Void) - DEBUG(general).print("dig_type: matchTileMaterial: tiletype is Void." - " {} in:{} out:{} ({},{})\n", - pos, static_cast(in_tt), static_cast(tt), - matpair.mat_type, matpair.mat_index); - if (tileMaterial(tt) == NONE) // true for tiletypes Void and Unused[0-9]+ - DEBUG(general).print("dig_type: matchTileMaterial: tiletype_material is NONE." - " {} in:{} out:{} ({},{})\n", + TRACE(general).print("dig_type: getGroundType did not return SOIL or STONE." + " not updating tiletype material. {} in:{} out:{} ({},{})\n", pos, static_cast(in_tt), static_cast(tt), matpair.mat_type, matpair.mat_index); } + if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) + TRACE(general).print("dig_type: derived tiletype: {} {} in:{} out:{}\n", + (tt == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE"), + pos, static_cast(in_tt), static_cast(tt)); // this segment temporarily reimplements buggy behavior to remain bug-for-bug compatible. // specifically, STONE tiles (i.e. layer stone) which are of the material type // of the current region's .lava_stone (e.g. obsidian) are changed to LAVA_STONE. // (later: unless they are in a local feature, apparently.) - if (true) { // TODO when bugfixing starts, remove this block. + // TODO when bugfixing starts, remove this block. + if (true && tileShapeBasic(tileShape(tt)) != Open) { auto matpair = baseMaterialAt(pos); auto des = index_tile(block->designation, pos); auto block_region_offset_idx = des.bits.biome; @@ -669,11 +674,9 @@ static void dig_type(const df::coord pos, df::tiletype in_tt) { tt = matchTileMaterial(tt, LAVA_STONE); } - if (tt == df::tiletype::Void) - DEBUG(general).print("dig_type: setting tile: tiletype is Void. {} in:{} out:{}\n", - pos, static_cast(in_tt), static_cast(tt)); - if (tileMaterial(tt) == NONE) // true for tiletypes Void and Unused[0-9]+ - DEBUG(general).print("dig_type: setting tile: tiletype_material is NONE. {} in:{} out:{}\n", + if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) + TRACE(general).print("dig_type: setting tile: {}. {} in:{} out:{}\n", + (tt == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE"), pos, static_cast(in_tt), static_cast(tt)); index_tile(block->tiletype, pos) = tt; } @@ -808,7 +811,7 @@ static bool is_diggable(const df::coord pos, df::tiletype tt) { case UNDERWORLD_GATE: return false; case AIR: - return true; + return false; default: break; } @@ -848,24 +851,38 @@ static bool dig_tile(color_ostream &out, { df::coord pos_below(pos.x, pos.y, pos.z-1); if (can_dig_channel(tt) && Maps::ensureTileBlock(pos_below) - && is_diggable(pos_below, *Maps::getTileType(pos_below))) { - TRACE(channels).print("dig_tile: channeling at ({}) [can_dig_channel: true]\n", pos_below); + /*&& is_diggable(pos_below, *Maps::getTileType(pos_below))*/) { + DEBUG(channels).print("dig_tile: channeling at {} [can_dig_channel: true]\n", pos_below); target_type = df::tiletype::OpenSpace; df::coord pos_above(pos.x, pos.y, pos.z+1); if (Maps::ensureTileBlock(pos_above)) remove_ramp_top(pos_above); + // TODO processing of the tile below probably should be pulled out of this case. df::tile_dig_designation td_below = (*Maps::getTileDesignation(pos_below)).bits.dig; - if (dig_tile(out, pos_below, df::tile_dig_designation::Ramp, dug_tiles)) { + if (is_diggable(pos_below, *Maps::getTileType(pos_below)) + && dig_tile(out, pos_below, df::tile_dig_designation::Ramp, dug_tiles)) { clean_ramps(pos_below); if (td_below == df::tile_dig_designation::Default) { dig_tile(out, pos_below, td_below, dug_tiles); } clean_ramps(pos); propagate_vertical_flags(pos); + if (*Maps::getTileType(pos_below) == df::tiletype::Void + || tileMaterial(*Maps::getTileType(pos_below)) == df::tiletype_shape::NONE) + TRACE(general).print("dig_tile: Channel: post-dig-pos_below: {}. pos_below {} tiletype {}\n", + *Maps::getTileType(pos_below) == df::tiletype::Void + ? "tiletype is Void" : "tiletype_material is NONE", + pos_below, static_cast(*Maps::getTileType(pos_below))); return true; } + else { + // TODO create a floor on the upper level if the lower level is WALL-basic-shaped. + // TODO also if the lower level is a construction, create a floor construction on + // the upper level with .flags.no_build_item and .flags.top_of_wall set, and + // the original_tile set to OpenSpace. + } } else { - DEBUG(channels).print("dig_tile: failed to channel at ({}) [can_dig_channel: false]\n", pos_below); + DEBUG(channels).print("dig_tile: failed to channel at {} [can_dig_channel: false]\n", pos_below); } break; } @@ -907,11 +924,9 @@ static bool dig_tile(color_ostream &out, pos.x, pos.y, pos.z, ENUM_AS_STR(designation)); } - if (target_type == df::tiletype::Void) - DEBUG(general).print("dig_tile: target_type: tiletype is Void. {} {}\n", - pos, static_cast(tt)); - if (tileMaterial(target_type) == df::tiletype_material::NONE) - DEBUG(general).print("dig_tile: target_type: tiletype_material is NONE. {} {}\n", + if (target_type == df::tiletype::Void || tileMaterial(target_type) == df::tiletype_material::NONE) + TRACE(general).print("dig_tile: target_type: {}. {} {}\n", + target_type == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE", pos, static_cast(tt)); // fail if unhandled or no change to tile @@ -919,7 +934,7 @@ static bool dig_tile(color_ostream &out, return false; dug_tiles.emplace_back(pos); - TRACE(general).print("dig_tile: digging the designation tile at ({})\n",pos); + TRACE(general).print("dig_tile: digging the designation tile at {}\n",pos); dig_type(pos, target_type); clean_ramps(pos); @@ -1413,7 +1428,6 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) { // Lua API -// runs dig-now for the specified tile coordinate. default options apply. static int dig_now_tile(lua_State *L) { df::coord pos; @@ -1449,6 +1463,38 @@ static int link_adjacent_smooth_walls(lua_State *L) return 0; } +/* LuaLS annotations. + +--- Runs dig-now for the specified tile coordinate. default options apply. +--- The tile must have a dig designation, a smooth designation, or the +--- occupancy must be set for track carving; otherwise nothing is done. +--- The designations and track-carving occupancy settings will be cleared. +--- +--- Returns false if no map is loaded or no unit is alive. +--- Otherwise, returns true whether or not any work was done. +--- +--- Note: to process large areas, it is better to invoke the `dig-now` plugin. +--- e.g. `dfhack.run_command("dig-now", "param1", "param2", ...)`. +--- +---@param x integer +---@param y integer +---@param z integer +---@return boolean +---@overload fun(pos: df.coord):boolean +function dig_now_tile(x, y, z) end + +--- re-connects smooth walls for the given tile and the orthogonally +--- adjacent tiles. +--- +---@param x integer +---@param y integer +---@param z integer +---@return nil +---@overload fun(pos: df.coord):boolean +function link_adjacent_smooth_walls(x, y, z) + +*/ + DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(dig_now_tile), DFHACK_LUA_COMMAND(link_adjacent_smooth_walls), diff --git a/plugins/lua/dig-now.lua b/plugins/lua/dig-now.lua index b3ffeb0bcc5..348c995c347 100644 --- a/plugins/lua/dig-now.lua +++ b/plugins/lua/dig-now.lua @@ -4,11 +4,21 @@ local argparse = require('argparse') local guidm = require('gui.dwarfmode') local utils = require('utils') +---@diagnostic disable-next-line: duplicate-doc-alias +---@alias coord df.coord | { x:integer, y: integer, z:integer } +---@alias boulder_percents { layer:integer, vein:integer, small_cluster:integer, deep:integer } -- dig-now.cpp boulder_percent_options_fields +---@alias opts { help:boolean, start:coord, end:coord, dump_pos:coord, boulder_percents:boulder_percents } -- dig-now.cpp dig_now_options_fields + +---@param opts opts +---@param configname 'start' | 'end' | 'dump_pos' +---@param arg 'here' | string -- "x,y,z", integers, map coordinate local function parse_coords(opts, configname, arg) - local cursor = argparse.coords(arg, configname) + local cursor = argparse.coords(arg, configname) ---@type coord utils.assign(opts[configname], cursor) end +---@param opts opts +---@param arg string -- "int,int,int,int" ranges 0..100 local function parse_percentages(opts, arg) local nums = argparse.numberList(arg, 'percentages', 4) for _,percentage in ipairs(nums) do @@ -24,12 +34,16 @@ local function parse_percentages(opts, arg) nums[1], nums[2], nums[3], nums[4] end +---@generic T -- any, non-nil +---@param ... T local function min_to_max(...) local args = {...} table.sort(args, function(a, b) return a < b end) return table.unpack(args) end +---@param opts opts +---@param ... string function parse_commandline(opts, ...) local use_zlevel = false local positionals = argparse.processArgsGetopt({...}, { From ba518dff83f9f616c993fad280d0fc47579d6067 Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 19 Jun 2026 18:11:16 -0700 Subject: [PATCH 6/7] dig-now: fix gcc warnings. --- plugins/dig-now.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 7a3d4dafba8..87509f514e4 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -84,7 +84,7 @@ static const df::world_geo_biome* geo_biome_at(const df::map_block &block, const return nullptr; if (biome->geo_index < 0) return nullptr; - if (biome->geo_index >= df::world_geo_biome::get_vector().size()) + if (static_cast(biome->geo_index) >= df::world_geo_biome::get_vector().size()) return nullptr; auto geo_biome = df::world_geo_biome::get_vector()[biome->geo_index]; return geo_biome; @@ -297,7 +297,7 @@ static const t_matpair baseMaterialAt(const df::map_block &block, df::coord2d p) &df::world_region_details::pos, region_details_coord2d); if (region_details_idx == -1) { - // "can't happen"; fall back to the region of the first map_block. + // "can't happen"; fall back to the first region in the region_details vector. DEBUG(general).print("BaseMaterialAt(block {}, tile {}, case " "LAVA_STONE, didn't find region_details for region coord {}\n", block.map_pos, p, region_details_coord2d); @@ -634,31 +634,33 @@ static void dig_type(const df::coord pos, df::tiletype in_tt) { // this bug has been temporarily preserved to remain bug-for-bug compatible. // this bug is NOT the same bug as the preserved bug 25 lines below. auto tt = in_tt; - if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) + if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) { TRACE(general).print("dig_type: pre-dig: {}. {} in:{} out:{}\n", (tt == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE"), pos, static_cast(in_tt), static_cast(tt)); - if (tileShapeBasic(tileShape(tt)) == None || tileShapeBasic(tileShape(tt)) == Open) + } + if (tileShapeBasic(tileShape(tt)) == None || tileShapeBasic(tileShape(tt)) == Open) { TRACE(general).print("dig_type: pre-dig: tiletype_shape_basic is {}. {} in:{} out:{}\n", (tileShapeBasic(tileShape(tt)) == None ? "None" : "Open"), pos, static_cast(in_tt), static_cast(tt)); - + } if (tileShapeBasic(tileShape(tt)) != Open) { auto matpair = baseMaterialAt(pos); auto ground_type = getGroundType(matpair); if (ground_type == SOIL || ground_type == STONE) tt = matchTileMaterial(tt, ground_type); - else + else { TRACE(general).print("dig_type: getGroundType did not return SOIL or STONE." " not updating tiletype material. {} in:{} out:{} ({},{})\n", pos, static_cast(in_tt), static_cast(tt), matpair.mat_type, matpair.mat_index); + } } - if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) + if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) { TRACE(general).print("dig_type: derived tiletype: {} {} in:{} out:{}\n", (tt == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE"), pos, static_cast(in_tt), static_cast(tt)); - + } // this segment temporarily reimplements buggy behavior to remain bug-for-bug compatible. // specifically, STONE tiles (i.e. layer stone) which are of the material type // of the current region's .lava_stone (e.g. obsidian) are changed to LAVA_STONE. @@ -674,10 +676,11 @@ static void dig_type(const df::coord pos, df::tiletype in_tt) { tt = matchTileMaterial(tt, LAVA_STONE); } - if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) + if (tt == df::tiletype::Void || tileMaterial(tt) == NONE) { TRACE(general).print("dig_type: setting tile: {}. {} in:{} out:{}\n", (tt == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE"), pos, static_cast(in_tt), static_cast(tt)); + } index_tile(block->tiletype, pos) = tt; } @@ -1107,7 +1110,7 @@ static bool produces_item(const boulder_percent_options &options, return rng.random(100) < probability; } -typedef std::map, std::vector> +typedef std::map, std::vector > item_coords_t; static void do_dig(color_ostream &out, std::vector &dug_coords, From f335d4a177cd93ece18b2c9a0f89eff39bb3c9f8 Mon Sep 17 00:00:00 2001 From: SilasD Date: Fri, 19 Jun 2026 18:33:01 -0700 Subject: [PATCH 7/7] dig-now: resolve more gcc warnings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit turns out I'm not allowed to have unused functions. so much for forward planning. also, apparently 0 is signed! who would have known? In file included from /home/runner/work/dfhack/dfhack/library/include/Error.h:31, from /home/runner/work/dfhack/dfhack/library/include/BitArray.h:26, from /home/runner/work/dfhack/dfhack/library/include/DataDefs.h:37, from /home/runner/work/dfhack/dfhack/library/include/DataIdentity.h:38, from /home/runner/work/dfhack/dfhack/library/include/DataFuncs.h:30, from /home/runner/work/dfhack/dfhack/plugins/dig-now.cpp:5: /home/runner/work/dfhack/dfhack/library/include/MiscUtils.h: In instantiation of ‘T clip_range(T, T1, T2) [with T = long unsigned int; T1 = int; T2 = long unsigned int]’: /home/runner/work/dfhack/dfhack/plugins/dig-now.cpp:108:23: required from here /home/runner/work/dfhack/dfhack/library/include/MiscUtils.h:553:11: error: comparison of integer expressions of different signedness: ‘long unsigned int’ and ‘int’ [-Werror=sign-compare] 553 | if (a < minv) return minv; | ~~^~~~~~ --- plugins/dig-now.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 87509f514e4..a715bbd3c81 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -68,6 +68,7 @@ static const df::region_map_entry* biome_at(const df::map_block &block, const df return biome; } +#if false // gcc warns about unused functions, and warnings are errors. static const df::region_map_entry* biome_at(const df::coord pos) { auto block = Maps::getTileBlock(pos); @@ -76,6 +77,7 @@ static const df::region_map_entry* biome_at(const df::coord pos) auto blockref = static_cast(*block); return biome_at(blockref, df::coord2d(pos) & 15); } +#endif static const df::world_geo_biome* geo_biome_at(const df::map_block &block, const df::coord2d p) { @@ -90,6 +92,7 @@ static const df::world_geo_biome* geo_biome_at(const df::map_block &block, const return geo_biome; } +#if false // gcc warns about unused functions, and warnings are errors. static const df::world_geo_biome* geo_biome_at(const df::coord pos) { auto block = Maps::getTileBlock(pos); @@ -98,6 +101,7 @@ static const df::world_geo_biome* geo_biome_at(const df::coord pos) auto blockref = static_cast(*block); return geo_biome_at(blockref, df::coord2d(pos) & 15); } +#endif static const t_matpair layer_inorganic_n(const df::map_block &block, const df::coord2d p, size_t layer) { @@ -105,10 +109,12 @@ static const t_matpair layer_inorganic_n(const df::map_block &block, const df::c auto geo_biome = geo_biome_at(block, p); if (!geo_biome) return t_matpair(INORGANIC, -1); // can't happen, generic "rock" - layer = clip_range(layer, 0, geo_biome->layers.size() - 1); + // gcc-11 complained that 0 was of different signed-ness than size_t! trying to cast it to unsigned. + layer = clip_range(layer, static_cast(0), geo_biome->layers.size() - 1); return t_matpair(INORGANIC, geo_biome->layers[layer]->mat_index); } +#if false // gcc warns about unused functions, and warnings are errors. static const t_matpair layer_inorganic_n(const df::coord pos, size_t layer) { using namespace df::enums::builtin_mats; @@ -118,6 +124,7 @@ static const t_matpair layer_inorganic_n(const df::coord pos, size_t layer) auto blockref = static_cast(*block); return layer_inorganic_n(blockref, df::coord2d(pos) & 15, layer); } +#endif static const size_t geolayer_at(const df::map_block &block, df::coord2d p) { @@ -129,6 +136,7 @@ static const t_matpair layer_inorganic_at(const df::map_block &block, df::coord2 return layer_inorganic_n(block, p, geolayer_at(block, p)); } +#if false // gcc warns about unused functions, and warnings are errors. static const t_matpair layer_inorganic_at(const df::coord pos) { using namespace df::enums::builtin_mats; @@ -138,6 +146,7 @@ static const t_matpair layer_inorganic_at(const df::coord pos) auto blockref = const_cast(static_cast(*block)); return layer_inorganic_at(blockref, df::coord2d(pos) & 15); } +#endif static const df::enums::tiletype_material::tiletype_material getGroundType(int32_t mat_index) { @@ -871,11 +880,12 @@ static bool dig_tile(color_ostream &out, clean_ramps(pos); propagate_vertical_flags(pos); if (*Maps::getTileType(pos_below) == df::tiletype::Void - || tileMaterial(*Maps::getTileType(pos_below)) == df::tiletype_shape::NONE) + || tileMaterial(*Maps::getTileType(pos_below)) == df::tiletype_material::NONE) { TRACE(general).print("dig_tile: Channel: post-dig-pos_below: {}. pos_below {} tiletype {}\n", *Maps::getTileType(pos_below) == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE", pos_below, static_cast(*Maps::getTileType(pos_below))); + } return true; } else { @@ -927,11 +937,11 @@ static bool dig_tile(color_ostream &out, pos.x, pos.y, pos.z, ENUM_AS_STR(designation)); } - if (target_type == df::tiletype::Void || tileMaterial(target_type) == df::tiletype_material::NONE) + if (target_type == df::tiletype::Void || tileMaterial(target_type) == df::tiletype_material::NONE) { TRACE(general).print("dig_tile: target_type: {}. {} {}\n", target_type == df::tiletype::Void ? "tiletype is Void" : "tiletype_material is NONE", pos, static_cast(tt)); - + } // fail if unhandled or no change to tile if (target_type == df::tiletype::Void || target_type == tt) return false;