From 4cffb6428d5e25b0c3cb09044f65dbf5d43667e9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 1 May 2012 18:55:30 +0400 Subject: [PATCH] Update building creation code with new knowledge, and fix zone. Also, document new lua api, and add a more convenient wrapper. --- LUA_API.rst | 64 +++++++++++ Lua API.html | 77 +++++++++++-- library/LuaApi.cpp | 36 +++++- library/include/modules/Buildings.h | 18 ++- library/lua/dfhack.lua | 5 + library/lua/dfhack/buildings.lua | 95 ++++++++++++++++ library/modules/Buildings.cpp | 166 ++++++++++++++++++++-------- library/modules/Units.cpp | 2 +- library/xml | 2 +- plugins/zone.cpp | 4 +- 10 files changed, 404 insertions(+), 65 deletions(-) create mode 100644 library/lua/dfhack/buildings.lua diff --git a/LUA_API.rst b/LUA_API.rst index d2afacd42..9925240dd 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -850,6 +850,70 @@ Burrows module Adds or removes the tile from the burrow. Returns *false* if invalid coords. +Buildings module +---------------- + +* ``dfhack.buildings.getSize(building)`` + + Returns *width, height, centerx, centery*. + +* ``dfhack.buildings.getCorrectSize(width, height, type, subtype, custom, direction)`` + + Computes correct dimensions for the specified building type and orientation, + using width and height for flexible dimensions. + Returns *is_flexible, width, height, center_x, center_y*. + +* ``dfhack.buildings.checkFreeTiles(pos,size[,extents,change_extents,allow_occupied])`` + + Checks if the rectangle defined by ``pos`` and ``size``, and possibly extents, + can be used for placing a building. If ``change_extents`` is true, bad tiles + are removed from extents. If ``allow_occupied``, the occupancy test is skipped. + +* ``dfhack.buildings.countExtentTiles(extents,defval)`` + + Returns the number of tiles included by extents, or defval. + +* ``dfhack.buildings.hasSupport(pos,size)`` + + Checks if a bridge constructed at specified position would have + support from terrain, and thus won't collapse if retracted. + +Low-level building creation functions; + +* ``dfhack.buildings.allocInstance(pos, type, subtype, custom)`` + + Creates a new building instance of given type, subtype and custom type, + at specified position. Returns the object, or *nil* in case of an error. + +* ``dfhack.buildings.setSize(building, width, height, direction)`` + + Configures an object returned by ``allocInstance``, using specified + parameters wherever appropriate. If the building has fixed size along + any dimension, the corresponding input parameter will be ignored. + Returns *nil* if the building cannot be placed, or *true, width, + height, rect_area, true_area*. Returned width and height are the + final values used by the building; true_area is less than rect_area + if any tiles were removed from designation. + +* ``dfhack.buildings.constructWithItems(building, items)`` + + Links a fully configured object created by ``allocInstance`` into the + world for construction, using a list of specific items as material. + Returns *true*, or *false* if impossible. + +* ``dfhack.buildings.constructWithFilters(building, job_items)`` + + Links a fully configured object created by ``allocInstance`` into the + world for construction, using a list of job_item filters as inputs. + Returns *true*, or *false* if impossible. Filter objects are claimed + and possibly destroyed in any case. + Use a negative ``quantity`` field value to auto-compute the amount + from the size of the building. + +More high-level functions are implemented in lua and can be loaded by +``require('dfhack.buildings')``. See ``hack/lua/dfhack/buildings.lua``. + + Core interpreter context ======================== diff --git a/Lua API.html b/Lua API.html index fd2004b8d..69a0ab120 100644 --- a/Lua API.html +++ b/Lua API.html @@ -343,17 +343,18 @@ ul.auto-toc {
  • Items module
  • Maps module
  • Burrows module
  • +
  • Buildings module
  • -
  • Core interpreter context
  • -
  • Plugins @@ -1061,9 +1062,65 @@ burrows, or the presence of invaders.

  • +
    +

    Buildings module

    + +

    Low-level building creation functions;

    + +

    More high-level functions are implemented in lua and can be loaded by +require('dfhack.buildings'). See hack/lua/dfhack/buildings.lua.

    +
    -

    Core interpreter context

    +

    Core interpreter context

    While plugins can create any number of interpreter instances, there is one special context managed by dfhack core. It is the only context that can receive events from DF and plugins.

    @@ -1077,7 +1134,7 @@ only context that can receive events from DF and plugins.

    -

    Event type

    +

    Event type

    An event is just a lua table with a predefined metatable that contains a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -1103,14 +1160,14 @@ order using dfhack.safecall.

    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -1148,7 +1205,7 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 82c8bb84b..11cf73c3f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -768,7 +768,8 @@ static const luaL_Reg dfhack_burrows_funcs[] = { static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { WRAPM(Buildings, allocInstance), WRAPM(Buildings, checkFreeTiles), - WRAPM(Buildings, setSize), + WRAPM(Buildings, countExtentTiles), + WRAPM(Buildings, hasSupport), WRAPM(Buildings, constructWithItems), WRAPM(Buildings, constructWithFilters), { NULL, NULL } @@ -776,15 +777,16 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { 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); + df::coord2d size(luaL_optint(state, 1, 1), luaL_optint(state, 2, 1)); + + auto t = (df::building_type)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); + bool flexible = Buildings::getCorrectSize(size, center, t, st, cu, d); + lua_pushboolean(state, flexible); lua_pushinteger(state, size.x); lua_pushinteger(state, size.y); @@ -793,8 +795,30 @@ static int buildings_getCorrectSize(lua_State *state) return 5; } +static int buildings_setSize(lua_State *state) +{ + auto bld = Lua::CheckDFObject(state, 1); + df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1)); + int dir = luaL_optint(state, 4, 0); + bool ok = Buildings::setSize(bld, size, dir); + lua_pushboolean(state, ok); + if (ok) + { + auto size = Buildings::getSize(bld).second; + int area = size.x*size.y; + lua_pushinteger(state, size.x); + lua_pushinteger(state, size.y); + lua_pushinteger(state, area); + lua_pushinteger(state, Buildings::countExtentTiles(&bld->room, area)); + return 5; + } + else + return 1; +} + static const luaL_Reg dfhack_buildings_funcs[] = { { "getCorrectSize", buildings_getCorrectSize }, + { "setSize", buildings_setSize }, { NULL, NULL } }; diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 5ec04f5e2..610688187 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -108,7 +108,18 @@ DFHACK_EXPORT bool getCorrectSize(df::coord2d &size, df::coord2d ¢er, * 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); + df::building_extents *ext = NULL, + bool create_ext = false, bool allow_occupied = false); + +/** + * Returns the number of tiles included by the extent, or defval. + */ +DFHACK_EXPORT int countExtentTiles(df::building_extents *ext, int defval = -1); + +/** + * Checks if the area has support from the terrain. + */ +DFHACK_EXPORT bool hasSupport(df::coord pos, df::coord2d size); /** * Sets the size of the building, using size and direction as guidance. @@ -116,6 +127,11 @@ DFHACK_EXPORT bool checkFreeTiles(df::coord pos, df::coord2d size, */ DFHACK_EXPORT bool setSize(df::building *bld, df::coord2d size, int direction = 0); +/** + * Decodes the size of the building into pos and size. + */ +DFHACK_EXPORT std::pair getSize(df::building *bld); + /** * Initiates construction of the building, using specified items as inputs. * Returns true if success. diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 6915cbb8a..c4c994d2a 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -173,6 +173,11 @@ function dfhack.maps.getTileSize() return map.x_count, map.y_count, map.z_count end +function dfhack.buildings.getSize(bld) + local x, y = bld.x1, bld.y1 + return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y +end + -- Interactive local print_banner = true diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua new file mode 100644 index 000000000..57427e9fb --- /dev/null +++ b/library/lua/dfhack/buildings.lua @@ -0,0 +1,95 @@ +local dfhack = dfhack +local _ENV = dfhack.BASE_G +local buildings = dfhack.buildings + +--[[ + Wraps all steps necessary to create a building with + a construct job into one function. + + dfhack.buildings.constructBuilding{ + -- Position: + pos = { x = ..., y = ..., z = ... }, + -- OR + x = ..., y = ..., z = ..., + + -- Type: + type = df.building_type.FOO, subtype = ..., custom = ..., + + -- Field initialization: + fields = { ... }, + + -- Size and orientation: + width = ..., height = ..., direction = ..., + + -- Abort if not all tiles in the rectangle are available: + full_rectangle = true, + + -- Materials: + items = { item, item ... }, + -- OR + filter = { { ... }, { ... }... } + } + + Returns: the created building, or 'nil, error' +--]] + +function buildings.constructBuilding(info) + local btype = info.type + local subtype = info.subtype or -1 + local custom = info.custom or -1 + + if not (info.pos or info.x) then + error('position is required') + end + if not (info.items or info.filters) then + error('either items or filters are required') + elseif info.filters then + for _,v in ipairs(info.filters) do + v.new = true + end + end + if type(btype) ~= 'number' or not df.building_type[btype] then + error('Invalid building type: '..tostring(btype)) + end + + local pos = info.pos or xyz2pos(info.x, info.y, info.z) + + local instance = buildings.allocInstance(pos, btype, subtype, custom) + if not instance then + error('Could not create building of type '..df.building_type[btype]) + end + + local to_delete = instance + return dfhack.with_finalize( + function() + df.delete(to_delete) + end, + function() + if info.fields then + instance:assign(info.fields) + end + local ok,w,h,area,r_area = buildings.setSize( + instance,info.width,info.height,info.direction + ) + if not ok then + return nil, "cannot place at this position" + end + if info.full_rectangle and area ~= r_area then + return nil, "not all tiles can be used" + end + if info.items then + ok = buildings.constructWithItems(instance, info.items) + else + ok = buildings.constructWithFilters(instance, info.filters) + end + if not ok then + return nil, "could not construct the building" + end + -- Success + to_delete = nil + return instance + end + ) +end + +return buildings \ No newline at end of file diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index a4dc1852a..bc062f980 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -160,8 +160,7 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in 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; + obj->initBurialFlags(); // DF has this copy&pasted break; } case building_type::Trap: @@ -332,18 +331,27 @@ static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) } bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, - df::building_extents *ext, bool create_ext) + df::building_extents *ext, + bool create_ext, bool allow_occupied) { + bool found_any = false; + 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); + uint8_t *etile = NULL; - if (etile && !*etile) - continue; + // Exclude using extents + if (ext && ext->extents) + { + etile = getExtentTile(*ext, tile); + if (!etile || !*etile) + continue; + } + // Look up map block df::map_block *block = Maps::getTileBlock(tile); if (!block) return false; @@ -352,7 +360,9 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, bool allowed = true; - if (block->occupancy[btile.x][btile.y].bits.building) + // Check occupancy and tile type + if (!allow_occupied && + block->occupancy[btile.x][btile.y].bits.building) allowed = false; else { @@ -361,7 +371,10 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, allowed = false; } - if (!allowed) + // Create extents if requested + if (allowed) + found_any = true; + else { if (!ext || !create_ext) return false; @@ -386,15 +399,72 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, } } - return true; + return found_any; } -static bool checkBuildingTiles(df::building *bld, bool can_use_extents) +std::pair Buildings::getSize(df::building *bld) { + CHECK_NULL_POINTER(bld); + 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); + return std::pair(pos, df::coord2d(bld->x2+1,bld->y2+1) - pos); +} + +static bool checkBuildingTiles(df::building *bld, bool can_change) +{ + auto psize = Buildings::getSize(bld); + + return Buildings::checkFreeTiles(psize.first, psize.second, &bld->room, + can_change && bld->isExtentShaped(), + !bld->isSettingOccupancy()); +} + +int Buildings::countExtentTiles(df::building_extents *ext, int defval) +{ + if (!ext || !ext->extents) + return defval; + + int cnt = 0; + for (int i = 0; i < ext->width * ext->height; i++) + if (ext->extents[i]) + cnt++; + return cnt; +} + +bool Buildings::hasSupport(df::coord pos, df::coord2d size) +{ + for (int dx = -1; dx <= size.x; dx++) + { + for (int dy = -1; dy <= size.y; dy++) + { + // skip corners + if ((dx < 0 || dx == size.x) && (dy < 0 || dy == size.y)) + continue; + + df::coord tile = pos + df::coord(dx,dy,0); + df::map_block *block = Maps::getTileBlock(tile); + if (!block) + continue; + + df::coord2d btile = df::coord2d(tile) & 15; + if (!isOpenTerrain(block->tiletype[btile.x][btile.y])) + return true; + } + } + + return false; +} + +static int computeMaterialAmount(df::building *bld) +{ + auto size = Buildings::getSize(bld).second; + int cnt = size.x * size.y; + + if (bld->room.extents && bld->isExtentShaped()) + cnt = Buildings::countExtentTiles(&bld->room, cnt); + + return cnt/4 + 1; } bool Buildings::setSize(df::building *bld, df::coord2d size, int direction) @@ -402,12 +472,14 @@ bool Buildings::setSize(df::building *bld, df::coord2d size, int direction) CHECK_NULL_POINTER(bld); CHECK_INVALID_ARGUMENT(bld->id == -1); + // Delete old extents if (bld->room.extents) { delete[] bld->room.extents; bld->room.extents = NULL; } + // Compute correct size and apply it df::coord2d center; getCorrectSize(size, center, bld->getType(), bld->getSubtype(), bld->getCustomType(), direction); @@ -418,7 +490,6 @@ bool Buildings::setSize(df::building *bld, df::coord2d size, int direction) bld->centery = bld->y1 + center.y; auto type = bld->getType(); - bool can_use_extents = false; using namespace df::enums::building_type; @@ -445,62 +516,65 @@ bool Buildings::setSize(df::building *bld, df::coord2d size, int direction) case Bridge: { auto obj = (df::building_bridgest*)bld; - // TODO: computes some kind of flag from tile data + auto psize = getSize(bld); + obj->gate_flags.bits.has_support = hasSupport(psize.first, psize.second); 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); + bool ok = checkBuildingTiles(bld, true); 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); - } + bld->setMaterialAmount(computeMaterialAmount(bld)); return ok; } -static void markBuildingTiles(df::building *bld, df::tile_building_occ occv, bool stockpile) +static void markBuildingTiles(df::building *bld, bool remove) { + bool use_extents = bld->room.extents && bld->isExtentShaped(); + bool stockpile = (bld->getType() == building_type::Stockpile); + bool complete = (bld->getBuildStage() >= bld->getMaxBuildStage()); + + if (remove) + stockpile = complete = false; + 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; + if (use_extents) + { + 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; + if (!remove) + des.bits.dig = tile_dig_designation::No; + + if (complete) + bld->updateOccupancy(tx, ty); + else + { + auto &occ = block->occupancy[btile.x][btile.y]; + + if (remove) + occ.bits.building = tile_building_occ::None; + else + occ.bits.building = tile_building_occ::Planned; + } } } } @@ -539,7 +613,8 @@ static void linkBuilding(df::building *bld) world->buildings.all.push_back(bld); bld->categorize(true); - markBuildingTiles(bld, tile_building_occ::Planned, false); + if (bld->isSettingOccupancy()) + markBuildingTiles(bld, false); linkRooms(bld); @@ -651,6 +726,9 @@ bool Buildings::constructWithFilters(df::building *bld, std::vectorquantity < 0) + items[i]->quantity = computeMaterialAmount(bld); + job->job_items.push_back(items[i]); if (items[i]->item_type == item_type::BOULDER) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 77a836c9f..6c417b6e1 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -151,7 +151,7 @@ void Units::CopyCreature(df::unit * source, t_unit & furball) // mood stuff furball.mood = source->mood; - furball.mood_skill = source->job.unk_2f8; // FIXME: really? More like currently used skill anyway. + furball.mood_skill = source->job.mood_skill; // FIXME: really? More like currently used skill anyway. Translation::readName(furball.artifact_name, &source->status.artifact_name); // labors diff --git a/library/xml b/library/xml index d75556019..6ce2b7120 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d755560192ec318fbb52133e4e52972bee67d1e0 +Subproject commit 6ce2b7120e5a246093116b48cb2ac64c35a8270d diff --git a/plugins/zone.cpp b/plugins/zone.cpp index ee6abf327..4314aa1e0 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -1606,8 +1606,8 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) out << ", pen/pasture"; else if (civ->zone_flags.bits.pit_pond) { - out << " (pit flags:" << civ->pit_flags << ")"; - if(civ->pit_flags & 1) + out << " (pit flags:" << civ->pit_flags.whole << ")"; + if(civ->pit_flags.bits.is_pond) out << ", pond"; else out << ", pit";