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.
develop
Alexander Gavrilov 2013-10-05 20:07:21 +04:00
parent 68b6e10b2c
commit f36041f6bd
6 changed files with 556 additions and 28 deletions

@ -26,8 +26,250 @@ distribution.
#include "TileTypes.h"
#include "Export.h"
#include <map>
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<df::tiletype_variant, df::tiletype> T_VariantMap;
typedef std::map<std::string, T_VariantMap> T_DirectionMap;
typedef std::map<df::tiletype_special, T_DirectionMap> T_SpecialMap;
typedef std::map<df::tiletype_shape, T_SpecialMap> 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<df::tiletype>::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<df::tiletype>::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;

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

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

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

@ -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<df::block_square_event_frozen_liquidst>(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

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