unify and update automelt/autotrade/autodump
parent
c06f54df8f
commit
5ffbb4d0ef
@ -1,55 +0,0 @@
|
||||
automelt
|
||||
========
|
||||
|
||||
.. dfhack-tool::
|
||||
:summary: Quickly designate items to be melted.
|
||||
:tags: fort productivity items stockpiles
|
||||
:no-command:
|
||||
|
||||
Automelt checks monitor-enabled stockpiles once every in-game day, and will mark valid items for melting.
|
||||
|
||||
Please see `gui/automelt` for the interactive configuration dialog.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
enable automelt
|
||||
automelt [status]
|
||||
automelt designate
|
||||
automelt (monitor|nomonitor) <stockpile>[,<stockpile>...]
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Automatically monitor stockpile ("melt"), marking new valid items for melting. This also immediately marks all present items for melting::
|
||||
|
||||
enable automelt
|
||||
automelt monitor melt
|
||||
|
||||
Enable monitoring for ("Stockpile #52"), which has not been given a custom name::
|
||||
|
||||
automelt monitor "Stockpile #52"
|
||||
|
||||
Enable monitoring for ("Stockpile #52"), which has not been given a custom name::
|
||||
|
||||
automelt monitor 52
|
||||
|
||||
Enable monitoring for multiple stockpiles ("Stockpile #52", "Stockpile #35", and "melt")::
|
||||
|
||||
automelt monitor 52,"Stockpile #35",melt
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
``status``
|
||||
Shows current configuration and relevant statistics
|
||||
|
||||
``designate``
|
||||
Designates items in monitored stockpiles for melting right now. This works even if ``automelt`` is not currently enabled.
|
||||
|
||||
``(no)monitor <stockpile>``
|
||||
Enable/disable monitoring of a given stockpile. Works with either the stockpile's name (if set) or ID.
|
||||
If the stockpile has no custom name set, you may designate it by either the full name as reported by
|
||||
the status command, or by just the number.
|
@ -1,18 +0,0 @@
|
||||
autotrade
|
||||
=========
|
||||
|
||||
.. dfhack-tool::
|
||||
:summary: Quickly designate items to be traded.
|
||||
:tags: unavailable fort productivity items stockpiles
|
||||
:no-command:
|
||||
|
||||
When `enabled <enable>`, this plugin adds an option to the :kbd:`q` menu for
|
||||
stockpiles. When the ``autotrade`` option is selected for the stockpile, any
|
||||
items placed in the stockpile will automatically be designated to be traded.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
enable autotrade
|
@ -0,0 +1,71 @@
|
||||
logistics
|
||||
=========
|
||||
|
||||
.. dfhack-tool::
|
||||
:summary: Automatically mark and route items in monitored stockpiles.
|
||||
:tags: fort productivity items stockpiles
|
||||
:no-command:
|
||||
|
||||
Commands act upon the stockpile selected in the UI unless another stockpile
|
||||
identifier is specified on the commandline.
|
||||
|
||||
When the plugin is enabled, it checks stockpiles marked with automelt,
|
||||
autotrade, and/or autodump features twice every in-game day, and will mark valid
|
||||
items in those stockpiles for melting, trading, and/or dumping, respectively.
|
||||
Note that items will only be marked for trading if a caravan is approaching or
|
||||
is already at the trade depot.
|
||||
|
||||
Please see `gui/logistics` for the interactive status and configuration dialog.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
enable logistics
|
||||
logistics [status]
|
||||
logistics now
|
||||
logistics add [melt] [trade] [dump] [<options>]
|
||||
logistics clear [all] [<options>]
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
``logistics``
|
||||
Print a summary of all your stockpiles, their ``logistics`` configuration,
|
||||
and the number of items that are designated (or can be designated) by each
|
||||
of the ``logistics`` processors.
|
||||
|
||||
``logistics now``
|
||||
Designate items in monitored stockpiles according to the current
|
||||
configuration. This works regardless of whether ``logistics`` is currently
|
||||
enabled.
|
||||
|
||||
``logistics add melt``
|
||||
Register the currently selected stockpile for automelting. Meltable items
|
||||
that are brought to this stockpile will be designated for melting.
|
||||
|
||||
``logistics add melt trade -s goblinite``
|
||||
Register the stockpile(s) named "goblinite" for automatic melting and
|
||||
automatic trading. Items will be marked for melting, but any items still in
|
||||
the stockpile when a caravan shows up will be brought to the trade depot
|
||||
for trading.
|
||||
|
||||
``logistics clear``
|
||||
Unregisters the currently selected stockpile from any monitoring. Any
|
||||
currently designated items will remain designated.
|
||||
|
||||
``logistics clear -s 12,15,goblinite``
|
||||
Unregisters the stockpiles with stockpile numbers 12 and 15, along with any
|
||||
stockpiles named "goblinite", from any monitoring.
|
||||
|
||||
``logistics clear all``
|
||||
Unregister all stockpiles from any monitoring.
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
``-s``, ``--stockpile <name or number>[,<name or number>...]``
|
||||
Causes the command to act upon stockpiles with the given names or numbers
|
||||
instead of the stockpile that is currently selected in the UI. Note that
|
||||
the numbers are the stockpile numbers, not the building ids.
|
@ -1,799 +0,0 @@
|
||||
#include "Debug.h"
|
||||
#include "LuaTools.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Items.h"
|
||||
#include "modules/World.h"
|
||||
#include "modules/Persistence.h"
|
||||
#include "modules/Gui.h"
|
||||
|
||||
#include "df/world.h"
|
||||
#include "df/building_stockpilest.h"
|
||||
#include "df/item_quality.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
DFHACK_PLUGIN("automelt");
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
REQUIRE_GLOBAL(world);
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
DBG_DECLARE(automelt, status, DebugCategory::LINFO);
|
||||
DBG_DECLARE(automelt, cycle, DebugCategory::LINFO);
|
||||
DBG_DECLARE(automelt, perf, DebugCategory::LINFO);
|
||||
}
|
||||
|
||||
static const string CONFIG_KEY = string(plugin_name) + "/config";
|
||||
static const string STOCKPILE_CONFIG_KEY_PREFIX = string(plugin_name) + "/stockpile/";
|
||||
static PersistentDataItem config;
|
||||
|
||||
static unordered_map<int32_t, PersistentDataItem> watched_stockpiles;
|
||||
|
||||
enum StockpileConfigValues
|
||||
{
|
||||
STOCKPILE_CONFIG_ID = 0,
|
||||
STOCKPILE_CONFIG_MONITORED = 1,
|
||||
};
|
||||
|
||||
static int get_config_val(PersistentDataItem &c, int index)
|
||||
{
|
||||
if (!c.isValid())
|
||||
return -1;
|
||||
return c.ival(index);
|
||||
}
|
||||
|
||||
static bool get_config_bool(PersistentDataItem &c, int index)
|
||||
{
|
||||
return get_config_val(c, index) == 1;
|
||||
}
|
||||
|
||||
static void set_config_val(PersistentDataItem &c, int index, int value)
|
||||
{
|
||||
if (c.isValid())
|
||||
c.ival(index) = value;
|
||||
}
|
||||
|
||||
static void set_config_bool(PersistentDataItem &c, int index, bool value)
|
||||
{
|
||||
set_config_val(c, index, value ? 1 : 0);
|
||||
}
|
||||
|
||||
static PersistentDataItem &ensure_stockpile_config(color_ostream &out, int id)
|
||||
{
|
||||
DEBUG(cycle,out).print("ensuring stockpile config id=%d\n", id);
|
||||
if (watched_stockpiles.count(id)){
|
||||
DEBUG(cycle,out).print("stockpile exists in watched_indices\n");
|
||||
return watched_stockpiles[id];
|
||||
}
|
||||
|
||||
string keyname = STOCKPILE_CONFIG_KEY_PREFIX + int_to_string(id);
|
||||
DEBUG(status,out).print("creating new persistent key for stockpile %d\n", id);
|
||||
watched_stockpiles.emplace(id, World::GetPersistentData(keyname, NULL));
|
||||
return watched_stockpiles[id];
|
||||
}
|
||||
|
||||
static void remove_stockpile_config(color_ostream &out, int id)
|
||||
{
|
||||
if (!watched_stockpiles.count(id))
|
||||
return;
|
||||
DEBUG(status, out).print("removing persistent key for stockpile %d\n", id);
|
||||
World::DeletePersistentData(watched_stockpiles[id]);
|
||||
watched_stockpiles.erase(id);
|
||||
}
|
||||
|
||||
static bool isStockpile(df::building * bld) {
|
||||
return bld && bld->getType() == df::building_type::Stockpile;
|
||||
}
|
||||
|
||||
static void validate_stockpile_configs(color_ostream &out)
|
||||
{
|
||||
for (auto &c : watched_stockpiles) {
|
||||
int id = get_config_val(c.second, STOCKPILE_CONFIG_ID);
|
||||
auto bld = df::building::find(id);
|
||||
if (!isStockpile(bld))
|
||||
remove_stockpile_config(out, id);
|
||||
}
|
||||
}
|
||||
|
||||
static const int32_t CYCLE_TICKS = 1200;
|
||||
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
||||
static int32_t do_cycle(color_ostream &out);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands)
|
||||
{
|
||||
DEBUG(status, out).print("initializing %s\n", plugin_name);
|
||||
|
||||
// provide a configuration interface for the plugin
|
||||
commands.push_back(PluginCommand(
|
||||
plugin_name,
|
||||
"Auto melt items in designated stockpiles.",
|
||||
do_command));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
||||
{
|
||||
if (enable != is_enabled)
|
||||
{
|
||||
is_enabled = enable;
|
||||
DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled");
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown(color_ostream &out)
|
||||
{
|
||||
DEBUG(status, out).print("shutting down %s\n", plugin_name);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_load_data(color_ostream &out)
|
||||
{
|
||||
cycle_timestamp = 0;
|
||||
config = World::GetPersistentData(CONFIG_KEY);
|
||||
|
||||
if (!config.isValid())
|
||||
{
|
||||
DEBUG(status, out).print("no config found in this save; initializing\n");
|
||||
config = World::AddPersistentData(CONFIG_KEY);
|
||||
}
|
||||
|
||||
DEBUG(status, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false");
|
||||
vector<PersistentDataItem> loaded_persist_data;
|
||||
World::GetPersistentData(&loaded_persist_data, STOCKPILE_CONFIG_KEY_PREFIX, true);
|
||||
watched_stockpiles.clear();
|
||||
const size_t num_watched_stockpiles = loaded_persist_data.size();
|
||||
for (size_t idx = 0; idx < num_watched_stockpiles; ++idx)
|
||||
{
|
||||
auto &c = loaded_persist_data[idx];
|
||||
watched_stockpiles.emplace(get_config_val(c, STOCKPILE_CONFIG_ID), c);
|
||||
}
|
||||
validate_stockpile_configs(out);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onupdate(color_ostream &out)
|
||||
{
|
||||
if (!Core::getInstance().isWorldLoaded())
|
||||
return CR_OK;
|
||||
if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS)
|
||||
{
|
||||
int32_t marked = do_cycle(out);
|
||||
if (0 < marked)
|
||||
out.print("automelt: marked %d item(s) for melting\n", marked);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static bool call_automelt_lua(color_ostream *out, const char *fn_name,
|
||||
int nargs = 0, int nres = 0,
|
||||
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
|
||||
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
|
||||
DEBUG(status).print("calling automelt lua function: '%s'\n", fn_name);
|
||||
|
||||
CoreSuspender guard;
|
||||
|
||||
auto L = Lua::Core::State;
|
||||
Lua::StackUnwinder top(L);
|
||||
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
|
||||
return Lua::CallLuaModuleFunction(*out, L, "plugins.automelt", fn_name,
|
||||
nargs, nres,
|
||||
std::forward<Lua::LuaLambda&&>(args_lambda),
|
||||
std::forward<Lua::LuaLambda&&>(res_lambda));
|
||||
}
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
||||
CoreSuspender suspend;
|
||||
|
||||
if (!Core::getInstance().isWorldLoaded()) {
|
||||
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
bool show_help = false;
|
||||
if (!call_automelt_lua(&out, "parse_commandline", parameters.size(), 1,
|
||||
[&](lua_State *L) {
|
||||
for (const string ¶m : parameters)
|
||||
Lua::Push(L, param);
|
||||
},
|
||||
[&](lua_State *L) {
|
||||
show_help = !lua_toboolean(L, -1);
|
||||
})) {
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
return show_help ? CR_WRONG_USAGE : CR_OK;
|
||||
}
|
||||
|
||||
static inline bool is_metal_item(df::item *item)
|
||||
{
|
||||
if (!item)
|
||||
return false;
|
||||
MaterialInfo mat(item);
|
||||
return (mat.getCraftClass() == craft_material_class::Metal);
|
||||
}
|
||||
|
||||
struct BadFlagsCanMelt {
|
||||
uint32_t whole;
|
||||
|
||||
BadFlagsCanMelt() {
|
||||
df::item_flags flags;
|
||||
#define F(x) 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
|
||||
whole = flags.whole;
|
||||
}
|
||||
};
|
||||
|
||||
struct BadFlagsMarkItem {
|
||||
uint32_t whole;
|
||||
|
||||
BadFlagsMarkItem() {
|
||||
df::item_flags flags;
|
||||
#define F(x) 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);
|
||||
#undef F
|
||||
whole = flags.whole;
|
||||
}
|
||||
};
|
||||
|
||||
// Copied from Kelly Martin's code
|
||||
static inline bool can_melt(df::item *item)
|
||||
{
|
||||
static const BadFlagsCanMelt bad_flags;
|
||||
|
||||
if (!is_metal_item(item))
|
||||
return false;
|
||||
|
||||
if (item->flags.whole & bad_flags.whole)
|
||||
return false;
|
||||
|
||||
df::item_type t = item->getType();
|
||||
|
||||
if (t == df::enums::item_type::BAR)
|
||||
return false;
|
||||
|
||||
for (auto &g : item->general_refs)
|
||||
{
|
||||
switch (g->getType())
|
||||
{
|
||||
case general_ref_type::CONTAINS_ITEM:
|
||||
case general_ref_type::UNIT_HOLDER:
|
||||
case general_ref_type::CONTAINS_UNIT:
|
||||
return false;
|
||||
case general_ref_type::CONTAINED_IN_ITEM:
|
||||
{
|
||||
df::item *c = g->getItem();
|
||||
for (auto &gg : c->general_refs)
|
||||
{
|
||||
if (gg->getType() == general_ref_type::UNIT_HOLDER)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->getQuality() >= item_quality::Masterful)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool is_set_to_melt(df::item *item)
|
||||
{
|
||||
return item->flags.bits.melt;
|
||||
}
|
||||
|
||||
static int mark_item(color_ostream &out, df::item *item, BadFlagsMarkItem bad_flags, int32_t stockpile_id,
|
||||
int32_t &premarked_item_count, int32_t &item_count, map<int32_t, bool> &tracked_item_map, bool should_melt)
|
||||
{
|
||||
DEBUG(perf,out).print("%s running mark_item: should_melt=%d\n", plugin_name, should_melt);
|
||||
|
||||
if (DBG_NAME(perf).isEnabled(DebugCategory::LDEBUG)) {
|
||||
string name = "";
|
||||
item->getItemDescription(&name, 0);
|
||||
DEBUG(perf,out).print("item %s %d\n", name.c_str(), item->id);
|
||||
}
|
||||
|
||||
if (item->flags.whole & bad_flags.whole){
|
||||
DEBUG(perf,out).print("rejected flag check\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (item->isAssignedToThisStockpile(stockpile_id))
|
||||
{
|
||||
DEBUG(perf,out).print("assignedToStockpile\n");
|
||||
size_t marked_count = 0;
|
||||
vector<df::item *> contents;
|
||||
Items::getContainedItems(item, &contents);
|
||||
for (auto child = contents.begin(); child != contents.end(); child++)
|
||||
{
|
||||
DEBUG(perf,out).print("inside child loop\n");
|
||||
marked_count += mark_item(out, *child, bad_flags, stockpile_id, premarked_item_count, item_count, tracked_item_map, should_melt);
|
||||
}
|
||||
return marked_count;
|
||||
}
|
||||
|
||||
DEBUG(perf,out).print("past recurse loop\n");
|
||||
|
||||
if (is_set_to_melt(item)) {
|
||||
DEBUG(perf,out).print("already set to melt\n");
|
||||
tracked_item_map.emplace(item->id, true);
|
||||
premarked_item_count++;
|
||||
DEBUG(perf,out).print("premarked_item_count=%d\n", premarked_item_count);
|
||||
item_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!can_melt(item)) {
|
||||
DEBUG(perf,out).print("cannot melt\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEBUG(perf,out).print("increment item count\n");
|
||||
item_count++;
|
||||
|
||||
//Only melt if told to, else count
|
||||
if (should_melt) {
|
||||
DEBUG(perf,out).print("should_melt hit\n");
|
||||
insert_into_vector(world->items.other[items_other_id::ANY_MELT_DESIGNATED], &df::item::id, item);
|
||||
item->flags.bits.melt = 1;
|
||||
tracked_item_map.emplace(item->id, true);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & stockpile, int32_t &premarked_item_count, int32_t &item_count, map<int32_t, bool> &tracked_item_map, bool should_melt)
|
||||
{
|
||||
DEBUG(perf,out).print("%s running mark_all_in_stockpile\nshould_melt=%d\n", plugin_name, should_melt);
|
||||
|
||||
static const BadFlagsMarkItem bad_flags;
|
||||
|
||||
int32_t marked_count = 0;
|
||||
|
||||
if(!stockpile.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spid = get_config_val(stockpile, STOCKPILE_CONFIG_ID);
|
||||
auto found = df::building::find(spid);
|
||||
if (!isStockpile(found))
|
||||
return 0;
|
||||
|
||||
df::building_stockpilest * pile_cast = virtual_cast<df::building_stockpilest>(found);
|
||||
|
||||
if (!pile_cast)
|
||||
return 0;
|
||||
|
||||
Buildings::StockpileIterator stored;
|
||||
DEBUG(perf,out).print("starting item iter. loop\n");
|
||||
for (stored.begin(pile_cast); !stored.done(); ++stored) {
|
||||
DEBUG(perf,out).print("calling mark_item\n");
|
||||
marked_count += mark_item(out, *stored, bad_flags, spid, premarked_item_count, item_count, tracked_item_map, should_melt);
|
||||
DEBUG(perf,out).print("end mark_item call\npremarked_item_count=%d\n", premarked_item_count);
|
||||
}
|
||||
DEBUG(perf,out).print("end item iter. loop\n");
|
||||
DEBUG(perf,out).print("exit mark_all_in_stockpile\nmarked_count %d\npremarked_count %d\n", marked_count, premarked_item_count);
|
||||
return marked_count;
|
||||
}
|
||||
|
||||
static int32_t scan_stockpiles(color_ostream &out, bool should_melt, map<int32_t, int32_t> &item_count_piles, map<int32_t, int32_t> &premarked_item_count_piles,
|
||||
map<int32_t, int32_t> &marked_item_count_piles, map<int32_t, bool> &tracked_item_map) {
|
||||
DEBUG(perf,out).print("running scan_stockpiles\n");
|
||||
int32_t newly_marked = 0;
|
||||
|
||||
item_count_piles.clear();
|
||||
premarked_item_count_piles.clear();
|
||||
marked_item_count_piles.clear();
|
||||
|
||||
//Parse all the watched piles
|
||||
for (auto &c : watched_stockpiles) {
|
||||
int id = get_config_val(c.second, STOCKPILE_CONFIG_ID);
|
||||
//Check monitor status
|
||||
bool monitored = get_config_bool(c.second, STOCKPILE_CONFIG_MONITORED);
|
||||
|
||||
if (!monitored) continue;
|
||||
|
||||
DEBUG(perf,out).print("%s past monitor check\nmonitored=%d\n", plugin_name, monitored);
|
||||
|
||||
int32_t item_count = 0;
|
||||
|
||||
int32_t premarked_count = 0;
|
||||
|
||||
int32_t marked = mark_all_in_stockpile(out, c.second, premarked_count, item_count, tracked_item_map, should_melt);
|
||||
|
||||
DEBUG(perf,out).print("post mark_all_in_stockpile premarked_count=%d\n", premarked_count);
|
||||
|
||||
item_count_piles.emplace(id, item_count);
|
||||
|
||||
premarked_item_count_piles.emplace(id, premarked_count);
|
||||
|
||||
marked_item_count_piles.emplace(id, marked);
|
||||
|
||||
newly_marked += marked;
|
||||
|
||||
}
|
||||
DEBUG(perf,out).print("exit scan_stockpiles\n");
|
||||
return newly_marked;
|
||||
}
|
||||
|
||||
static int32_t scan_all_melt_designated(color_ostream &out, map<int32_t, bool> &tracked_item_map) {
|
||||
|
||||
DEBUG(perf,out).print("running scan_all_melt_designated\n");
|
||||
int32_t marked_item_count = 0;
|
||||
//loop over all items marked as melt-designated
|
||||
for (auto item : world->items.other[items_other_id::ANY_MELT_DESIGNATED]) {
|
||||
//item has already been marked/counted as inside a stockpile. Don't double-count.
|
||||
if (tracked_item_map.count(item->id)) {
|
||||
continue;
|
||||
}
|
||||
marked_item_count++;
|
||||
}
|
||||
DEBUG(perf,out).print("exiting scan_all_melt_designated\n");
|
||||
return marked_item_count;
|
||||
}
|
||||
|
||||
static int32_t scan_count_all(color_ostream &out, bool should_melt, int32_t &marked_item_count_total, int32_t &marked_total_count_all_piles, int32_t &marked_item_count_global,
|
||||
int32_t &total_items_all_piles, map<int32_t, int32_t> &item_count_piles, map<int32_t, int32_t> &premarked_item_count_piles, map<int32_t, int32_t> &marked_item_count_piles) {
|
||||
|
||||
//Wraps both scan_stockpiles and scan_all_melt_designated
|
||||
//Returns count of items in piles freshly marked
|
||||
|
||||
int32_t newly_marked_items_piles = 0;
|
||||
|
||||
map<int32_t, bool> tracked_item_map_piles;
|
||||
|
||||
newly_marked_items_piles = scan_stockpiles(out, should_melt, item_count_piles, premarked_item_count_piles, marked_item_count_piles, tracked_item_map_piles);
|
||||
marked_item_count_global = scan_all_melt_designated(out, tracked_item_map_piles);
|
||||
|
||||
for (auto &i : watched_stockpiles) {
|
||||
int id = get_config_val(i.second, STOCKPILE_CONFIG_ID);
|
||||
total_items_all_piles+= item_count_piles[id];
|
||||
marked_total_count_all_piles += premarked_item_count_piles[id];
|
||||
}
|
||||
|
||||
marked_item_count_total = marked_item_count_global + marked_total_count_all_piles;
|
||||
|
||||
|
||||
return newly_marked_items_piles;
|
||||
|
||||
}
|
||||
|
||||
static int32_t do_cycle(color_ostream &out) {
|
||||
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
|
||||
cycle_timestamp = world->frame_counter;
|
||||
|
||||
validate_stockpile_configs(out);
|
||||
|
||||
int32_t marked_item_count_total = 0;
|
||||
int32_t marked_item_count_global = 0;
|
||||
int32_t newly_marked_items_piles = 0;
|
||||
int32_t total_items_all_piles = 0;
|
||||
int32_t marked_total_count_all_piles = 0;
|
||||
map<int32_t, int32_t> item_count_piles, premarked_item_count_piles, marked_item_count_piles;
|
||||
|
||||
newly_marked_items_piles = scan_count_all(out, true, marked_item_count_total, marked_total_count_all_piles, marked_item_count_global,
|
||||
total_items_all_piles, item_count_piles, premarked_item_count_piles, marked_item_count_piles);
|
||||
|
||||
DEBUG(perf,out).print("exit %s do_cycle\n", plugin_name);
|
||||
return newly_marked_items_piles;
|
||||
}
|
||||
|
||||
static int getSelectedStockpile(color_ostream &out) {
|
||||
df::building *bld = Gui::getSelectedBuilding(out, true);
|
||||
if (!isStockpile(bld)) {
|
||||
DEBUG(status,out).print("Selected building is not stockpile\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bld->id;
|
||||
}
|
||||
|
||||
static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) {
|
||||
int32_t bldg_id = getSelectedStockpile(out);
|
||||
if (bldg_id == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
validate_stockpile_configs(out);
|
||||
PersistentDataItem *c = NULL;
|
||||
if (watched_stockpiles.count(bldg_id)) {
|
||||
c = &(watched_stockpiles[bldg_id]);
|
||||
return c;
|
||||
}
|
||||
|
||||
DEBUG(status,out).print("No existing config\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void push_stockpile_config(lua_State *L, int id, bool monitored) {
|
||||
map<string, int32_t> stockpile_config;
|
||||
stockpile_config.emplace("id", id);
|
||||
stockpile_config.emplace("monitored", monitored);
|
||||
Lua::Push(L, stockpile_config);
|
||||
}
|
||||
|
||||
static void push_stockpile_config(lua_State *L, PersistentDataItem &c) {
|
||||
push_stockpile_config(L, get_config_val(c, STOCKPILE_CONFIG_ID),
|
||||
get_config_bool(c, STOCKPILE_CONFIG_MONITORED));
|
||||
}
|
||||
|
||||
static void emplace_bulk_stockpile_config(lua_State *L, int id, bool monitored, map<int32_t, map<string, int32_t>> &stockpiles) {
|
||||
map<string, int32_t> stockpile_config;
|
||||
stockpile_config.emplace("id", id);
|
||||
stockpile_config.emplace("monitored", monitored);
|
||||
|
||||
stockpiles.emplace(id, stockpile_config);
|
||||
}
|
||||
|
||||
static void emplace_bulk_stockpile_config(lua_State *L, PersistentDataItem &c, map<int32_t, map<string, int32_t>> &stockpiles) {
|
||||
int32_t id = get_config_val(c, STOCKPILE_CONFIG_ID);
|
||||
bool monitored = get_config_bool(c, STOCKPILE_CONFIG_MONITORED);
|
||||
emplace_bulk_stockpile_config(L, id, monitored, stockpiles);
|
||||
}
|
||||
|
||||
static void automelt_designate(color_ostream &out) {
|
||||
DEBUG(status, out).print("entering automelt designate\n");
|
||||
out.print("designated %d item(s) for melting\n", do_cycle(out));
|
||||
}
|
||||
|
||||
static void automelt_printStatus(color_ostream &out) {
|
||||
DEBUG(status,out).print("entering automelt_printStatus\n");
|
||||
validate_stockpile_configs(out);
|
||||
out.print("automelt is %s\n\n", is_enabled ? "enabled" : "disabled");
|
||||
|
||||
int32_t marked_item_count_total = 0;
|
||||
int32_t marked_item_count_global = 0;
|
||||
int32_t total_items_all_piles = 0;
|
||||
int32_t marked_total_count_all_piles = 0;
|
||||
map<int32_t, int32_t> item_count_piles, premarked_item_count_piles, marked_item_count_piles;
|
||||
|
||||
scan_count_all(out, false, marked_item_count_total, marked_total_count_all_piles, marked_item_count_global,
|
||||
total_items_all_piles, item_count_piles, premarked_item_count_piles, marked_item_count_piles);
|
||||
|
||||
out.print("summary:\n");
|
||||
out.print(" total items in monitored piles: %d\n", total_items_all_piles);
|
||||
out.print(" marked items in monitored piles: %d\n", marked_total_count_all_piles);
|
||||
out.print("marked items global (excludes those in monitored piles): %d\n", marked_item_count_global);
|
||||
out.print(" marked items global (monitored piles + others): %d\n", marked_item_count_total);
|
||||
|
||||
int name_width = 11;
|
||||
for (auto &stockpile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(stockpile)) continue;
|
||||
if (stockpile->name.empty()) {
|
||||
string stock_name = "Stockpile #" + int_to_string(stockpile->stockpile_number);
|
||||
name_width = std::max(name_width, (int)(stock_name.size()));
|
||||
} else {
|
||||
name_width = std::max(name_width, (int)stockpile->name.size());
|
||||
}
|
||||
}
|
||||
name_width = -name_width;
|
||||
|
||||
const char *fmt = "%*s %9s %5s %6s\n";
|
||||
out.print(fmt, name_width, "name", "monitored", "items", "marked");
|
||||
out.print(fmt, name_width, "----", "---------", "-----", "------");
|
||||
|
||||
for (auto &stockpile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(stockpile)) continue;
|
||||
bool monitored = false;
|
||||
int32_t item_count = 0;
|
||||
int32_t marked_item_count = 0;
|
||||
if (watched_stockpiles.count(stockpile->id)) {
|
||||
auto &c = watched_stockpiles[stockpile->id];
|
||||
monitored = get_config_bool(c, STOCKPILE_CONFIG_MONITORED);
|
||||
int id = get_config_val(c, STOCKPILE_CONFIG_ID);
|
||||
item_count = item_count_piles[id];
|
||||
marked_item_count = premarked_item_count_piles[id];
|
||||
}
|
||||
|
||||
if (stockpile->name.empty()) {
|
||||
string stock_name = "Stockpile #" + int_to_string(stockpile->stockpile_number);
|
||||
out.print(fmt, name_width, stock_name.c_str(), monitored ? "[x]": "[ ]",
|
||||
int_to_string(item_count).c_str(), int_to_string(marked_item_count).c_str());
|
||||
} else {
|
||||
out.print(fmt, name_width, stockpile->name.c_str(), monitored ? "[x]": "[ ]",
|
||||
int_to_string(item_count).c_str(), int_to_string(marked_item_count).c_str());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
DEBUG(status,out).print("exiting automelt_printStatus\n");
|
||||
|
||||
}
|
||||
|
||||
static void automelt_setStockpileConfig(color_ostream &out, int id, bool monitored) {
|
||||
DEBUG(status,out).print("entering automelt_setStockpileConfig for id=%d and monitored=%d\n", id, monitored);
|
||||
validate_stockpile_configs(out);
|
||||
auto bldg = df::building::find(id);
|
||||
bool isInvalidStockpile = !isStockpile(bldg);
|
||||
bool hasNoData = !monitored;
|
||||
if (isInvalidStockpile || hasNoData) {
|
||||
DEBUG(cycle,out).print("calling remove_stockpile_config with id=%d monitored=%d\n", id, monitored);
|
||||
remove_stockpile_config(out, id);
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentDataItem &c = ensure_stockpile_config(out, id);
|
||||
set_config_val(c, STOCKPILE_CONFIG_ID, id);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_MONITORED, monitored);
|
||||
|
||||
//If we're marking something as monitored, go ahead and designate contents.
|
||||
if (monitored) {
|
||||
automelt_designate(out);
|
||||
}
|
||||
}
|
||||
|
||||
static int automelt_getStockpileConfig(lua_State *L) {
|
||||
color_ostream *out = Lua::GetOutput(L);
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
DEBUG(status, *out).print("entering automelt_getStockpileConfig\n");
|
||||
validate_stockpile_configs(*out);
|
||||
|
||||
int id;
|
||||
if (lua_isnumber(L, -1)) {
|
||||
bool found = false;
|
||||
id = lua_tointeger(L, -1);
|
||||
|
||||
for (auto &stockpile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(stockpile)) continue;
|
||||
if (id == stockpile->stockpile_number){
|
||||
id = stockpile->id;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
const char * name = lua_tostring(L, -1);
|
||||
if (!name)
|
||||
return 0;
|
||||
string nameStr = name;
|
||||
bool found = false;
|
||||
for (auto &stockpile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(stockpile)) continue;
|
||||
if (nameStr == stockpile->name) {
|
||||
id = stockpile->id;
|
||||
found = true;
|
||||
break;
|
||||
} else {
|
||||
string stock_name = "Stockpile #" + int_to_string(stockpile->stockpile_number);
|
||||
if (stock_name == nameStr) {
|
||||
id = stockpile->id;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!found)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (watched_stockpiles.count(id)) {
|
||||
push_stockpile_config(L, watched_stockpiles[id]);
|
||||
} else {
|
||||
push_stockpile_config(L, id, false);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int automelt_getSelectedStockpileConfig(lua_State *L){
|
||||
color_ostream *out = Lua::GetOutput(L);
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
DEBUG(status, *out).print("entering automelt_getSelectedStockpileConfig\n");
|
||||
validate_stockpile_configs(*out);
|
||||
|
||||
int32_t stock_id = getSelectedStockpile(*out);
|
||||
PersistentDataItem *c = getSelectedStockpileConfig(*out);
|
||||
|
||||
//If we have a valid config entry, use that. Else assume all false.
|
||||
if (c) {
|
||||
push_stockpile_config(L, *c);
|
||||
} else {
|
||||
push_stockpile_config(L, stock_id, false);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int automelt_getItemCountsAndStockpileConfigs(lua_State *L) {
|
||||
color_ostream *out = Lua::GetOutput(L);
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
DEBUG(status,*out).print("entering automelt_getItemCountsAndStockpileConfigs\n");
|
||||
validate_stockpile_configs(*out);
|
||||
|
||||
int32_t marked_item_count_total = 0;
|
||||
int32_t marked_item_count_global = 0;
|
||||
int32_t total_items_all_piles = 0;
|
||||
int32_t marked_total_count_all_piles = 0;
|
||||
map<int32_t, int32_t> item_count_piles, premarked_item_count_piles, marked_item_count_piles;
|
||||
|
||||
scan_count_all(*out, false, marked_item_count_total, marked_total_count_all_piles, marked_item_count_global,
|
||||
total_items_all_piles, item_count_piles, premarked_item_count_piles, marked_item_count_piles);
|
||||
|
||||
map<string, int32_t> summary;
|
||||
summary.emplace("total_items", total_items_all_piles);
|
||||
summary.emplace("premarked_items", marked_total_count_all_piles);
|
||||
summary.emplace("marked_item_count_global", marked_item_count_global);
|
||||
summary.emplace("marked_item_count_total", marked_item_count_total);
|
||||
|
||||
Lua::Push(L, summary);
|
||||
Lua::Push(L, item_count_piles);
|
||||
Lua::Push(L, marked_item_count_piles);
|
||||
Lua::Push(L, premarked_item_count_piles);
|
||||
|
||||
map<int32_t, map<string, int32_t>> stockpile_config_map;
|
||||
|
||||
for (auto pile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(pile))
|
||||
continue;
|
||||
|
||||
int id = pile->id;
|
||||
if (watched_stockpiles.count(id)) {
|
||||
emplace_bulk_stockpile_config(L, watched_stockpiles[id], stockpile_config_map);
|
||||
|
||||
} else {
|
||||
emplace_bulk_stockpile_config(L, id, false, stockpile_config_map);
|
||||
}
|
||||
}
|
||||
|
||||
Lua::Push(L, stockpile_config_map);
|
||||
|
||||
|
||||
DEBUG(perf, *out).print("exit automelt_getItemCountsAndStockpileConfigs\n");
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
DFHACK_PLUGIN_LUA_FUNCTIONS{
|
||||
DFHACK_LUA_FUNCTION(automelt_printStatus),
|
||||
DFHACK_LUA_FUNCTION(automelt_designate),
|
||||
DFHACK_LUA_FUNCTION(automelt_setStockpileConfig),
|
||||
DFHACK_LUA_END};
|
||||
|
||||
DFHACK_PLUGIN_LUA_COMMANDS{
|
||||
DFHACK_LUA_COMMAND(automelt_getStockpileConfig),
|
||||
DFHACK_LUA_COMMAND(automelt_getItemCountsAndStockpileConfigs),
|
||||
DFHACK_LUA_COMMAND(automelt_getSelectedStockpileConfig),
|
||||
DFHACK_LUA_END};
|
@ -1,467 +0,0 @@
|
||||
#include "uicommon.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
|
||||
#include "df/world.h"
|
||||
#include "df/world_raws.h"
|
||||
#include "df/building_def.h"
|
||||
#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"
|
||||
#include "df/job.h"
|
||||
#include "df/job_item_ref.h"
|
||||
#include "modules/Job.h"
|
||||
#include "df/plotinfost.h"
|
||||
#include "df/mandate.h"
|
||||
#include "modules/Maps.h"
|
||||
|
||||
using df::building_stockpilest;
|
||||
|
||||
DFHACK_PLUGIN("autotrade");
|
||||
REQUIRE_GLOBAL(gps);
|
||||
REQUIRE_GLOBAL(world);
|
||||
REQUIRE_GLOBAL(cursor);
|
||||
REQUIRE_GLOBAL(plotinfo);
|
||||
|
||||
static const string PERSISTENCE_KEY = "autotrade/stockpiles";
|
||||
|
||||
/*
|
||||
* Depot Access
|
||||
*/
|
||||
|
||||
class TradeDepotInfo
|
||||
{
|
||||
public:
|
||||
TradeDepotInfo() : depot(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool findDepot()
|
||||
{
|
||||
if (isValid())
|
||||
return true;
|
||||
|
||||
reset();
|
||||
for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++)
|
||||
{
|
||||
auto bld = *bld_it;
|
||||
if (!isUsableDepot(bld))
|
||||
continue;
|
||||
|
||||
depot = bld;
|
||||
id = depot->id;
|
||||
break;
|
||||
}
|
||||
|
||||
return depot;
|
||||
}
|
||||
|
||||
bool assignItem(df::item *item)
|
||||
{
|
||||
auto href = df::allocate<df::general_ref_building_holderst>();
|
||||
if (!href)
|
||||
return false;
|
||||
|
||||
auto job = new df::job();
|
||||
|
||||
df::coord tpos(depot->centerx, depot->centery, depot->z);
|
||||
job->pos = tpos;
|
||||
|
||||
job->job_type = job_type::BringItemToDepot;
|
||||
|
||||
// job <-> item link
|
||||
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled))
|
||||
{
|
||||
delete job;
|
||||
delete href;
|
||||
return false;
|
||||
}
|
||||
|
||||
// job <-> building link
|
||||
href->building_id = id;
|
||||
depot->jobs.push_back(job);
|
||||
job->general_refs.push_back(href);
|
||||
|
||||
// add to job list
|
||||
Job::linkIntoWorld(job);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
depot = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t id;
|
||||
df::building *depot;
|
||||
|
||||
bool isUsableDepot(df::building* bld)
|
||||
{
|
||||
if (bld->getType() != building_type::TradeDepot)
|
||||
return false;
|
||||
|
||||
if (bld->getBuildStage() < bld->getMaxBuildStage())
|
||||
return false;
|
||||
|
||||
if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValid()
|
||||
{
|
||||
if (!depot)
|
||||
return false;
|
||||
|
||||
auto found = df::building::find(id);
|
||||
return found && found == depot && isUsableDepot(found);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static TradeDepotInfo depot_info;
|
||||
|
||||
|
||||
/*
|
||||
* Item Manipulation
|
||||
*/
|
||||
|
||||
static bool is_valid_item(df::item *item)
|
||||
{
|
||||
// Similar to Items::canTrade() with a few checks changed
|
||||
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:
|
||||
return false;
|
||||
|
||||
case general_ref_type::UNIT_HOLDER:
|
||||
return false;
|
||||
|
||||
case general_ref_type::BUILDING_HOLDER:
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < item->specific_refs.size(); i++)
|
||||
{
|
||||
df::specific_ref *ref = item->specific_refs[i];
|
||||
|
||||
if (ref->type == specific_ref_type::JOB)
|
||||
{
|
||||
// Ignore any items assigned to a job
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Items::checkMandates(item))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
|
||||
{
|
||||
if (!depot_info.findDepot())
|
||||
return;
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
size_t marked_count = 0;
|
||||
size_t error_count = 0;
|
||||
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
|
||||
{
|
||||
if (!it->isValid())
|
||||
continue;
|
||||
|
||||
Buildings::StockpileIterator stored;
|
||||
for (stored.begin(it->getStockpile()); !stored.done(); ++stored)
|
||||
{
|
||||
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
|
||||
bool mandates_ok = true;
|
||||
vector<df::item*> contained_items;
|
||||
Items::getContainedItems(item, &contained_items);
|
||||
for (df::item *cit : contained_items)
|
||||
{
|
||||
if (!Items::checkMandates(cit))
|
||||
{
|
||||
mandates_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mandates_ok)
|
||||
continue;
|
||||
|
||||
if (depot_info.assignItem(item))
|
||||
{
|
||||
++marked_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (++error_count < 5)
|
||||
{
|
||||
Gui::showZoomAnnouncement(df::announcement_type::CANCEL_JOB, item->pos,
|
||||
"Cannot trade item from stockpile " + int_to_string(it->getId()), COLOR_RED, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (marked_count)
|
||||
Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items for trade", COLOR_GREEN, false);
|
||||
|
||||
if (error_count >= 5)
|
||||
{
|
||||
Gui::showAnnouncement(int_to_string(error_count) + " items were not marked", COLOR_RED, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Stockpile Monitoring
|
||||
*/
|
||||
|
||||
class StockpileMonitor
|
||||
{
|
||||
public:
|
||||
bool isMonitored(df::building_stockpilest *sp)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void add(df::building_stockpilest *sp)
|
||||
{
|
||||
auto pile = PersistentStockpileInfo(sp, PERSISTENCE_KEY);
|
||||
if (pile.isValid())
|
||||
{
|
||||
monitored_stockpiles.push_back(pile);
|
||||
monitored_stockpiles.back().save();
|
||||
}
|
||||
}
|
||||
|
||||
void remove(df::building_stockpilest *sp)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
{
|
||||
it->remove();
|
||||
monitored_stockpiles.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doCycle()
|
||||
{
|
||||
if (!can_trade())
|
||||
return;
|
||||
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();)
|
||||
{
|
||||
if (!it->isValid())
|
||||
{
|
||||
it = monitored_stockpiles.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
mark_all_in_stockpiles(monitored_stockpiles);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
monitored_stockpiles.clear();
|
||||
std::vector<PersistentDataItem> items;
|
||||
DFHack::World::GetPersistentData(&items, PERSISTENCE_KEY);
|
||||
|
||||
for (auto i = items.begin(); i != items.end(); i++)
|
||||
{
|
||||
auto pile = PersistentStockpileInfo(*i, PERSISTENCE_KEY);
|
||||
if (pile.load())
|
||||
monitored_stockpiles.push_back(pile);
|
||||
else
|
||||
pile.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
vector<PersistentStockpileInfo> monitored_stockpiles;
|
||||
};
|
||||
|
||||
static StockpileMonitor monitor;
|
||||
|
||||
#define DELTA_TICKS 600
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
{
|
||||
if(!Maps::IsValid())
|
||||
return CR_OK;
|
||||
|
||||
if (DFHack::World::ReadPauseState())
|
||||
return CR_OK;
|
||||
|
||||
if (world->frame_counter % DELTA_TICKS != 0)
|
||||
return CR_OK;
|
||||
|
||||
monitor.doCycle();
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Interface
|
||||
*/
|
||||
|
||||
struct trade_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
bool handleInput(set<df::interface_key> *input)
|
||||
{
|
||||
if (Gui::inRenameBuilding())
|
||||
return false;
|
||||
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return false;
|
||||
|
||||
if (input->count(interface_key::CUSTOM_SHIFT_T))
|
||||
{
|
||||
if (monitor.isMonitored(sp))
|
||||
monitor.remove(sp);
|
||||
else
|
||||
monitor.add(sp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (!handleInput(input))
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return;
|
||||
|
||||
auto dims = Gui::getDwarfmodeViewDims();
|
||||
int left_margin = dims.menu_x1 + 1;
|
||||
int x = left_margin;
|
||||
int y = dims.y2 - 5;
|
||||
|
||||
int links = 0;
|
||||
links += sp->links.give_to_pile.size();
|
||||
links += sp->links.take_from_pile.size();
|
||||
links += sp->links.give_to_workshop.size();
|
||||
links += sp->links.take_from_workshop.size();
|
||||
bool state = monitor.isMonitored(sp);
|
||||
|
||||
if (links + 12 >= y) {
|
||||
y = dims.y2;
|
||||
OutputString(COLOR_WHITE, x, y, "Auto: ");
|
||||
x += 11;
|
||||
OutputString(COLOR_LIGHTRED, x, y, "T");
|
||||
OutputString(state? COLOR_LIGHTGREEN: COLOR_GREY, x, y, "rade ");
|
||||
} else {
|
||||
OutputToggleString(x, y, "Auto trade", "T", state, true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, feed);
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, render);
|
||||
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case DFHack::SC_MAP_LOADED:
|
||||
depot_info.reset();
|
||||
monitor.reset();
|
||||
break;
|
||||
case DFHack::SC_MAP_UNLOADED:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
||||
{
|
||||
if (enable != is_enabled)
|
||||
{
|
||||
depot_info.reset();
|
||||
monitor.reset();
|
||||
|
||||
if (!INTERPOSE_HOOK(trade_hook, feed).apply(enable) ||
|
||||
!INTERPOSE_HOOK(trade_hook, render).apply(enable))
|
||||
return CR_FAILURE;
|
||||
|
||||
is_enabled = enable;
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,676 @@
|
||||
#include "Debug.h"
|
||||
#include "LuaTools.h"
|
||||
#include "PluginManager.h"
|
||||
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Job.h"
|
||||
#include "modules/Persistence.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/building_stockpilest.h"
|
||||
#include "df/building_tradedepotst.h"
|
||||
#include "df/caravan_state.h"
|
||||
#include "df/general_ref_building_holderst.h"
|
||||
#include "df/plotinfost.h"
|
||||
#include "df/world.h"
|
||||
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
DFHACK_PLUGIN("logistics");
|
||||
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
REQUIRE_GLOBAL(plotinfo);
|
||||
REQUIRE_GLOBAL(world);
|
||||
|
||||
namespace DFHack {
|
||||
DBG_DECLARE(logistics, status, DebugCategory::LINFO);
|
||||
DBG_DECLARE(logistics, cycle, DebugCategory::LINFO);
|
||||
}
|
||||
|
||||
static const string CONFIG_KEY_PREFIX = string(plugin_name) + "/";
|
||||
static unordered_map<int32_t, PersistentDataItem> watched_stockpiles;
|
||||
|
||||
enum StockpileConfigValues {
|
||||
STOCKPILE_CONFIG_STOCKPILE_NUMBER = 0,
|
||||
STOCKPILE_CONFIG_MELT = 1,
|
||||
STOCKPILE_CONFIG_TRADE = 2,
|
||||
STOCKPILE_CONFIG_DUMP = 3,
|
||||
};
|
||||
|
||||
static int get_config_val(PersistentDataItem& c, int index) {
|
||||
if (!c.isValid())
|
||||
return -1;
|
||||
return c.ival(index);
|
||||
}
|
||||
|
||||
static bool get_config_bool(PersistentDataItem& c, int index) {
|
||||
return get_config_val(c, index) == 1;
|
||||
}
|
||||
|
||||
static void set_config_val(PersistentDataItem& c, int index, int value) {
|
||||
if (c.isValid())
|
||||
c.ival(index) = value;
|
||||
}
|
||||
|
||||
static void set_config_bool(PersistentDataItem& c, int index, bool value) {
|
||||
set_config_val(c, index, value ? 1 : 0);
|
||||
}
|
||||
|
||||
static PersistentDataItem& ensure_stockpile_config(color_ostream& out, int stockpile_number) {
|
||||
DEBUG(cycle, out).print("ensuring stockpile config stockpile_number=%d\n", stockpile_number);
|
||||
if (watched_stockpiles.count(stockpile_number)) {
|
||||
DEBUG(cycle, out).print("stockpile exists in watched_stockpiles\n");
|
||||
return watched_stockpiles[stockpile_number];
|
||||
}
|
||||
|
||||
string keyname = CONFIG_KEY_PREFIX + int_to_string(stockpile_number);
|
||||
DEBUG(status, out).print("creating new persistent key for stockpile %d\n", stockpile_number);
|
||||
watched_stockpiles.emplace(stockpile_number, World::GetPersistentData(keyname, NULL));
|
||||
PersistentDataItem& c = watched_stockpiles[stockpile_number];
|
||||
set_config_val(c, STOCKPILE_CONFIG_STOCKPILE_NUMBER, stockpile_number);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_MELT, false);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_TRADE, false);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_DUMP, false);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void remove_stockpile_config(color_ostream& out, int stockpile_number) {
|
||||
if (!watched_stockpiles.count(stockpile_number))
|
||||
return;
|
||||
DEBUG(status, out).print("removing persistent key for stockpile %d\n", stockpile_number);
|
||||
World::DeletePersistentData(watched_stockpiles[stockpile_number]);
|
||||
watched_stockpiles.erase(stockpile_number);
|
||||
}
|
||||
|
||||
static const int32_t CYCLE_TICKS = 600;
|
||||
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
||||
static void do_cycle(color_ostream &out, int32_t &melt_count, int32_t &trade_count, int32_t &dump_count);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands) {
|
||||
DEBUG(status, out).print("initializing %s\n", plugin_name);
|
||||
|
||||
commands.push_back(PluginCommand(
|
||||
plugin_name,
|
||||
"Automatically mark and route items in monitored stockpiles.",
|
||||
do_command));
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
||||
is_enabled = enable;
|
||||
DEBUG(status, out).print("now %s\n", is_enabled ? "enabled" : "disabled");
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static df::building_stockpilest* find_stockpile(int32_t stockpile_number) {
|
||||
return binsearch_in_vector(world->buildings.other.STOCKPILE,
|
||||
&df::building_stockpilest::stockpile_number, stockpile_number);
|
||||
}
|
||||
|
||||
static void validate_stockpile_configs(color_ostream& out,
|
||||
unordered_map<df::building_stockpilest *, PersistentDataItem> &cache) {
|
||||
for (auto& entry : watched_stockpiles) {
|
||||
int stockpile_number = entry.first;
|
||||
PersistentDataItem &c = entry.second;
|
||||
auto bld = find_stockpile(stockpile_number);
|
||||
if (!bld || (
|
||||
!get_config_bool(c, STOCKPILE_CONFIG_MELT) &&
|
||||
!get_config_bool(c, STOCKPILE_CONFIG_TRADE) &&
|
||||
!get_config_bool(c, STOCKPILE_CONFIG_DUMP))) {
|
||||
remove_stockpile_config(out, stockpile_number);
|
||||
continue;
|
||||
}
|
||||
cache.emplace(bld, c);
|
||||
}
|
||||
}
|
||||
|
||||
// remove this function once saves from 50.08 are no longer compatible
|
||||
static void migrate_old_keys(color_ostream &out) {
|
||||
vector<PersistentDataItem> old_data;
|
||||
World::GetPersistentData(&old_data, "automelt/stockpile/", true);
|
||||
const size_t num_old_keys = old_data.size();
|
||||
for (size_t idx = 0; idx < num_old_keys; ++idx) {
|
||||
auto& old_c = old_data[idx];
|
||||
int32_t bld_id = get_config_val(old_c, 0);
|
||||
bool melt_was_on = get_config_bool(old_c, 1);
|
||||
World::DeletePersistentData(old_c);
|
||||
auto bld = df::building::find(bld_id);
|
||||
if (!bld || bld->getType() != df::building_type::Stockpile ||
|
||||
watched_stockpiles.count(static_cast<df::building_stockpilest *>(bld)->stockpile_number))
|
||||
continue;
|
||||
auto &c = ensure_stockpile_config(out, static_cast<df::building_stockpilest *>(bld)->stockpile_number);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_MELT, melt_was_on);
|
||||
}
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_load_data(color_ostream &out) {
|
||||
cycle_timestamp = 0;
|
||||
|
||||
vector<PersistentDataItem> loaded_persist_data;
|
||||
World::GetPersistentData(&loaded_persist_data, CONFIG_KEY_PREFIX, true);
|
||||
watched_stockpiles.clear();
|
||||
const size_t num_watched_stockpiles = loaded_persist_data.size();
|
||||
for (size_t idx = 0; idx < num_watched_stockpiles; ++idx) {
|
||||
auto& c = loaded_persist_data[idx];
|
||||
watched_stockpiles.emplace(get_config_val(c, STOCKPILE_CONFIG_STOCKPILE_NUMBER), c);
|
||||
}
|
||||
migrate_old_keys(out);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
|
||||
if (!is_enabled || !Core::getInstance().isWorldLoaded())
|
||||
return CR_OK;
|
||||
if (world->frame_counter - cycle_timestamp >= CYCLE_TICKS) {
|
||||
int32_t melt_count = 0, trade_count = 0, dump_count = 0;
|
||||
do_cycle(out, melt_count, trade_count, dump_count);
|
||||
if (0 < melt_count)
|
||||
out.print("logistics: marked %d item(s) for melting\n", melt_count);
|
||||
if (0 < trade_count)
|
||||
out.print("logistics: marked %d item(s) for trading\n", trade_count);
|
||||
if (0 < dump_count)
|
||||
out.print("logistics: marked %d item(s) for dumping\n", dump_count);
|
||||
}
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
static bool call_logistics_lua(color_ostream* out, const char* fn_name,
|
||||
int nargs = 0, int nres = 0,
|
||||
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
|
||||
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
|
||||
DEBUG(status).print("calling logistics lua function: '%s'\n", fn_name);
|
||||
|
||||
CoreSuspender guard;
|
||||
|
||||
auto L = Lua::Core::State;
|
||||
Lua::StackUnwinder top(L);
|
||||
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
|
||||
return Lua::CallLuaModuleFunction(*out, L, "plugins.logistics", fn_name,
|
||||
nargs, nres,
|
||||
std::forward<Lua::LuaLambda&&>(args_lambda),
|
||||
std::forward<Lua::LuaLambda&&>(res_lambda));
|
||||
}
|
||||
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
||||
CoreSuspender suspend;
|
||||
|
||||
bool show_help = false;
|
||||
if (!call_logistics_lua(&out, "parse_commandline", 1, 1,
|
||||
[&](lua_State *L) {
|
||||
Lua::PushVector(L, parameters);
|
||||
},
|
||||
[&](lua_State *L) {
|
||||
show_help = !lua_toboolean(L, -1);
|
||||
})) {
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
return show_help ? CR_WRONG_USAGE : CR_OK;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// cycle
|
||||
//
|
||||
|
||||
typedef unordered_map<int32_t, size_t> StatMap;
|
||||
|
||||
struct ProcessorStats {
|
||||
size_t newly_designated = 0;
|
||||
StatMap designated_counts, can_designate_counts;
|
||||
};
|
||||
|
||||
class StockProcessor {
|
||||
public:
|
||||
const string name;
|
||||
const int32_t stockpile_number;
|
||||
const bool enabled;
|
||||
ProcessorStats &stats;
|
||||
protected:
|
||||
StockProcessor(const string &name, int32_t stockpile_number, bool enabled, ProcessorStats &stats)
|
||||
: name(name), stockpile_number(stockpile_number), enabled(enabled), stats(stats) { }
|
||||
public:
|
||||
virtual bool is_designated(color_ostream &out, df::item *item) = 0;
|
||||
virtual bool can_designate(color_ostream &out, df::item *item) = 0;
|
||||
virtual bool designate(color_ostream &out, df::item *item) = 0;
|
||||
};
|
||||
|
||||
class MeltStockProcessor : public StockProcessor {
|
||||
public:
|
||||
MeltStockProcessor(int32_t stockpile_number, bool enabled, ProcessorStats &stats)
|
||||
: StockProcessor("melt", stockpile_number, enabled, stats) { }
|
||||
|
||||
virtual bool is_designated(color_ostream &out, df::item *item) override {
|
||||
return item->flags.bits.melt;
|
||||
}
|
||||
|
||||
virtual bool can_designate(color_ostream &out, df::item *item) override {
|
||||
MaterialInfo mat(item);
|
||||
if (mat.getCraftClass() != df::craft_material_class::Metal)
|
||||
return false;
|
||||
|
||||
if (item->getType() == df::item_type::BAR)
|
||||
return false;
|
||||
|
||||
for (auto &g : item->general_refs) {
|
||||
switch (g->getType()) {
|
||||
case df::general_ref_type::CONTAINS_ITEM:
|
||||
case df::general_ref_type::UNIT_HOLDER:
|
||||
case df::general_ref_type::CONTAINS_UNIT:
|
||||
return false;
|
||||
case df::general_ref_type::CONTAINED_IN_ITEM:
|
||||
{
|
||||
df::item *c = g->getItem();
|
||||
for (auto &gg : c->general_refs) {
|
||||
if (gg->getType() == df::general_ref_type::UNIT_HOLDER)
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->getQuality() >= df::item_quality::Masterful)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool designate(color_ostream &out, df::item *item) override {
|
||||
insert_into_vector(world->items.other.ANY_MELT_DESIGNATED, &df::item::id, item);
|
||||
item->flags.bits.melt = 1;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class TradeStockProcessor: public StockProcessor {
|
||||
public:
|
||||
TradeStockProcessor(int32_t stockpile_number, bool enabled, ProcessorStats& stats)
|
||||
: StockProcessor("trade", stockpile_number, enabled && get_active_trade_depot(), stats),
|
||||
depot(get_active_trade_depot()) { }
|
||||
|
||||
virtual bool is_designated(color_ostream& out, df::item* item) override {
|
||||
auto ref = Items::getSpecificRef(item, df::specific_ref_type::JOB);
|
||||
return ref && ref->data.job &&
|
||||
ref->data.job->job_type == df::job_type::BringItemToDepot;
|
||||
}
|
||||
|
||||
virtual bool can_designate(color_ostream& out, df::item* item) override {
|
||||
return Items::canTradeWithContents(item);
|
||||
}
|
||||
|
||||
virtual bool designate(color_ostream& out, df::item* item) override {
|
||||
if (!depot)
|
||||
return false;
|
||||
|
||||
auto href = df::allocate<df::general_ref_building_holderst>();
|
||||
if (!href)
|
||||
return false;
|
||||
|
||||
auto job = new df::job();
|
||||
job->job_type = df::job_type::BringItemToDepot;
|
||||
job->pos = df::coord(depot->centerx, depot->centery, depot->z);
|
||||
|
||||
// job <-> item link
|
||||
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) {
|
||||
delete job;
|
||||
delete href;
|
||||
return false;
|
||||
}
|
||||
|
||||
// job <-> building link
|
||||
href->building_id = depot->id;
|
||||
depot->jobs.push_back(job);
|
||||
job->general_refs.push_back(href);
|
||||
|
||||
// add to job list
|
||||
Job::linkIntoWorld(job);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
df::building_tradedepotst * const depot;
|
||||
|
||||
static df::building_tradedepotst * get_active_trade_depot() {
|
||||
// at least one caravan must be approaching or ready to trade
|
||||
if (!plotinfo->caravans.size())
|
||||
return NULL;
|
||||
bool found = false;
|
||||
for (auto caravan : plotinfo->caravans) {
|
||||
auto trade_state = caravan->trade_state;
|
||||
auto time_remaining = caravan->time_remaining;
|
||||
if ((trade_state == df::caravan_state::T_trade_state::Approaching ||
|
||||
trade_state == df::caravan_state::T_trade_state::AtDepot) && time_remaining != 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return NULL;
|
||||
|
||||
// at least one trade depot must be ready to receive goods
|
||||
for (auto bld : world->buildings.other.TRADE_DEPOT) {
|
||||
if (bld->getBuildStage() < bld->getMaxBuildStage())
|
||||
continue;
|
||||
|
||||
if (bld->jobs.size() == 1 &&
|
||||
bld->jobs[0]->job_type == df::job_type::DestroyBuilding)
|
||||
continue;
|
||||
|
||||
return bld;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
class DumpStockProcessor: public StockProcessor {
|
||||
public:
|
||||
DumpStockProcessor(int32_t stockpile_number, bool enabled, ProcessorStats& stats)
|
||||
: StockProcessor("dump", stockpile_number, enabled, stats) { }
|
||||
|
||||
virtual bool is_designated(color_ostream& out, df::item* item) override {
|
||||
return item->flags.bits.dump;
|
||||
}
|
||||
|
||||
virtual bool can_designate(color_ostream& out, df::item* item) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool designate(color_ostream& out, df::item* item) override {
|
||||
item->flags.bits.dump = true;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static const struct BadFlags {
|
||||
const uint32_t whole;
|
||||
|
||||
BadFlags() : whole(get_bad_flags()) { }
|
||||
|
||||
private:
|
||||
uint32_t get_bad_flags() {
|
||||
df::item_flags flags;
|
||||
#define F(x) flags.bits.x = true;
|
||||
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
|
||||
return flags.whole;
|
||||
}
|
||||
} bad_flags;
|
||||
|
||||
static void scan_item(color_ostream &out, df::item *item, StockProcessor &processor) {
|
||||
DEBUG(cycle,out).print("scan_item [%s] item_id=%d\n", processor.name.c_str(), item->id);
|
||||
|
||||
if (DBG_NAME(cycle).isEnabled(DebugCategory::LTRACE)) {
|
||||
string name = "";
|
||||
item->getItemDescription(&name, 0);
|
||||
TRACE(cycle,out).print("item: %s\n", name.c_str());
|
||||
}
|
||||
|
||||
if (item->flags.whole & bad_flags.whole) {
|
||||
TRACE(cycle,out).print("rejected flag check\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (processor.is_designated(out, item)) {
|
||||
TRACE(cycle,out).print("already designated\n");
|
||||
++processor.stats.designated_counts[processor.stockpile_number];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!processor.can_designate(out, item)) {
|
||||
TRACE(cycle,out).print("cannot designate\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!processor.enabled) {
|
||||
++processor.stats.can_designate_counts[processor.stockpile_number];
|
||||
return;
|
||||
}
|
||||
|
||||
processor.designate(out, item);
|
||||
++processor.stats.newly_designated;
|
||||
++processor.stats.designated_counts[processor.stockpile_number];
|
||||
}
|
||||
|
||||
static void scan_stockpile(color_ostream &out, df::building_stockpilest *bld,
|
||||
MeltStockProcessor &melt_stock_processor,
|
||||
TradeStockProcessor &trade_stock_processor,
|
||||
DumpStockProcessor &dump_stock_processor) {
|
||||
auto id = bld->id;
|
||||
Buildings::StockpileIterator items;
|
||||
for (items.begin(bld); !items.done(); ++items) {
|
||||
df::item *item = *items;
|
||||
scan_item(out, item, trade_stock_processor);
|
||||
if (0 == (item->flags.whole & bad_flags.whole) &&
|
||||
item->isAssignedToThisStockpile(id)) {
|
||||
TRACE(cycle,out).print("assignedToStockpile\n");
|
||||
vector<df::item *> contents;
|
||||
Items::getContainedItems(item, &contents);
|
||||
for (df::item *contained_item : contents) {
|
||||
scan_item(out, contained_item, melt_stock_processor);
|
||||
scan_item(out, contained_item, dump_stock_processor);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
scan_item(out, item, melt_stock_processor);
|
||||
scan_item(out, item, dump_stock_processor);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_cycle(color_ostream &out, int32_t &melt_count, int32_t &trade_count, int32_t &dump_count) {
|
||||
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
|
||||
cycle_timestamp = world->frame_counter;
|
||||
|
||||
ProcessorStats melt_stats, trade_stats, dump_stats;
|
||||
unordered_map<df::building_stockpilest *, PersistentDataItem> cache;
|
||||
validate_stockpile_configs(out, cache);
|
||||
|
||||
for (auto &entry : cache) {
|
||||
df::building_stockpilest *bld = entry.first;
|
||||
PersistentDataItem &c = entry.second;
|
||||
int32_t stockpile_number = bld->stockpile_number;
|
||||
|
||||
bool melt = get_config_bool(c, STOCKPILE_CONFIG_MELT);
|
||||
bool trade = get_config_bool(c, STOCKPILE_CONFIG_TRADE);
|
||||
bool dump = get_config_bool(c, STOCKPILE_CONFIG_DUMP);
|
||||
|
||||
MeltStockProcessor melt_stock_processor(stockpile_number, melt, melt_stats);
|
||||
TradeStockProcessor trade_stock_processor(stockpile_number, trade, trade_stats);
|
||||
DumpStockProcessor dump_stock_processor(stockpile_number, dump, dump_stats);
|
||||
|
||||
scan_stockpile(out, bld, melt_stock_processor,
|
||||
trade_stock_processor, dump_stock_processor);
|
||||
}
|
||||
|
||||
melt_count = melt_stats.newly_designated;
|
||||
trade_count = trade_stats.newly_designated;
|
||||
dump_count = dump_stats.newly_designated;
|
||||
TRACE(cycle,out).print("exit %s do_cycle\n", plugin_name);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Lua API
|
||||
//
|
||||
|
||||
static int logistics_getStockpileData(lua_State *L) {
|
||||
color_ostream *out = Lua::GetOutput(L);
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
DEBUG(status,*out).print("entering logistics_getStockpileData\n");
|
||||
|
||||
unordered_map<df::building_stockpilest *, PersistentDataItem> cache;
|
||||
validate_stockpile_configs(*out, cache);
|
||||
|
||||
ProcessorStats melt_stats, trade_stats, dump_stats;
|
||||
|
||||
for (auto bld : df::global::world->buildings.other.STOCKPILE) {
|
||||
int32_t stockpile_number = bld->stockpile_number;
|
||||
MeltStockProcessor melt_stock_processor(stockpile_number, false, melt_stats);
|
||||
TradeStockProcessor trade_stock_processor(stockpile_number, false, trade_stats);
|
||||
DumpStockProcessor dump_stock_processor(stockpile_number, false, dump_stats);
|
||||
|
||||
scan_stockpile(*out, bld, melt_stock_processor,
|
||||
trade_stock_processor, dump_stock_processor);
|
||||
}
|
||||
|
||||
unordered_map<string, StatMap> stats;
|
||||
stats.emplace("melt_designated", melt_stats.designated_counts);
|
||||
stats.emplace("melt_can_designate", melt_stats.can_designate_counts);
|
||||
stats.emplace("trade_designated", trade_stats.designated_counts);
|
||||
stats.emplace("trade_can_designate", trade_stats.can_designate_counts);
|
||||
stats.emplace("dump_designated", dump_stats.designated_counts);
|
||||
stats.emplace("dump_can_designate", dump_stats.can_designate_counts);
|
||||
Lua::Push(L, stats);
|
||||
|
||||
unordered_map<int32_t, unordered_map<string, string>> configs;
|
||||
for (auto &entry : cache) {
|
||||
df::building_stockpilest *bld = entry.first;
|
||||
PersistentDataItem &c = entry.second;
|
||||
|
||||
bool melt = get_config_bool(c, STOCKPILE_CONFIG_MELT);
|
||||
bool trade = get_config_bool(c, STOCKPILE_CONFIG_TRADE);
|
||||
bool dump = get_config_bool(c, STOCKPILE_CONFIG_DUMP);
|
||||
|
||||
unordered_map<string, string> config;
|
||||
config.emplace("melt", melt ? "true" : "false");
|
||||
config.emplace("trade", trade ? "true" : "false");
|
||||
config.emplace("dump", dump ? "true" : "false");
|
||||
|
||||
configs.emplace(bld->stockpile_number, config);
|
||||
}
|
||||
Lua::Push(L, configs);
|
||||
|
||||
TRACE(cycle, *out).print("exit logistics_getStockpileData\n");
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void logistics_cycle(color_ostream &out) {
|
||||
DEBUG(status, out).print("entering logistics_cycle\n");
|
||||
int32_t melt_count = 0, trade_count = 0, dump_count = 0;
|
||||
do_cycle(out, melt_count, trade_count, dump_count);
|
||||
out.print("logistics: marked %d item(s) for melting\n", melt_count);
|
||||
out.print("logistics: marked %d item(s) for trading\n", trade_count);
|
||||
out.print("logistics: marked %d item(s) for dumping\n", dump_count);
|
||||
}
|
||||
|
||||
static void find_stockpiles(lua_State *L, int idx,
|
||||
vector<df::building_stockpilest*> &sps) {
|
||||
if (lua_isnumber(L, idx)) {
|
||||
sps.emplace_back(find_stockpile(lua_tointeger(L, -1)));
|
||||
return;
|
||||
}
|
||||
|
||||
const char * pname = lua_tostring(L, -1);
|
||||
if (!pname || !*pname)
|
||||
return;
|
||||
string name(pname);
|
||||
for (auto sp : world->buildings.other.STOCKPILE) {
|
||||
if (name == sp->name)
|
||||
sps.emplace_back(sp);
|
||||
}
|
||||
}
|
||||
|
||||
static unordered_map<string, int> get_stockpile_config(int32_t stockpile_number) {
|
||||
unordered_map<string, int> stockpile_config;
|
||||
stockpile_config.emplace("stockpile_number", stockpile_number);
|
||||
if (watched_stockpiles.count(stockpile_number)) {
|
||||
PersistentDataItem &c = watched_stockpiles[stockpile_number];
|
||||
stockpile_config.emplace("melt", get_config_bool(c, STOCKPILE_CONFIG_MELT));
|
||||
stockpile_config.emplace("trade", get_config_bool(c, STOCKPILE_CONFIG_TRADE));
|
||||
stockpile_config.emplace("dump", get_config_bool(c, STOCKPILE_CONFIG_DUMP));
|
||||
} else {
|
||||
stockpile_config.emplace("melt", false);
|
||||
stockpile_config.emplace("trade", false);
|
||||
stockpile_config.emplace("dump", false);
|
||||
}
|
||||
return stockpile_config;
|
||||
}
|
||||
|
||||
static int logistics_getStockpileConfigs(lua_State *L) {
|
||||
color_ostream *out = Lua::GetOutput(L);
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
DEBUG(status, *out).print("entering logistics_getStockpileConfig\n");
|
||||
|
||||
unordered_map<df::building_stockpilest*, PersistentDataItem> cache;
|
||||
validate_stockpile_configs(*out, cache);
|
||||
|
||||
vector<df::building_stockpilest*> sps;
|
||||
find_stockpiles(L, -1, sps);
|
||||
if (sps.empty())
|
||||
return 0;
|
||||
|
||||
vector<unordered_map<string, int>> configs;
|
||||
for (auto sp : sps)
|
||||
configs.emplace_back(get_stockpile_config(sp->stockpile_number));
|
||||
Lua::PushVector(L, configs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void logistics_setStockpileConfig(color_ostream &out, int stockpile_number, bool melt, bool trade, bool dump) {
|
||||
DEBUG(status, out).print("entering logistics_setStockpileConfig stockpile_number=%d, melt=%d, trade=%d, dump=%d\n",
|
||||
stockpile_number, melt, trade, dump);
|
||||
|
||||
if (!find_stockpile(stockpile_number)) {
|
||||
out.printerr("invalid stockpile number: %d\n", stockpile_number);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &c = ensure_stockpile_config(out, stockpile_number);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_MELT, melt);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_TRADE, trade);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_DUMP, dump);
|
||||
}
|
||||
|
||||
static int logistics_clearStockpileConfig(lua_State *L) {
|
||||
color_ostream *out = Lua::GetOutput(L);
|
||||
if (!out)
|
||||
out = &Core::getInstance().getConsole();
|
||||
DEBUG(status, *out).print("entering logistics_clearStockpileConfig\n");
|
||||
|
||||
vector<df::building_stockpilest*> sps;
|
||||
find_stockpiles(L, -1, sps);
|
||||
if (sps.empty())
|
||||
return 0;
|
||||
|
||||
for (auto sp : sps)
|
||||
remove_stockpile_config(*out, sp->stockpile_number);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void logistics_clearAllStockpileConfigs(color_ostream &out) {
|
||||
DEBUG(status, out).print("entering logistics_clearAllStockpileConfigs\n");
|
||||
for (auto &entry : watched_stockpiles)
|
||||
World::DeletePersistentData(entry.second);
|
||||
watched_stockpiles.clear();
|
||||
}
|
||||
|
||||
DFHACK_PLUGIN_LUA_FUNCTIONS{
|
||||
DFHACK_LUA_FUNCTION(logistics_cycle),
|
||||
DFHACK_LUA_FUNCTION(logistics_setStockpileConfig),
|
||||
DFHACK_LUA_FUNCTION(logistics_clearAllStockpileConfigs),
|
||||
DFHACK_LUA_END};
|
||||
|
||||
DFHACK_PLUGIN_LUA_COMMANDS{
|
||||
DFHACK_LUA_COMMAND(logistics_getStockpileData),
|
||||
DFHACK_LUA_COMMAND(logistics_getStockpileConfigs),
|
||||
DFHACK_LUA_COMMAND(logistics_clearStockpileConfig),
|
||||
DFHACK_LUA_END};
|
@ -1,87 +0,0 @@
|
||||
local _ENV = mkmodule('plugins.automelt')
|
||||
|
||||
local argparse = require('argparse')
|
||||
|
||||
local function process_args(opts, args)
|
||||
if args[1] == 'help' then
|
||||
opts.help = true
|
||||
return
|
||||
end
|
||||
|
||||
return argparse.processArgsGetopt(args, {
|
||||
{'h', 'help', handler=function() opts.help = true end},
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
local function do_set_stockpile_config(var_name, val, stockpiles)
|
||||
for _,bspec in ipairs(argparse.stringList(stockpiles)) do
|
||||
local config = automelt_getStockpileConfig(bspec)
|
||||
if not config then
|
||||
dfhack.printerr('invalid stockpile: '..tostring(bspec))
|
||||
else
|
||||
config[var_name] = val
|
||||
automelt_setStockpileConfig(config.id, config.monitor, config.melt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function parse_commandline(...)
|
||||
local args, opts = {...}, {}
|
||||
local positionals = process_args(opts, args)
|
||||
|
||||
if opts.help then
|
||||
return false
|
||||
end
|
||||
|
||||
local command = positionals[1]
|
||||
if not command or command == 'status' then
|
||||
automelt_printStatus()
|
||||
elseif command == 'designate' then
|
||||
automelt_designate()
|
||||
elseif command == 'monitor' then
|
||||
do_set_stockpile_config('monitor', true, args[2])
|
||||
elseif command == 'nomonitor' or command == 'unmonitor' then
|
||||
do_set_stockpile_config('monitor', false, args[2])
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- used by gui/automelt
|
||||
function setStockpileConfig(config)
|
||||
automelt_setStockpileConfig(config.id, config.monitored)
|
||||
end
|
||||
|
||||
function getItemCountsAndStockpileConfigs()
|
||||
local fmt = 'Stockpile #%-5s'
|
||||
local data = {automelt_getItemCountsAndStockpileConfigs()}
|
||||
local ret = {}
|
||||
ret.summary = table.remove(data, 1)
|
||||
ret.item_counts = table.remove(data, 1)
|
||||
ret.marked_item_counts = table.remove(data, 1)
|
||||
ret.premarked_item_counts = table.remove(data, 1)
|
||||
local unparsed_stockpile_configs = table.remove(data, 1)
|
||||
ret.stockpile_configs = {}
|
||||
|
||||
for idx,c in pairs(unparsed_stockpile_configs) do
|
||||
if not c.id or c.id == -1 then
|
||||
c.name = "ERROR"
|
||||
c.monitored = false
|
||||
else
|
||||
c.name = df.building.find(c.id).name
|
||||
if not c.name or c.name == '' then
|
||||
c.name = (fmt):format(tostring(df.building.find(c.id).stockpile_number))
|
||||
end
|
||||
c.monitored = c.monitored ~= 0
|
||||
end
|
||||
table.insert(ret.stockpile_configs, c)
|
||||
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,146 @@
|
||||
local _ENV = mkmodule('plugins.logistics')
|
||||
|
||||
local argparse = require('argparse')
|
||||
local utils = require('utils')
|
||||
|
||||
local function make_stat(name, stockpile_number, stats, configs)
|
||||
return {
|
||||
enabled=configs[stockpile_number] and configs[stockpile_number][name] == 'true',
|
||||
designated=stats[name..'_designated'][stockpile_number] or 0,
|
||||
can_designate=stats[name..'_can_designate'][stockpile_number] or 0,
|
||||
}
|
||||
end
|
||||
|
||||
function getStockpileData()
|
||||
local stats, configs = logistics_getStockpileData()
|
||||
local data = {}
|
||||
for _,bld in ipairs(df.global.world.buildings.other.STOCKPILE) do
|
||||
local stockpile_number, name = bld.stockpile_number, bld.name
|
||||
local sort_key = name
|
||||
if #name == 0 then
|
||||
name = ('Stockpile #%d'):format(bld.stockpile_number)
|
||||
sort_key = ('Stockpile #%09d'):format(bld.stockpile_number)
|
||||
end
|
||||
table.insert(data, {
|
||||
stockpile_number=stockpile_number,
|
||||
name=name,
|
||||
sort_key=sort_key,
|
||||
melt=make_stat('melt', stockpile_number, stats, configs),
|
||||
trade=make_stat('trade', stockpile_number, stats, configs),
|
||||
dump=make_stat('dump', stockpile_number, stats, configs),
|
||||
})
|
||||
end
|
||||
table.sort(data, function(a, b) return a.sort_key < b.sort_key end)
|
||||
return data
|
||||
end
|
||||
|
||||
local function print_status()
|
||||
print(('logistics is %sactively monitoring stockpiles and marking items')
|
||||
:format(isEnabled() and '' or 'not '))
|
||||
|
||||
if df.global.gamemode ~= df.game_mode.DWARF or not dfhack.isMapLoaded() then
|
||||
return
|
||||
end
|
||||
|
||||
local data = getStockpileData()
|
||||
|
||||
print()
|
||||
if not data[1] then
|
||||
print 'No stockpiles defined -- go make some!'
|
||||
return
|
||||
end
|
||||
|
||||
local name_len = 12
|
||||
for _,sp in ipairs(data) do
|
||||
name_len = math.min(40, math.max(name_len, #sp.name))
|
||||
end
|
||||
|
||||
print('Designated/designatable items in stockpiles:')
|
||||
print()
|
||||
local fmt = '%6s %-' .. name_len .. 's %11s %11s %11s';
|
||||
print(fmt:format('number', 'name', 'melt', 'trade', 'dump'))
|
||||
local function uline(len) return ('-'):rep(len) end
|
||||
print(fmt:format(uline(6), uline(name_len), uline(11), uline(11), uline(11)))
|
||||
local function get_enab(stats) return ('[%s]'):format(stats.enabled and 'x' or ' ') end
|
||||
local function get_dstat(stats) return ('%d/%d '):format(stats.designated, stats.designated + stats.can_designate) end
|
||||
for _,sp in ipairs(data) do
|
||||
print(fmt:format(sp.stockpile_number, sp.name, get_enab(sp.melt), get_enab(sp.trade), get_enab(sp.dump)))
|
||||
print(fmt:format('', '', get_dstat(sp.melt), get_dstat(sp.trade), get_dstat(sp.dump)))
|
||||
end
|
||||
end
|
||||
|
||||
local function for_stockpiles(opts, fn)
|
||||
if not opts.sp then
|
||||
local selected_sp = dfhack.gui.getSelectedStockpile()
|
||||
if not selected_sp then qerror('please specify or select a stockpile') end
|
||||
fn(selected_sp.stockpile_number)
|
||||
return
|
||||
end
|
||||
for _,sp in ipairs(argparse.stringList(opts.sp)) do
|
||||
fn(sp)
|
||||
end
|
||||
end
|
||||
|
||||
local function do_add_stockpile_config(features, opts)
|
||||
for_stockpiles(opts, function(sp)
|
||||
local configs = logistics_getStockpileConfigs(tonumber(sp) or sp)
|
||||
if not configs then
|
||||
dfhack.printerr('invalid stockpile: '..sp)
|
||||
else
|
||||
for _,config in ipairs(configs) do
|
||||
logistics_setStockpileConfig(config.stockpile_number,
|
||||
features.melt or config.melt,
|
||||
features.trade or config.trade,
|
||||
features.dump or config.dump)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function do_clear_stockpile_config(all, opts)
|
||||
if all then
|
||||
logistics_clearAllStockpileConfigs()
|
||||
return
|
||||
end
|
||||
for_stockpiles(opts, function(sp)
|
||||
logistics_clearStockpileConfig(tonumber(sp) or sp)
|
||||
end)
|
||||
end
|
||||
|
||||
local function process_args(opts, args)
|
||||
if args[1] == 'help' then
|
||||
opts.help = true
|
||||
return
|
||||
end
|
||||
|
||||
return argparse.processArgsGetopt(args, {
|
||||
{'h', 'help', handler=function() opts.help = true end},
|
||||
{'s', 'stockpile', hasArg=true, handler=function(arg) opts.sp = arg end},
|
||||
})
|
||||
end
|
||||
|
||||
function parse_commandline(args)
|
||||
local opts = {}
|
||||
local positionals = process_args(opts, args)
|
||||
|
||||
if opts.help or not positionals then
|
||||
return false
|
||||
end
|
||||
|
||||
local command = table.remove(positionals, 1)
|
||||
if not command or command == 'status' then
|
||||
print_status()
|
||||
elseif command == 'now' then
|
||||
logistics_cycle()
|
||||
elseif command == 'add' then
|
||||
do_add_stockpile_config(utils.invert(positionals), opts)
|
||||
elseif command == 'clear' then
|
||||
do_clear_stockpile_config(utils.invert(positionals).all, opts)
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return _ENV
|
Loading…
Reference in New Issue