diff --git a/Lua API.html b/Lua API.html
index 49505e65b..659e19945 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -1562,6 +1562,10 @@ are removed from extents. If allow_occupied, t
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;
diff --git a/Lua API.rst b/Lua API.rst
index 7283f196f..d770271ae 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -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)``
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 3fd18a8d6..f2693363a 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1744,11 +1744,20 @@ int buildings_setSize(lua_State *state)
}
+static int buildings_getStockpileContents(lua_State *state)
+{
+ std::vector pvec;
+ Buildings::getStockpileContents(Lua::CheckDFObject(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 },
+ { "getStockpileContents", buildings_getStockpileContents},
{ NULL, NULL }
};
diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h
index 9bb5c7e54..db12bb73e 100644
--- a/library/include/modules/Buildings.h
+++ b/library/include/modules/Buildings.h
@@ -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
+{
+ 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 *items);
+
}
}
diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp
index 3d836db52..885edfa02 100644
--- a/library/modules/Buildings.cpp
+++ b/library/modules/Buildings.cpp
@@ -1163,3 +1163,16 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr)
corner2.erase(id);
}
}
+
+void Buildings::getStockpileContents(df::building_stockpilest *stockpile, std::vector *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);
+ }
+}
diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp
index ed907a788..05855a749 100644
--- a/plugins/autolabor.cpp
+++ b/plugins/autolabor.cpp
@@ -1480,206 +1480,3 @@ command_result autolabor (color_ostream &out, std::vector & 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 & parameters)
-{
- int count = 0;
-
- std::vector 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(build);
- StockpileInfo *spi = new StockpileInfo(sp);
- stockpiles.push_back(spi);
- }
-
- }
-
- std::vector &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;
-}
diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp
index 6e60d35d1..5b28a9171 100644
--- a/plugins/autotrade.cpp
+++ b/plugins/autotrade.cpp
@@ -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 &stockpiles)
if (!depot_info.findDepot())
return;
- std::vector &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 &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
diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua
index 2f564cec1..d1c06525a 100644
--- a/plugins/lua/stockflow.lua
+++ b/plugins/lua/stockflow.lua
@@ -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.
diff --git a/plugins/uicommon.h b/plugins/uicommon.h
index 1daabf4c7..c900a585e 100644
--- a/plugins/uicommon.h
+++ b/plugins/uicommon.h
@@ -346,6 +346,11 @@ public:
return this->sp == sp;
}
+ df::building_stockpilest* getStockpile()
+ {
+ return sp;
+ }
+
protected:
int32_t id;
df::building_stockpilest* sp;