Implement unconstructed building instance creation and linking into world.

For more flexibility, the base api is split into 3 phases:
alloc, setSize, and construct. No support for non-actual
buildings like stockpiles and activity zones at the moment.
develop
Alexander Gavrilov 2012-04-29 21:07:39 +04:00
parent 9c94b7c1e7
commit 2303a25bde
13 changed files with 764 additions and 17 deletions

@ -232,15 +232,26 @@ void virtual_identity::doInit(Core *core)
known[vtable_ptr] = this;
}
virtual_identity *virtual_identity::find(const std::string &name)
{
auto name_it = name_lookup.find(name);
return (name_it != name_lookup.end()) ? name_it->second : NULL;
}
virtual_identity *virtual_identity::get(virtual_ptr instance_ptr)
{
if (!instance_ptr) return NULL;
return find(get_vtable(instance_ptr));
}
virtual_identity *virtual_identity::find(void *vtable)
{
// Actually, a reader/writer lock would be sufficient,
// since the table is only written once per class.
tthread::lock_guard<tthread::mutex> lock(*known_mutex);
void *vtable = get_vtable(instance_ptr);
std::map<void*, virtual_identity*>::iterator it = known.find(vtable);
if (it != known.end())

@ -47,6 +47,7 @@ distribution.
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/Burrows.h"
#include "modules/Buildings.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
@ -762,6 +763,40 @@ static const luaL_Reg dfhack_burrows_funcs[] = {
{ NULL, NULL }
};
/***** Buildings module *****/
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles),
WRAPM(Buildings, setSize),
WRAPM(Buildings, constructWithItems),
WRAPM(Buildings, constructWithFilters),
{ NULL, NULL }
};
static int buildings_getCorrectSize(lua_State *state)
{
int w = luaL_optint(state, 1, 1);
int h = luaL_optint(state, 2, 1);
int t = luaL_optint(state, 3, -1);
int st = luaL_optint(state, 4, -1);
int cu = luaL_optint(state, 5, -1);
int d = luaL_optint(state, 6, 0);
df::coord2d size(w,h);
df::coord2d center;
bool flexible = Buildings::getCorrectSize(size, center, df::building_type(t), st, cu, d);
lua_pushboolean(state, flexible);
lua_pushinteger(state, size.x);
lua_pushinteger(state, size.y);
lua_pushinteger(state, center.x);
lua_pushinteger(state, center.y);
return 5;
}
static const luaL_Reg dfhack_buildings_funcs[] = {
{ "getCorrectSize", buildings_getCorrectSize },
{ NULL, NULL }
};
/************************
* Main Open function *
@ -779,4 +814,5 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs);
OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs);
OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs);
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
}

@ -1029,6 +1029,11 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (std::exception &e) {
std::string tmp = stl_sprintf("C++ exception: %s", e.what());
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");

@ -41,7 +41,11 @@ distribution.
#include <map>
const char *DFHack::Error::NullPointer::what() const throw() {
return "NULL pointer access";
return "DFHack::Error::NullPointer";
}
const char *DFHack::Error::InvalidArgument::what() const throw() {
return "DFHack::Error::InvalidArgument";
}
std::string stl_sprintf(const char *fmt, ...) {

@ -318,6 +318,9 @@ namespace DFHack
public:
static virtual_identity *get(virtual_ptr instance_ptr);
static virtual_identity *find(void *vtable);
static virtual_identity *find(const std::string &name);
bool is_instance(virtual_ptr instance_ptr) {
if (!instance_ptr) return false;
if (vtable_ptr) {

@ -51,6 +51,18 @@ namespace DFHack
#define CHECK_NULL_POINTER(var) \
{ if (var == NULL) throw DFHack::Error::NullPointer(#var); }
class DFHACK_EXPORT InvalidArgument : public All {
const char *expr_;
public:
InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {}
const char *expr() const { return expr_; }
virtual const char *what() const throw();
};
#define CHECK_INVALID_ARGUMENT(expr) \
{ if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); }
class DFHACK_EXPORT AllSymbols : public All{};
// Syntax errors and whatnot, the xml can't be read
class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols

@ -34,6 +34,13 @@ distribution.
#include "df/siegeengine_type.h"
#include "df/trap_type.h"
namespace df
{
struct job_item;
struct item;
struct building_extents;
}
namespace DFHack
{
namespace Buildings
@ -84,5 +91,43 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes);
/**
* Allocates a building object using this type and position.
*/
DFHACK_EXPORT df::building *allocInstance(df::coord pos, df::building_type type, int subtype = -1, int custom = -1);
/**
* Sets size and center to the correct dimensions of the building.
* If part of the original size value was used, returns true.
*/
DFHACK_EXPORT bool getCorrectSize(df::coord2d &size, df::coord2d &center,
df::building_type type, int subtype = -1, int custom = -1,
int direction = 0);
/**
* Checks if the tiles are free to be built upon.
*/
DFHACK_EXPORT bool checkFreeTiles(df::coord pos, df::coord2d size,
df::building_extents *ext = NULL, bool create_ext = false);
/**
* Sets the size of the building, using size and direction as guidance.
* Returns true if the building can be created at its position, using that size.
*/
DFHACK_EXPORT bool setSize(df::building *bld, df::coord2d size, int direction = 0);
/**
* Initiates construction of the building, using specified items as inputs.
* Returns true if success.
*/
DFHACK_EXPORT bool constructWithItems(df::building *bld, std::vector<df::item*> items);
/**
* Initiates construction of the building, using specified item filters.
* Assumes immediate ownership of the item objects, and deletes them in case of error.
* Returns true if success.
*/
DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector<df::job_item*> items);
}
}

