Merge remote-tracking branch 'eswald/stockpile-iterator'

develop
expwnent 2014-09-16 17:05:02 -04:00
commit 5a1d88307f
9 changed files with 161 additions and 269 deletions

@ -1562,6 +1562,10 @@ are removed from extents. If <tt class="docutils literal">allow_occupied</tt>, t
<p>Checks if a bridge constructed at specified position would have
support from terrain, and thus won't collapse if retracted.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getStockpileContents(stockpile)</tt></p>
<p>Returns a list of items stored on the given stockpile.
Ignores empty bins, barrels, and wheelbarrows assigned as storage and transport for that stockpile.</p>
</li>
</ul>
<p>Low-level building creation functions;</p>
<ul>

@ -1394,6 +1394,11 @@ Buildings module
Checks if a bridge constructed at specified position would have
support from terrain, and thus won't collapse if retracted.
* ``dfhack.buildings.getStockpileContents(stockpile)``
Returns a list of items stored on the given stockpile.
Ignores empty bins, barrels, and wheelbarrows assigned as storage and transport for that stockpile.
Low-level building creation functions;
* ``dfhack.buildings.allocInstance(pos, type, subtype, custom)``

@ -1744,11 +1744,20 @@ int buildings_setSize(lua_State *state)
}
static int buildings_getStockpileContents(lua_State *state)
{
std::vector<df::item*> pvec;
Buildings::getStockpileContents(Lua::CheckDFObject<df::building_stockpilest>(state,1),&pvec);
Lua::PushVector(state, pvec);
return 1;
}
static const luaL_Reg dfhack_buildings_funcs[] = {
{ "findAtTile", buildings_findAtTile },
{ "findCivzonesAt", buildings_findCivzonesAt },
{ "getCorrectSize", buildings_getCorrectSize },
{ "setSize", &Lua::CallWithCatchWrapper<buildings_setSize> },
{ "getStockpileContents", buildings_getStockpileContents},
{ NULL, NULL }
};

@ -27,14 +27,18 @@ distribution.
#include "DataDefs.h"
#include "Types.h"
#include "df/building.h"
#include "df/building_stockpilest.h"
#include "df/building_type.h"
#include "df/civzone_type.h"
#include "df/furnace_type.h"
#include "df/item.h"
#include "df/workshop_type.h"
#include "df/construction_type.h"
#include "df/shop_type.h"
#include "df/siegeengine_type.h"
#include "df/trap_type.h"
#include "modules/Items.h"
#include "modules/Maps.h"
namespace df
{
@ -186,5 +190,104 @@ DFHACK_EXPORT bool deconstruct(df::building *bld);
void updateBuildings(color_ostream& out, void* ptr);
void clearBuildings(color_ostream& out);
/**
* Iterates over the items stored on a stockpile.
* (For stockpiles with containers, yields the containers, not their contents.)
*
* Usage:
*
* Buildings::StockpileIterator stored;
* for (stored.begin(stockpile); !stored.done(); ++stored) {
* df::item *item = *stored;
* }
*
* Implementation detail: Uses tile blocks for speed.
* For each tile block that contains at least part of the stockpile,
* starting at the top left and moving right, row by row,
* the block's items are checked for anything on the ground within that stockpile.
*/
class DFHACK_EXPORT StockpileIterator : public std::iterator<std::input_iterator_tag, df::item>
{
df::building_stockpilest* stockpile;
df::map_block* block;
size_t current;
df::item *item;
public:
StockpileIterator() {
stockpile = NULL;
block = NULL;
item = NULL;
}
StockpileIterator& operator++() {
while (stockpile) {
if (block) {
// Check the next item in the current block.
++current;
} else {
// Start with the top-left block covering the stockpile.
block = Maps::getTileBlock(stockpile->x1, stockpile->y1, stockpile->z);
current = 0;
}
while (current >= block->items.size()) {
// Out of items in this block; find the next block to search.
if (block->map_pos.x + 16 < stockpile->x2) {
block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z);
current = 0;
} else if (block->map_pos.y + 16 < stockpile->y2) {
block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z);
current = 0;
} else {
// All items in all blocks have been checked.
block = NULL;
item = NULL;
return *this;
}
}
// If the current item isn't properly stored, move on to the next.
item = df::item::find(block->items[current]);
if (!item->flags.bits.on_ground) {
continue;
}
if (!Buildings::containsTile(stockpile, item->pos, false)) {
continue;
}
// Ignore empty bins, barrels, and wheelbarrows assigned here.
if (item->isAssignedToThisStockpile(stockpile->id)) {
auto ref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_ITEM);
if (!ref) continue;
}
// Found a valid item; yield it.
break;
}
return *this;
}
void begin(df::building_stockpilest* sp) {
stockpile = sp;
operator++();
}
df::item* operator*() {
return item;
}
bool done() {
return block == NULL;
}
};
/**
* Collects items stored on a stockpile into a vector.
*/
DFHACK_EXPORT void getStockpileContents(df::building_stockpilest *stockpile, std::vector<df::item*> *items);
}
}

