From f36041f6bd0ae697a92ca740fcfdc1b0421ea8f4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 5 Oct 2013 20:07:21 +0400 Subject: [PATCH] Implement a MapCache api for writing base layer tiles of arbitrary stone. Precompute some tables in TileTypes for extremely efficient conversion between tiles of most important material types. --- library/TileTypes.cpp | 242 ++++++++++++++++++++++++++++ library/include/TileTypes.h | 60 ++++++- library/include/modules/MapCache.h | 37 ++++- library/include/modules/Materials.h | 3 + library/modules/MapCache.cpp | 223 ++++++++++++++++++++++--- library/modules/Materials.cpp | 19 +++ 6 files changed, 556 insertions(+), 28 deletions(-) diff --git a/library/TileTypes.cpp b/library/TileTypes.cpp index 804d5e772..bd2c52a11 100644 --- a/library/TileTypes.cpp +++ b/library/TileTypes.cpp @@ -26,8 +26,250 @@ distribution. #include "TileTypes.h" #include "Export.h" +#include + +using namespace DFHack; + +const int NUM_TILETYPES = 1+(int)ENUM_LAST_ITEM(tiletype); +const int NUM_MATERIALS = 1+(int)ENUM_LAST_ITEM(tiletype_material); +const int NUM_CVTABLES = 1+(int)tiletype_material::CONSTRUCTION; + +typedef std::map T_VariantMap; +typedef std::map T_DirectionMap; +typedef std::map T_SpecialMap; +typedef std::map T_ShapeMap; +typedef T_ShapeMap T_MaterialMap[NUM_MATERIALS]; + +static bool tables_ready = false; +static T_MaterialMap tile_table; +static df::tiletype tile_to_mat[NUM_CVTABLES][NUM_TILETYPES]; + +static df::tiletype find_match( + df::tiletype_material mat, df::tiletype_shape shape, df::tiletype_special special, + std::string dir, df::tiletype_variant variant, bool warn +) { + using namespace df::enums::tiletype_shape; + using namespace df::enums::tiletype_special; + + if (mat < 0 || mat >= NUM_MATERIALS) + return tiletype::Void; + + auto &sh_map = tile_table[mat]; + + if (!sh_map.count(shape)) + { + if (warn) + { + fprintf( + stderr, "NOTE: No shape %s in %s.\n", + enum_item_key(shape).c_str(), enum_item_key(mat).c_str() + ); + } + + switch (shape) + { + case BROOK_BED: + if (sh_map.count(FORTIFICATION)) { shape = FORTIFICATION; break; } + + case FORTIFICATION: + if (sh_map.count(WALL)) { shape = WALL; break; } + return tiletype::Void; + + case BROOK_TOP: + case BOULDER: + case PEBBLES: + if (sh_map.count(FLOOR)) { shape = FLOOR; break; } + return tiletype::Void; + + default: + return tiletype::Void; + }; + } + + auto &sp_map = sh_map[shape]; + + if (!sp_map.count(special)) + { + if (warn) + { + fprintf( + stderr, "NOTE: No special %s in %s:%s.\n", + enum_item_key(special).c_str(), enum_item_key(mat).c_str(), + enum_item_key(shape).c_str() + ); + } + + switch (special) + { + case TRACK: + if (sp_map.count(SMOOTH)) { + special = SMOOTH; break; + } + + case df::enums::tiletype_special::NONE: + case NORMAL: + case SMOOTH: + case WET: + case FURROWED: + case RIVER_SOURCE: + case WATERFALL: + case WORN_1: + case WORN_2: + case WORN_3: + if (sp_map.count(NORMAL)) { + special = NORMAL; break; + } + if (sp_map.count(df::enums::tiletype_special::NONE)) { + special = df::enums::tiletype_special::NONE; break; + } + // For targeting construction + if (sp_map.count(SMOOTH)) { + special = SMOOTH; break; + } + + default: + return tiletype::Void; + } + } + + auto &dir_map = sp_map[special]; + + if (!dir_map.count(dir)) + { + if (warn) + { + fprintf( + stderr, "NOTE: No direction '%s' in %s:%s:%s.\n", + dir.c_str(), enum_item_key(mat).c_str(), + enum_item_key(shape).c_str(), enum_item_key(special).c_str() + ); + } + + if (dir_map.count("--------")) + dir = "--------"; + else if (dir_map.count("NSEW")) + dir = "NSEW"; + else if (dir_map.count("N-S-W-E-")) + dir = "N-S-W-E-"; + else + dir = dir_map.begin()->first; + } + + auto &var_map = dir_map[dir]; + + if (!var_map.count(variant)) + { + if (warn) + { + fprintf( + stderr, "NOTE: No variant '%s' in %s:%s:%s:%s.\n", + enum_item_key(variant).c_str(), enum_item_key(mat).c_str(), + enum_item_key(shape).c_str(), enum_item_key(special).c_str(), dir.c_str() + ); + } + + variant = var_map.begin()->first; + } + + return var_map[variant]; +} + +static void init_tables() +{ + tables_ready = true; + memset(tile_to_mat, 0, sizeof(tile_to_mat)); + + // Index tile types + FOR_ENUM_ITEMS(tiletype, tt) + { + auto &attrs = df::enum_traits::attrs(tt); + if (attrs.material < 0) + continue; + + tile_table[attrs.material][attrs.shape][attrs.special][attrs.direction][attrs.variant] = tt; + + if (isCoreMaterial(attrs.material)) + { + assert(attrs.material < NUM_CVTABLES); + tile_to_mat[attrs.material][tt] = tt; + } + } + + // Build mapping of everything to STONE and back + FOR_ENUM_ITEMS(tiletype, tt) + { + auto &attrs = df::enum_traits::attrs(tt); + if (!isCoreMaterial(attrs.material)) + continue; + + if (attrs.material != tiletype_material::STONE) + { + df::tiletype ttm = find_match( + tiletype_material::STONE, + attrs.shape, attrs.special, attrs.direction, attrs.variant, + isStoneMaterial(attrs.material) + ); + + tile_to_mat[tiletype_material::STONE][tt] = ttm; + + if (ttm == tiletype::Void) + fprintf(stderr, "No match for tile %s in STONE.\n", + enum_item_key(tt).c_str()); + } + else + { + FOR_ENUM_ITEMS(tiletype_material, mat) + { + if (!isCoreMaterial(mat) || mat == attrs.material) + continue; + + df::tiletype ttm = find_match( + mat, + attrs.shape, attrs.special, attrs.direction, attrs.variant, + isStoneMaterial(mat) + ); + + tile_to_mat[mat][tt] = ttm; + + if (ttm == tiletype::Void) + fprintf(stderr, "No match for tile %s in %s.\n", + enum_item_key(tt).c_str(), enum_item_key(mat).c_str()); + } + } + } + + // Transitive closure via STONE + FOR_ENUM_ITEMS(tiletype_material, mat) + { + if (!isCoreMaterial(mat) || mat == tiletype_material::STONE) + continue; + + FOR_ENUM_ITEMS(tiletype, tt) + { + if (!tt || tile_to_mat[mat][tt]) + continue; + + auto stone = tile_to_mat[tiletype_material::STONE][tt]; + if (stone) + tile_to_mat[mat][tt] = tile_to_mat[mat][stone]; + } + } +} + +df::tiletype DFHack::matchTileMaterial(df::tiletype source, df::tiletype_material tmat) +{ + if (!isCoreMaterial(tmat) || !source || source >= NUM_TILETYPES) + return tiletype::Void; + + if (!tables_ready) + init_tables(); + + return tile_to_mat[tmat][source]; +} + namespace DFHack { + df::tiletype findSimilarTileType (const df::tiletype sourceTileType, const df::tiletype_shape tshape) { df::tiletype match = tiletype::Void; diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index f46cf2f2c..1ac58f221 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -119,10 +119,10 @@ namespace DFHack inline char * getStr() const { static char str[16]; - //type punning trick - *( (uint64_t *)str ) = *( (uint64_t *)"--------" ); + str[8]=0; #define DIRECTION(x,i,c) \ + str[i] = str[i+1] = '-'; \ if(x){ \ str[i]=c; \ if(1==x) ; \ @@ -183,6 +183,56 @@ namespace DFHack return TileDirection(ENUM_ATTR(tiletype, direction, tiletype)); } + // Air + inline bool isAirMaterial(df::tiletype_material mat) { return mat == tiletype_material::AIR; } + inline bool isAirMaterial(df::tiletype tt) { return isAirMaterial(tileMaterial(tt)); } + + // Soil + inline bool isSoilMaterial(df::tiletype_material mat) { return mat == tiletype_material::SOIL; } + inline bool isSoilMaterial(df::tiletype tt) { return isSoilMaterial(tileMaterial(tt)); } + + // Stone materials - their tiles are completely interchangable + inline bool isStoneMaterial(df::tiletype_material mat) + { + using namespace df::enums::tiletype_material; + switch (mat) { + case STONE: case LAVA_STONE: case MINERAL: case FEATURE: + return true; + default: + return false; + } + } + inline bool isStoneMaterial(df::tiletype tt) { return isStoneMaterial(tileMaterial(tt)); } + + // Regular ground materials = stone + soil + inline bool isGroundMaterial(df::tiletype_material mat) + { + using namespace df::enums::tiletype_material; + switch (mat) { + case SOIL: + case STONE: case LAVA_STONE: case MINERAL: case FEATURE: + return true; + default: + return false; + } + } + inline bool isGroundMaterial(df::tiletype tt) { return isGroundMaterial(tileMaterial(tt)); } + + // Core materials - their tile sets are sufficiently close to stone + inline bool isCoreMaterial(df::tiletype_material mat) + { + using namespace df::enums::tiletype_material; + switch (mat) { + case SOIL: + case STONE: case LAVA_STONE: case MINERAL: case FEATURE: + case FROZEN_LIQUID: case CONSTRUCTION: + return true; + default: + return false; + } + } + inline bool isCoreMaterial(df::tiletype tt) { return isCoreMaterial(tileMaterial(tt)); } + // tile is missing a floor inline bool LowPassable(df::tiletype tiletype) @@ -295,5 +345,11 @@ namespace DFHack * If there are no variants, returns the same tile */ DFHACK_EXPORT df::tiletype findRandomVariant(const df::tiletype tile); + + /** + * Map a tile type to a different core material (see above for the list). + * Returns Void (0) in case of failure. + */ + DFHACK_EXPORT df::tiletype matchTileMaterial(df::tiletype source, df::tiletype_material tmat); } diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index faf9fe32f..68aa28044 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -64,6 +64,7 @@ struct BiomeInfo { }; typedef uint8_t t_veintype[16][16]; +typedef df::tiletype t_tilearr[16][16]; class BlockInfo { @@ -174,7 +175,7 @@ public: * Use -1 to clear the tile from all veins. Does not update tile types. * Returns false in case of some error, e.g. non-stone mat. */ - bool setVeinMaterialAt(df::coord2d p, int16_t mat, df::inclusion_type type = df::enums::inclusion_type::VEIN); + bool setVeinMaterialAt(df::coord2d p, int16_t mat, df::inclusion_type type = df::enums::inclusion_type::CLUSTER); /// Geological layer soil or stone material at pos int16_t layerMaterialAt(df::coord2d p) { @@ -184,6 +185,21 @@ public: /// Biome-specific lava stone at pos int16_t lavaStoneAt(df::coord2d p) { return biomeInfoAt(p).lava_stone; } + /** + * Sets the stone tile and material at specified position, automatically + * choosing between layer, lava or vein stone. + * The force_type flags ensures the correct inclusion type, even forcing + * a vein format if necessary. If kill_veins is true and the chosen mode + * isn't vein, it will clear any old veins from the tile. + */ + bool setStoneAt(df::coord2d p, df::tiletype tile, int16_t mat, df::inclusion_type type = df::enums::inclusion_type::CLUSTER, bool force_type = false, bool kill_veins = false); + + /** + * Sets the tile at the position to SOIL material. The actual material + * is completely determined by geological layers and cannot be set. + */ + bool setSoilAt(df::coord2d p, df::tiletype tile, bool kill_veins = false); + /// Static layer tile (i.e. base + constructions) df::tiletype staticTiletypeAt(df::coord2d p) { @@ -334,26 +350,33 @@ private: bool addItemOnGround(df::item *item); bool removeItemOnGround(df::item *item); + struct IceInfo { + df::tile_bitmask frozen; + df::tile_bitmask dirty; + }; struct ConInfo { df::tile_bitmask constructed; - df::tiletype tiles[16][16]; + df::tile_bitmask dirty; + t_tilearr tiles; t_blockmaterials mat_type; t_blockmaterials mat_index; }; struct TileInfo { - df::tile_bitmask frozen; df::tile_bitmask dirty_raw; - df::tiletype raw_tiles[16][16]; + t_tilearr raw_tiles; + IceInfo *ice_info; ConInfo *con_info; - df::tile_bitmask dirty_base; - df::tiletype base_tiles[16][16]; + t_tilearr base_tiles; TileInfo(); ~TileInfo(); + void init_iceinfo(); void init_coninfo(); + + void set_base_tile(df::coord2d pos, df::tiletype tile); }; struct BasematInfo { t_blockmaterials mat_type; @@ -364,6 +387,8 @@ private: t_blockmaterials veinmat; BasematInfo(); + + void set_base_mat(TileInfo *tiles, df::coord2d pos, int16_t type, int16_t idx); }; TileInfo *tiles; BasematInfo *basemats; diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 86814898f..b326719c6 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -169,6 +169,9 @@ namespace DFHack return a.type != b.type || a.index != b.index; } + DFHACK_EXPORT bool isSoilInorganic(int material); + DFHACK_EXPORT bool isStoneInorganic(int material); + typedef int32_t t_materialIndex; typedef int16_t t_materialType, t_itemType, t_itemSubtype; diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index f0744fcbb..07f0d45e2 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -44,6 +44,7 @@ using namespace std; #include "MiscUtils.h" #include "modules/Buildings.h" +#include "modules/Materials.h" #include "DataDefs.h" #include "df/world_data.h" @@ -178,19 +179,27 @@ void MapExtras::Block::init_tiles(bool basemat) MapExtras::Block::TileInfo::TileInfo() { - frozen.clear(); dirty_raw.clear(); memset(raw_tiles,0,sizeof(raw_tiles)); + ice_info = NULL; con_info = NULL; - dirty_base.clear(); memset(base_tiles,0,sizeof(base_tiles)); } MapExtras::Block::TileInfo::~TileInfo() { + delete ice_info; delete con_info; } +void MapExtras::Block::TileInfo::init_iceinfo() +{ + if (ice_info) + return; + + ice_info = new IceInfo(); +} + void MapExtras::Block::TileInfo::init_coninfo() { if (con_info) @@ -242,7 +251,7 @@ bool MapExtras::Block::setVeinMaterialAt(df::coord2d pos, int16_t mat, df::inclu auto &cur_mat = basemats->veinmat[pos.x][pos.y]; auto &cur_type = basemats->veintype[pos.x][pos.y]; - if (cur_mat == mat && cur_type == type) + if (cur_mat == mat && (mat < 0 || cur_type == type)) return true; if (mat >= 0) @@ -252,11 +261,7 @@ bool MapExtras::Block::setVeinMaterialAt(df::coord2d pos, int16_t mat, df::inclu return false; // Bad material? - auto raw = df::inorganic_raw::find(mat); - if (!raw || - raw->flags.is_set(inorganic_flags::SOIL_ANY) || - raw->material.flags.is_set(material_flags::IS_METAL) || - raw->material.flags.is_set(material_flags::NO_STONE_STOCKPILE)) + if (!isStoneInorganic(mat)) return false; } @@ -266,7 +271,97 @@ bool MapExtras::Block::setVeinMaterialAt(df::coord2d pos, int16_t mat, df::inclu basemats->vein_dirty.setassignment(pos, true); if (tileMaterial(tiles->base_tiles[pos.x][pos.y]) == MINERAL) - basemats->mat_index[pos.x][pos.y] = mat; + basemats->set_base_mat(tiles, pos, 0, mat); + + return true; +} + +bool MapExtras::Block::setStoneAt(df::coord2d pos, df::tiletype tile, int16_t mat, df::inclusion_type type, bool force_vein, bool kill_veins) +{ + using namespace df::enums::tiletype_material; + + if (!block) + return false; + + if (!isStoneInorganic(mat) || !isCoreMaterial(tile)) + return false; + + if (!basemats) + init_tiles(true); + + // Check if anything needs to be done + pos = pos & 15; + auto &cur_tile = tiles->base_tiles[pos.x][pos.y]; + auto &cur_mattype = basemats->mat_type[pos.x][pos.y]; + auto &cur_matidx = basemats->mat_index[pos.x][pos.y]; + + if (!force_vein && cur_tile == tile && cur_mattype == 0 && cur_matidx == mat) + return true; + + bool vein = false; + + if (force_vein && type != inclusion_type::CLUSTER) + vein = true; + else if (mat == lavaStoneAt(pos)) + tile = matchTileMaterial(tile, LAVA_STONE); + else if (mat == layerMaterialAt(pos)) + tile = matchTileMaterial(tile, STONE); + else + vein = true; + + if (vein) + tile = matchTileMaterial(tile, MINERAL); + + if (tile == tiletype::Void) + return false; + if ((vein || kill_veins) && !setVeinMaterialAt(pos, vein ? mat : -1, type)) + return false; + + if (cur_tile != tile) + { + dirty_tiles = true; + tiles->set_base_tile(pos, tile); + } + + basemats->set_base_mat(tiles, pos, 0, mat); + + return true; +} + +bool MapExtras::Block::setSoilAt(df::coord2d pos, df::tiletype tile, bool kill_veins) +{ + using namespace df::enums::tiletype_material; + + if (!block) + return false; + + if (!isCoreMaterial(tile)) + return false; + + if (!basemats) + init_tiles(true); + + pos = pos & 15; + auto &cur_tile = tiles->base_tiles[pos.x][pos.y]; + + tile = matchTileMaterial(tile, SOIL); + if (tile == tiletype::Void) + return false; + + if (kill_veins && !setVeinMaterialAt(pos, -1)) + return false; + + if (cur_tile != tile) + { + dirty_tiles = true; + tiles->set_base_tile(pos, tile); + } + + int mat = layerMaterialAt(pos); + if (BlockInfo::getGroundType(mat) == BlockInfo::G_STONE) + mat = biomeInfoAt(pos).default_soil; + + basemats->set_base_mat(tiles, pos, 0, mat); return true; } @@ -290,7 +385,9 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles) // Frozen liquid comes topmost if (tileMaterial(tt) == FROZEN_LIQUID) { - tiles->frozen.setassignment(x,y,true); + tiles->init_iceinfo(); + + tiles->ice_info->frozen.setassignment(x,y,true); if (icetiles[x][y] != tiletype::Void) { tt = icetiles[x][y]; @@ -328,11 +425,92 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles) } } +void MapExtras::Block::TileInfo::set_base_tile(df::coord2d pos, df::tiletype tile) +{ + base_tiles[pos.x][pos.y] = tile; + + if (con_info) + { + if (con_info->constructed.getassignment(pos)) + { + con_info->dirty.setassignment(pos, true); + return; + } + + con_info->tiles[pos.x][pos.y] = tile; + } + + if (ice_info && ice_info->frozen.getassignment(pos)) + { + ice_info->dirty.setassignment(pos, true); + return; + } + + dirty_raw.setassignment(pos, true); + raw_tiles[pos.x][pos.y] = tile; +} + void MapExtras::Block::WriteTiles(TileInfo *tiles) { - for (int x = 0; x < 16; x++) + if (tiles->con_info) { for (int y = 0; y < 16; y++) + { + if (!tiles->con_info->dirty[y]) + continue; + + for (int x = 0; x < 16; x++) + { + if (!tiles->con_info->dirty.getassignment(x,y)) + continue; + + df::coord coord = block->map_pos + df::coord(x,y,0); + df::construction *con = df::construction::find(coord); + if (con) + con->original_tile = tiles->base_tiles[x][y]; + } + } + + tiles->con_info->dirty.clear(); + } + + if (tiles->ice_info && tiles->ice_info->dirty.has_assignments()) + { + df::tiletype (*newtiles)[16] = (tiles->con_info ? tiles->con_info->tiles : tiles->base_tiles); + + for (int i = block->block_events.size()-1; i >= 0; i--) + { + auto event = block->block_events[i]; + auto ice = strict_virtual_cast(event); + if (!ice) + continue; + + for (int y = 0; y < 16; y++) + { + if (!tiles->ice_info->dirty[y]) + continue; + + for (int x = 0; x < 16; x++) + { + if (!tiles->ice_info->dirty.getassignment(x,y)) + continue; + if (ice->tiles[x][y] == tiletype::Void) + continue; + + ice->tiles[x][y] = newtiles[x][y]; + } + } + } + + tiles->ice_info->dirty.clear(); + } + + for (int y = 0; y < 16; y++) + { + if (!tiles->dirty_raw[y]) + continue; + + for (int x = 0; x < 16; x++) { if (tiles->dirty_raw.getassignment(x,y)) block->tiletype[x][y] = tiles->raw_tiles[x][y]; @@ -358,19 +536,24 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats) auto tt = tiles->base_tiles[x][y]; auto mat = info.getBaseMaterial(tt, df::coord2d(x,y)); - bmats->mat_type[x][y] = mat.mat_type; - bmats->mat_index[x][y] = mat.mat_index; - - // Copy base info back to construction layer - if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y)) - { - tiles->con_info->mat_type[x][y] = mat.mat_type; - tiles->con_info->mat_index[x][y] = mat.mat_index; - } + bmats->set_base_mat(tiles, df::coord2d(x,y), mat.mat_type, mat.mat_index); } } } +void MapExtras::Block::BasematInfo::set_base_mat(TileInfo *tiles, df::coord2d pos, int16_t type, int16_t idx) +{ + mat_type[pos.x][pos.y] = type; + mat_index[pos.x][pos.y] = idx; + + // Copy base info back to construction layer + if (tiles->con_info && !tiles->con_info->constructed.getassignment(pos)) + { + tiles->con_info->mat_type[pos.x][pos.y] = type; + tiles->con_info->mat_index[pos.x][pos.y] = idx; + } +} + void MapExtras::Block::WriteVeins(TileInfo *tiles, BasematInfo *bmats) { // Classify modified tiles into distinct buckets diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 17823f5f1..5bda6a9bc 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -561,6 +561,25 @@ bool DFHack::parseJobMaterialCategory(df::dfhack_material_category *cat, const s return true; } +bool DFHack::isSoilInorganic(int material) +{ + auto raw = df::inorganic_raw::find(material); + + return raw && raw->flags.is_set(inorganic_flags::SOIL_ANY); +} + +bool DFHack::isStoneInorganic(int material) +{ + auto raw = df::inorganic_raw::find(material); + + if (!raw || + raw->flags.is_set(inorganic_flags::SOIL_ANY) || + raw->material.flags.is_set(material_flags::IS_METAL)) + return false; + + return true; +} + Module* DFHack::createMaterials() { return new Materials();