@ -30,6 +30,9 @@ distribution.
#include "Module.h"
#include <ostream>
#include "DataDefs.h"
#include "df/job_item_ref.h"
namespace df
{
struct job;
@ -58,6 +61,10 @@ namespace DFHack
// lists jobs with ids >= *id_var, and sets *id_var = *job_next_id;
DFHACK_EXPORT bool listNewlyCreated(std::vector<df::job*> *pvec, int *id_var);
DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item,
df::job_item_ref::T_role role,
int filter_idx = -1, int insert_idx = -1);
}
DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b);

@ -199,6 +199,25 @@ function dfhack.interpreter(prompt,hfile,env)
local t_prompt = nil
local vcnt = 1
local pfix_handlers = {
['!'] = function(data)
print(table.unpack(data,2,data.n))
end,
['~'] = function(data)
print(table.unpack(data,2,data.n))
printall(data[2])
end,
['='] = function(data)
for i=2,data.n do
local varname = '_'..vcnt
prompt_env[varname] = data[i]
dfhack.print(varname..' = ')
safecall(print, data[i])
vcnt = vcnt + 1
end
end
}
setmetatable(prompt_env, { __index = env or _G })
while true do
@ -209,7 +228,7 @@ function dfhack.interpreter(prompt,hfile,env)
elseif cmdline ~= '' then
local pfix = string.sub(cmdline,1,1)
if not t_prompt and (pfix == '!' or pfix == '=') then
if not t_prompt and pfix_handlers[pfix] then
cmdline = 'return '..string.sub(cmdline,2)
else
pfix = nil
@ -236,18 +255,7 @@ function dfhack.interpreter(prompt,hfile,env)
if data[1] and data.n > 1 then
prompt_env._ = data[2]
if pfix == '!' then
safecall(print, table.unpack(data,2,data.n))
else
for i=2,data.n do
local varname = '_'..vcnt
prompt_env[varname] = data[i]
dfhack.print(varname..' = ')
safecall(print, data[i])
vcnt = vcnt + 1
end
end
safecall(pfix_handlers[pfix], data)
end
end
end