@ -1163,3 +1163,16 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr)
corner2.erase(id);
}
}
void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector<df::item*> *items)
{
CHECK_NULL_POINTER(stockpile);
items->clear();
Buildings::StockpileIterator stored;
for (stored.begin(stockpile); !stored.done(); ++stored) {
df::item *item = *stored;
items->push_back(item);
}
}

@ -1480,206 +1480,3 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
return CR_OK;
}
}
struct StockpileInfo {
df::building_stockpilest* sp;
int size;
int free;
int x1, x2, y1, y2, z;
public:
StockpileInfo(df::building_stockpilest *sp_) : sp(sp_)
{
MapExtras::MapCache mc;
z = sp_->z;
x1 = sp_->room.x;
x2 = sp_->room.x + sp_->room.width;
y1 = sp_->room.y;
y2 = sp_->room.y + sp_->room.height;
int e = 0;
size = 0;
free = 0;
for (int y = y1; y < y2; y++)
for (int x = x1; x < x2; x++)
if (sp_->room.extents[e++] == 1)
{
size++;
DFCoord cursor (x,y,z);
uint32_t blockX = x / 16;
uint32_t tileX = x % 16;
uint32_t blockY = y / 16;
uint32_t tileY = y % 16;
MapExtras::Block * b = mc.BlockAt(cursor/16);
if(b && b->is_valid())
{
auto &block = *b->getRaw();
df::tile_occupancy &occ = block.occupancy[tileX][tileY];
if (!occ.bits.item)
free++;
}
}
}
bool isFull() { return free == 0; }
bool canHold(df::item *i)
{
return false;
}
bool inStockpile(df::item *i)
{
df::item *container = Items::getContainer(i);
if (container)
return inStockpile(container);
if (i->pos.z != z) return false;
if (i->pos.x < x1 || i->pos.x >= x2 ||
i->pos.y < y1 || i->pos.y >= y2) return false;
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
return sp->room.extents[e] == 1;
}
int getId() { return sp->id; }
};
static int stockcheck(color_ostream &out, vector <string> & parameters)
{
int count = 0;
std::vector<StockpileInfo*> stockpiles;
for (int i = 0; i < world->buildings.all.size(); ++i)
{
df::building *build = world->buildings.all[i];
auto type = build->getType();
if (building_type::Stockpile == type)
{
df::building_stockpilest *sp = virtual_cast<df::building_stockpilest>(build);
StockpileInfo *spi = new StockpileInfo(sp);
stockpiles.push_back(spi);
}
}
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact);
F(spider_web); F(owned); F(in_job);
#undef F
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG
df::item_type typ = item->getType();
if (typ != item_type::MEAT &&
typ != item_type::FISH &&
typ != item_type::FISH_RAW &&
typ != item_type::PLANT &&
typ != item_type::CHEESE &&
typ != item_type::FOOD &&
typ != item_type::EGG)
continue;
df::item *container = 0;
df::unit *holder = 0;
df::building *building = 0;
for (size_t i = 0; i < item->general_refs.size(); i++)
{
df::general_ref *ref = item->general_refs[i];
switch (ref->getType())
{
case general_ref_type::CONTAINED_IN_ITEM:
container = ref->getItem();
break;
case general_ref_type::UNIT_HOLDER:
holder = ref->getUnit();
break;
case general_ref_type::BUILDING_HOLDER:
building = ref->getBuilding();
break;
default:
break;
}
}
df::item *nextcontainer = container;
df::item *lastcontainer = 0;
while(nextcontainer) {
df::item *thiscontainer = nextcontainer;
nextcontainer = 0;
for (size_t i = 0; i < thiscontainer->general_refs.size(); i++)
{
df::general_ref *ref = thiscontainer->general_refs[i];
switch (ref->getType())
{
case general_ref_type::CONTAINED_IN_ITEM:
lastcontainer = nextcontainer = ref->getItem();
break;
case general_ref_type::UNIT_HOLDER:
holder = ref->getUnit();
break;
case general_ref_type::BUILDING_HOLDER:
building = ref->getBuilding();
break;
default:
break;
}
}
}
if (holder)
continue; // carried items do not rot as far as i know
if (building) {
df::building_type btype = building->getType();
if (btype == building_type::TradeDepot ||
btype == building_type::Wagon)
continue; // items in trade depot or the embark wagon do not rot
if (typ == item_type::EGG && btype ==building_type::NestBox)
continue; // eggs in nest box do not rot
}
int canHoldCount = 0;
StockpileInfo *current = 0;
for (int idx = 0; idx < stockpiles.size(); idx++)
{
StockpileInfo *spi = stockpiles[idx];
if (spi->canHold(item)) canHoldCount++;
if (spi->inStockpile(item)) current=spi;
}
if (current)
continue;
count++;
}
return count;
}