@ -35,17 +35,41 @@ using namespace std;
#include "Types.h"
#include "Error.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Job.h"
#include "ModuleFactory.h"
#include "Core.h"
#include "TileTypes.h"
#include "MiscUtils.h"
using namespace DFHack;
#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/d_init.h"
#include "df/item.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/general_ref_building_holderst.h"
#include "df/buildings_other_id.h"
#include "df/building_design.h"
#include "df/building_def.h"
#include "df/building_axle_horizontalst.h"
#include "df/building_trapst.h"
#include "df/building_bridgest.h"
#include "df/building_coffinst.h"
#include "df/building_furnacest.h"
#include "df/building_workshopst.h"
#include "df/building_screw_pumpst.h"
#include "df/building_water_wheelst.h"
#include "df/building_wellst.h"
using namespace df::enums;
using df::global::ui;
using df::global::world;
using df::global::d_init;
using df::global::building_next_id;
using df::global::process_jobs;
using df::building_def;
uint32_t Buildings::getNumBuildings()
@ -86,3 +110,558 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true;
}
df::building *Buildings::allocInstance(df::coord pos, df::building_type type, int subtype, int custom)
{
if (!building_next_id)
return NULL;
// Allocate object
const char *classname = ENUM_ATTR(building_type, classname, type);
if (!classname)
return NULL;
auto id = virtual_identity::find(classname);
if (!id)
return NULL;
df::building *bld = (df::building*)id->allocate();
if (!bld)
return NULL;
// Init base fields
bld->x1 = bld->x2 = bld->centerx = pos.x;
bld->y1 = bld->y2 = bld->centery = pos.y;
bld->z = pos.z;
bld->race = ui->race_id;
if (subtype != -1)
bld->setSubtype(subtype);
if (custom != -1)
bld->setCustomType(custom);
bld->setMaterialAmount(1);
// Type specific init
switch (type)
{
case building_type::Well:
{
auto obj = (df::building_wellst*)bld;
obj->bucket_z = bld->z;
break;
}
case building_type::Furnace:
{
auto obj = (df::building_furnacest*)bld;
obj->melt_remainder.resize(df::inorganic_raw::get_vector().size(), 0);
break;
}
case building_type::Coffin:
{
auto obj = (df::building_coffinst*)bld;
if (d_init && d_init->flags3.is_set(d_init_flags3::COFFIN_NO_PETS_DEFAULT))
obj->burial_mode.bits.no_pets = true;
break;
}
case building_type::Trap:
{
auto obj = (df::building_trapst*)bld;
if (obj->trap_type == trap_type::PressurePlate)
obj->unk_cc = 500;
break;
}
default:
break;
}
return bld;
}
static void makeOneDim(df::coord2d &size, df::coord2d &center, bool vertical)
{
if (vertical)
size.x = 1;
else
size.y = 1;
center = size/2;
}
bool Buildings::getCorrectSize(df::coord2d &size, df::coord2d &center,
df::building_type type, int subtype, int custom, int direction)
{
using namespace df::enums::building_type;
if (size.x <= 0)
size.x = 1;
if (size.y <= 0)
size.y = 1;
switch (type)
{
case FarmPlot:
case Bridge:
case RoadDirt:
case RoadPaved:
case Stockpile:
case Civzone:
center = size/2;
return true;
case TradeDepot:
case Shop:
size = df::coord2d(5,5);
center = df::coord2d(2,2);
return false;
case SiegeEngine:
case Windmill:
case Wagon:
size = df::coord2d(3,3);
center = df::coord2d(1,1);
return false;
case AxleHorizontal:
makeOneDim(size, center, direction);
return true;
case WaterWheel:
size = df::coord2d(3,3);
makeOneDim(size, center, direction);
return false;
case Workshop:
{
using namespace df::enums::workshop_type;
switch ((df::workshop_type)subtype)
{
case Quern:
case Millstone:
case Tool:
size = df::coord2d(1,1);
center = df::coord2d(0,0);
break;
case Siege:
case Kennels:
size = df::coord2d(5,5);
center = df::coord2d(2,2);
break;
case Custom:
if (auto def = df::building_def::find(custom))
{
size = df::coord2d(def->dim_x, def->dim_y);
center = df::coord2d(def->workloc_x, def->workloc_y);
break;
}
default:
size = df::coord2d(3,3);
center = df::coord2d(1,1);
}
return false;
}
case Furnace:
{
using namespace df::enums::furnace_type;
switch ((df::furnace_type)subtype)
{
case Custom:
if (auto def = df::building_def::find(custom))
{
size = df::coord2d(def->dim_x, def->dim_y);
center = df::coord2d(def->workloc_x, def->workloc_y);
break;
}
default:
size = df::coord2d(3,3);
center = df::coord2d(1,1);
}
return false;
}
case ScrewPump:
{
using namespace df::enums::screw_pump_direction;
switch ((df::screw_pump_direction)direction)
{
case FromEast:
size = df::coord2d(2,1);
center = df::coord2d(1,0);
break;
case FromSouth:
size = df::coord2d(1,2);
center = df::coord2d(0,1);
break;
case FromWest:
size = df::coord2d(2,1);
center = df::coord2d(0,0);
break;
default:
size = df::coord2d(1,2);
center = df::coord2d(0,0);
}
return false;
}
default:
size = df::coord2d(1,1);
center = df::coord2d(0,0);
return false;
}
}
static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile)
{
if (!extent.extents)
return NULL;
int dx = tile.x - extent.x;
int dy = tile.y - extent.y;
if (dx < 0 || dy < 0 || dx >= extent.width || dy >= extent.height)
return NULL;
return &extent.extents[dx + dy*extent.width];
}
bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size,
df::building_extents *ext, bool create_ext)
{
for (int dx = 0; dx < size.x; dx++)
{
for (int dy = 0; dy < size.y; dy++)
{
df::coord tile = pos + df::coord(dx,dy,0);
uint8_t *etile = (ext ? getExtentTile(*ext, tile) : NULL);
if (etile && !*etile)
continue;
df::map_block *block = Maps::getTileBlock(tile);
if (!block)
return false;
df::coord2d btile = df::coord2d(tile) & 15;
bool allowed = true;
if (block->occupancy[btile.x][btile.y].bits.building)
allowed = false;
else
{
auto tile = block->tiletype[btile.x][btile.y];
if (!HighPassable(tile))
allowed = false;
}
if (!allowed)
{
if (!ext || !create_ext)
return false;
if (!ext->extents)
{
ext->extents = new uint8_t[size.x*size.y];
ext->x = pos.x;
ext->y = pos.y;
ext->width = size.x;
ext->height = size.y;
memset(ext->extents, 1, size.x*size.y);
etile = getExtentTile(*ext, tile);
}
if (!etile)
return false;
*etile = 0;
}
}
}
return true;
}
static bool checkBuildingTiles(df::building *bld, bool can_use_extents)
{
df::coord pos(bld->x1,bld->y1,bld->z);
return Buildings::checkFreeTiles(pos, df::coord2d(bld->x2+1,bld->y2+1) - pos,
&bld->room, can_use_extents);
}
bool Buildings::setSize(df::building *bld, df::coord2d size, int direction)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(bld->id == -1);
if (bld->room.extents)
{
delete[] bld->room.extents;
bld->room.extents = NULL;
}
df::coord2d center;
getCorrectSize(size, center, bld->getType(), bld->getSubtype(),
bld->getCustomType(), direction);
bld->x2 = bld->x1 + size.x - 1;
bld->y2 = bld->y1 + size.y - 1;
bld->centerx = bld->x1 + center.x;
bld->centery = bld->y1 + center.y;
auto type = bld->getType();
bool can_use_extents = false;
using namespace df::enums::building_type;
switch (type)
{
case WaterWheel:
{
auto obj = (df::building_water_wheelst*)bld;
obj->is_vertical = !!direction;
break;
}
case AxleHorizontal:
{
auto obj = (df::building_axle_horizontalst*)bld;
obj->is_vertical = !!direction;
break;
}
case ScrewPump:
{
auto obj = (df::building_screw_pumpst*)bld;
obj->direction = (df::screw_pump_direction)direction;
break;
}
case Bridge:
{
auto obj = (df::building_bridgest*)bld;
// TODO: computes some kind of flag from tile data
obj->direction = (df::building_bridgest::T_direction)direction;
break;
}
case FarmPlot:
case RoadDirt:
case RoadPaved:
case Stockpile:
case Civzone:
can_use_extents = true;
break;
default:
break;
}
bool ok = checkBuildingTiles(bld, can_use_extents);
if (type != building_type::Construction)
{
int cnt = size.x * size.y;
if (bld->room.extents)
{
cnt = 0;
for (int i = 0; i < bld->room.width * bld->room.height; i++)
if (bld->room.extents[i] == 1)
cnt++;
}
bld->setMaterialAmount(cnt/4 + 1);
}
return ok;
}
static void markBuildingTiles(df::building *bld, df::tile_building_occ occv, bool stockpile)
{
for (int tx = bld->x1; tx <= bld->x2; tx++)
{
for (int ty = bld->y1; ty <= bld->y2; ty++)
{
df::coord tile(tx,ty,bld->z);
uint8_t *etile = getExtentTile(bld->room, tile);
if (etile && !*etile)
continue;
df::map_block *block = Maps::getTileBlock(tile);
df::coord2d btile = df::coord2d(tile) & 15;
auto &occ = block->occupancy[btile.x][btile.y];
occ.bits.building = occv;
auto &des = block->designation[btile.x][btile.y];
des.bits.dig = tile_dig_designation::No;
des.bits.pile = stockpile;
}
}
}
static void linkRooms(df::building *bld)
{
auto &vec = world->buildings.other[buildings_other_id::ANY_FREE];
bool changed = false;
for (size_t i = 0; i < vec.size(); i++)
{
auto room = vec[i];
if (!room->is_room || room->z != bld->z)
continue;
uint8_t *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1));
if (!pext || !*pext)
continue;
changed = true;
room->children.push_back(bld);
bld->parents.push_back(room);
// TODO: the game updates room rent here if economy is enabled
}
if (changed)
df::global::ui->equipment.update.bits.buildings = true;
}
static void linkBuilding(df::building *bld)
{
bld->id = (*building_next_id)++;
world->buildings.all.push_back(bld);
bld->categorize(true);
markBuildingTiles(bld, tile_building_occ::Planned, false);
linkRooms(bld);
if (process_jobs)
*process_jobs = true;
}
static void createDesign(df::building *bld, bool rough)
{
auto job = bld->jobs[0];
job->mat_type = bld->mat_type;
job->mat_index = bld->mat_index;
if (bld->needsDesign())
{
auto act = (df::building_actual*)bld;
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
}
static bool linkForConstruct(df::job* &job, df::building *bld)
{
if (!checkBuildingTiles(bld, false))
return false;
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref)
{
Core::printerr("Could not allocate general_ref_building_holderst\n");
return false;
}
linkBuilding(bld);
ref->building_id = bld->id;
job = new df::job();
job->job_type = df::job_type::ConstructBuilding;
job->pos = df::coord(bld->centerx, bld->centery, bld->z);
job->references.push_back(ref);
bld->jobs.push_back(job);
Job::linkIntoWorld(job);
return true;
}
bool Buildings::constructWithItems(df::building *bld, std::vector<df::item*> items)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(!items.empty());
CHECK_INVALID_ARGUMENT(bld->id == -1);
CHECK_INVALID_ARGUMENT(bld->isActual());
for (size_t i = 0; i < items.size(); i++)
{
CHECK_NULL_POINTER(items[i]);
if (items[i]->flags.bits.in_job)
return false;
}
df::job *job = NULL;
if (!linkForConstruct(job, bld))
return false;
bool rough = false;
for (size_t i = 0; i < items.size(); i++)
{
Job::attachJobItem(job, items[i], df::job_item_ref::Hauled);
if (items[i]->getType() == item_type::BOULDER)
rough = true;
if (bld->mat_type == -1)
bld->mat_type = items[i]->getMaterial();
if (bld->mat_index == -1)
bld->mat_index = items[i]->getMaterialIndex();
}
createDesign(bld, rough);
return true;
}
bool Buildings::constructWithFilters(df::building *bld, std::vector<df::job_item*> items)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(!items.empty());
CHECK_INVALID_ARGUMENT(bld->id == -1);
CHECK_INVALID_ARGUMENT(bld->isActual());
for (size_t i = 0; i < items.size(); i++)
CHECK_NULL_POINTER(items[i]);
df::job *job = NULL;
if (!linkForConstruct(job, bld))
{
for (size_t i = 0; i < items.size(); i++)
delete items[i];
return false;
}
bool rough = false;
for (size_t i = 0; i < items.size(); i++)
{
job->job_items.push_back(items[i]);
if (items[i]->item_type == item_type::BOULDER)
rough = true;
if (bld->mat_type == -1)
bld->mat_type = items[i]->mat_type;
if (bld->mat_index == -1)
bld->mat_index = items[i]->mat_index;
}
createDesign(bld, rough);
return true;
}

@ -311,3 +311,40 @@ bool DFHack::Job::listNewlyCreated(std::vector<df::job*> *pvec, int *id_var)
return true;
}
bool DFHack::Job::attachJobItem(df::job *job, df::item *item,
df::job_item_ref::T_role role,
int filter_idx, int insert_idx)
{
CHECK_NULL_POINTER(job);
CHECK_NULL_POINTER(item);
/*
* Functionality 100% reverse-engineered from DF code.
*/
if (role != df::job_item_ref::TargetContainer)
{
if (item->flags.bits.in_job)
return false;
item->flags.bits.in_job = true;
}
auto item_link = new df::item::T_jobs();
item_link->type = 2;
item_link->job = job;
item->jobs.push_back(item_link);
auto job_link = new df::job_item_ref();
job_link->item = item;
job_link->role = role;
job_link->job_item_idx = filter_idx;
if (size_t(insert_idx) < job->items.size())
vector_insert_at(job->items, insert_idx, job_link);
else
job->items.push_back(job_link);
return true;
}

@ -1 +1 @@
Subproject commit f649d31001e6023a9df5fe83c7971c17afe0d87d
Subproject commit d755560192ec318fbb52133e4e52972bee67d1e0

@ -1084,7 +1084,7 @@ static bool itemInRealJob(df::item *item)
return false;
if (item->jobs.size() != 1 ||
item->jobs[0]->unk1 != 2 ||
item->jobs[0]->type != 2 ||
item->jobs[0]->job == NULL)
return true;