@ -8,6 +8,7 @@
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/building_stockpilest.h"
#include "modules/Buildings.h"
#include "modules/Items.h"
#include "df/building_tradedepotst.h"
#include "df/general_ref_building_holderst.h"
@ -202,8 +203,6 @@ static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
if (!depot_info.findDepot())
return;
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
@ -218,18 +217,19 @@ static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
size_t marked_count = 0;
size_t error_count = 0;
for (size_t i = 0; i < items.size(); i++)
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
if (!is_valid_item(item))
if (!it->isValid())
continue;
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
Buildings::StockpileIterator stored;
for (stored.begin(it->getStockpile()); !stored.done(); ++stored)
{
if (!it->inStockpile(item))
df::item *item = *stored;
if (item->flags.whole & bad_flags.whole)
continue;
if (!is_valid_item(item))
continue;
// In case of container, check contained items for mandates

@ -1,7 +1,6 @@
local _ENV = mkmodule('plugins.stockflow')
local gui = require "gui"
local utils = require "utils"
reaction_list = reaction_list or {}
saved_orders = saved_orders or {}
@ -116,10 +115,9 @@ function collect_orders()
local result = {}
local entries = dfhack.persistent.get_all("stockflow/entry", true)
if entries then
local stockpiles = df.global.world.buildings.other.STOCKPILE
for _, entry in ipairs(entries) do
local spid = entry.ints[entry_ints.stockpile_id]
local stockpile = utils.binsearch(stockpiles, spid, "id")
local stockpile = df.building.find(spid)
if stockpile then
local order_number = entry.ints[entry_ints.order_number]
if reaction_list[order_number] and entry.value == reaction_list[order_number].name then
@ -975,49 +973,28 @@ function create_orders(order, amount)
df.global.world.manager_orders:insert('#', new_order)
end
function findItemsAtTile(x, y, z)
-- There should be a faster and easier way to do this...
local found = {}
local items = dfhack.maps.getTileBlock(x, y, z).items
for _, item_id in ipairs(items) do
local item = df.item.find(item_id)
if item.pos.x == x and item.pos.y == y and item.flags.on_ground then
found[#found+1] = item
end
end
return found
end
function countContents(container, settings)
local total = 0
local blocking = false
for _, item in ipairs(dfhack.items.getContainedItems(container)) do
if item.flags.container then
-- Recursively count the total of items contained.
-- Not likely to go more than two levels deep.
local subtotal, subblocked = countContents(item, settings)
local subtotal = countContents(item, settings)
if subtotal > 0 then
-- Ignore the inner container itself;
-- generally, only the contained items matter.
total = total + subtotal
elseif subblocked then
blocking = true
elseif matches_stockpile(item, settings) then
-- The container may or may not be empty,
-- but is stockpiled as a container itself.
total = total + 1
else
blocking = true
end
elseif matches_stockpile(item, settings) then
total = total + 1
else
blocking = true
end
end
return total, blocking
return total
end
function check_stockpiles(verbose)
@ -1048,19 +1025,21 @@ function check_pile(sp, verbose)
if not designation.liquid_type then
if not occupancy.item then
empty = empty + 1
else
local item_count, blocked = count_pile_items(sp, x, y)
if item_count > 0 then
filled = filled + item_count
elseif not blocked then
empty = empty + 1
end
end
end
end
end
end
for _, item in ipairs(dfhack.buildings.getStockpileContents(sp)) do
if item:isAssignedToThisStockpile(sp.id) then
-- This is a bin or barrel associated with the stockpile.
filled = filled + countContents(item, sp.settings)
elseif matches_stockpile(item, sp.settings) then
filled = filled + 1
end
end
if verbose then
print("Stockpile #"..sp.stockpile_number,
string.format("%3d spaces", numspaces),
@ -1071,29 +1050,6 @@ function check_pile(sp, verbose)
return filled, empty
end
function count_pile_items(sp, x, y)
local item_count = 0
local blocked = false
for _, item in ipairs(findItemsAtTile(x, y, sp.z)) do
if item:isAssignedToThisStockpile(sp.id) then
-- This is a bin or barrel associated with the stockpile.
-- If it's empty, it doesn't count as blocking the stockpile space.
-- Oddly, when empty, item.flags.container might be false.
local subtotal, subblocked = countContents(item, sp.settings)
item_count = item_count + subtotal
if subblocked then
blocked = true
end
elseif matches_stockpile(item, sp.settings) then
item_count = item_count + 1
else
blocked = true
end
end
return item_count, blocked
end
function matches_stockpile(item, settings)
-- Check whether the item matches the stockpile.
-- FIXME: This is starting to look like a whole lot of work.

@ -346,6 +346,11 @@ public:
return this->sp == sp;
}
df::building_stockpilest* getStockpile()
{
return sp;
}
protected:
int32_t id;
df::building_stockpilest* sp;