Merge pull request #3285 from myk002/myk_stockpiles
[logistics] unified plugin for automelt/autotrade/autodump; stockpiles integrationdevelop
commit
1673c797e9
@ -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,68 @@
|
|||||||
|
logistics
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. dfhack-tool::
|
||||||
|
:summary: Automatically mark and route items in monitored stockpiles.
|
||||||
|
:tags: fort auto animals items stockpiles
|
||||||
|
|
||||||
|
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, autodump, and/or autotrain features twice every in-game day, and
|
||||||
|
will mark valid items/animals in those stockpiles for melting, trading,
|
||||||
|
dumping, and/or training, respectively. Note that items will only be marked for
|
||||||
|
trading if a caravan is approaching or is already at the trade depot.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
enable logistics
|
||||||
|
logistics [status]
|
||||||
|
logistics now
|
||||||
|
logistics add [melt] [trade] [dump] [train] [<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,778 @@
|
|||||||
|
#include "Debug.h"
|
||||||
|
#include "LuaTools.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/Buildings.h"
|
||||||
|
#include "modules/Job.h"
|
||||||
|
#include "modules/Persistence.h"
|
||||||
|
#include "modules/Units.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/training_assignment.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,
|
||||||
|
STOCKPILE_CONFIG_TRAIN = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
set_config_bool(c, STOCKPILE_CONFIG_TRAIN, 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, int32_t& train_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) &&
|
||||||
|
!get_config_bool(c, STOCKPILE_CONFIG_TRAIN))) {
|
||||||
|
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, train_count = 0;
|
||||||
|
do_cycle(out, melt_count, trade_count, dump_count, train_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);
|
||||||
|
if (0 < train_count)
|
||||||
|
out.print("logistics: marked %d animal(s) for training\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) { }
|
||||||
|
|
||||||
|
bool is_designated(color_ostream &out, df::item *item) override {
|
||||||
|
return item->flags.bits.melt;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) { }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_designate(color_ostream& out, df::item* item) override {
|
||||||
|
return Items::canTradeWithContents(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) { }
|
||||||
|
|
||||||
|
bool is_designated(color_ostream& out, df::item* item) override {
|
||||||
|
return item->flags.bits.dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_designate(color_ostream& out, df::item* item) override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool designate(color_ostream& out, df::item* item) override {
|
||||||
|
item->flags.bits.dump = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TrainStockProcessor : public StockProcessor {
|
||||||
|
public:
|
||||||
|
TrainStockProcessor(int32_t stockpile_number, bool enabled, ProcessorStats& stats)
|
||||||
|
: StockProcessor("train", stockpile_number, enabled, stats) {}
|
||||||
|
|
||||||
|
bool is_designated(color_ostream& out, df::item* item) override {
|
||||||
|
auto unit = get_caged_unit(item);
|
||||||
|
return unit && has_training_assignment(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_designate(color_ostream& out, df::item* item) override {
|
||||||
|
auto unit = get_caged_unit(item);
|
||||||
|
return unit && Units::isTamable(unit) && !Units::isTame(unit)
|
||||||
|
&& !has_training_assignment(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool designate(color_ostream& out, df::item* item) override {
|
||||||
|
auto unit = get_caged_unit(item);
|
||||||
|
if (!unit)
|
||||||
|
return false;
|
||||||
|
df::training_assignment *assignment = new df::training_assignment();
|
||||||
|
assignment->animal_id = unit->id;
|
||||||
|
assignment->trainer_id = -1;
|
||||||
|
assignment->flags.bits.any_trainer = true;
|
||||||
|
insert_into_vector(df::global::plotinfo->equipment.training_assignments,
|
||||||
|
&df::training_assignment::animal_id, assignment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static df::unit* get_caged_unit(df::item* item) {
|
||||||
|
if (item->getType() != df::item_type::CAGE)
|
||||||
|
return NULL;
|
||||||
|
auto gref = Items::getGeneralRef(item, df::general_ref_type::CONTAINS_UNIT);
|
||||||
|
if (!gref)
|
||||||
|
return NULL;
|
||||||
|
return gref->getUnit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool has_training_assignment(df::unit* unit) {
|
||||||
|
return binsearch_index(df::global::plotinfo->equipment.training_assignments,
|
||||||
|
&df::training_assignment::animal_id, unit->id) > -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
TrainStockProcessor &train_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);
|
||||||
|
scan_item(out, item, train_stock_processor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_cycle(color_ostream& out, int32_t& melt_count, int32_t& trade_count, int32_t& dump_count, int32_t& train_count) {
|
||||||
|
DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
|
||||||
|
cycle_timestamp = world->frame_counter;
|
||||||
|
|
||||||
|
ProcessorStats melt_stats, trade_stats, dump_stats, train_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);
|
||||||
|
bool train = get_config_bool(c, STOCKPILE_CONFIG_TRAIN);
|
||||||
|
|
||||||
|
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);
|
||||||
|
TrainStockProcessor train_stock_processor(stockpile_number, train, train_stats);
|
||||||
|
|
||||||
|
scan_stockpile(out, bld, melt_stock_processor,
|
||||||
|
trade_stock_processor, dump_stock_processor, train_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, train_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);
|
||||||
|
TrainStockProcessor train_stock_processor(stockpile_number, false, train_stats);
|
||||||
|
|
||||||
|
scan_stockpile(*out, bld, melt_stock_processor,
|
||||||
|
trade_stock_processor, dump_stock_processor, train_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);
|
||||||
|
stats.emplace("train_designated", train_stats.designated_counts);
|
||||||
|
stats.emplace("train_can_designate", train_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);
|
||||||
|
bool train = get_config_bool(c, STOCKPILE_CONFIG_TRAIN);
|
||||||
|
|
||||||
|
unordered_map<string, string> config;
|
||||||
|
config.emplace("melt", melt ? "true" : "false");
|
||||||
|
config.emplace("trade", trade ? "true" : "false");
|
||||||
|
config.emplace("dump", dump ? "true" : "false");
|
||||||
|
config.emplace("train", train ? "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, train_count = 0;
|
||||||
|
do_cycle(out, melt_count, trade_count, dump_count, train_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);
|
||||||
|
out.print("logistics: marked %d animal(s) for train\n", train_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));
|
||||||
|
stockpile_config.emplace("train", get_config_bool(c, STOCKPILE_CONFIG_TRAIN));
|
||||||
|
} else {
|
||||||
|
stockpile_config.emplace("melt", false);
|
||||||
|
stockpile_config.emplace("trade", false);
|
||||||
|
stockpile_config.emplace("dump", false);
|
||||||
|
stockpile_config.emplace("train", 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_getStockpileConfigs\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, bool train) {
|
||||||
|
DEBUG(status, out).print("entering logistics_setStockpileConfig stockpile_number=%d, melt=%d, trade=%d, dump=%d, train=%d\n",
|
||||||
|
stockpile_number, melt, trade, dump, train);
|
||||||
|
|
||||||
|
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);
|
||||||
|
set_config_bool(c, STOCKPILE_CONFIG_TRAIN, train);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int logistics_getGlobalCounts(lua_State *L) {
|
||||||
|
color_ostream *out = Lua::GetOutput(L);
|
||||||
|
if (!out)
|
||||||
|
out = &Core::getInstance().getConsole();
|
||||||
|
DEBUG(status,*out).print("entering logistics_getGlobalCounts\n");
|
||||||
|
|
||||||
|
size_t num_melt = df::global::world->items.other.ANY_MELT_DESIGNATED.size();
|
||||||
|
|
||||||
|
size_t num_trade = 0;
|
||||||
|
for (auto link = world->jobs.list.next; link; link = link->next) {
|
||||||
|
df::job *job = link->item;
|
||||||
|
if (job->job_type == df::job_type::BringItemToDepot)
|
||||||
|
++num_trade;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t num_dump = 0;
|
||||||
|
for (auto item : world->items.other.IN_PLAY) {
|
||||||
|
if (item->flags.bits.dump)
|
||||||
|
++num_dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t num_train = 0;
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
unordered_map<string, size_t> results;
|
||||||
|
results.emplace("total_melt", num_melt);
|
||||||
|
results.emplace("total_trade", num_trade);
|
||||||
|
results.emplace("total_dump", num_dump);
|
||||||
|
results.emplace("total_train", num_train);
|
||||||
|
Lua::Push(L, results);
|
||||||
|
|
||||||
|
TRACE(cycle, *out).print("exit logistics_getGlobalCounts\n");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_COMMAND(logistics_getGlobalCounts),
|
||||||
|
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,156 @@
|
|||||||
|
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 = tostring(name):lower()
|
||||||
|
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),
|
||||||
|
train=make_stat('train', 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_stockpile_data(data)
|
||||||
|
local name_len = 4
|
||||||
|
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 %4s %10s %5s %11s %4s %10s %5s %11s';
|
||||||
|
print(fmt:format('number', 'name', 'melt', 'melt items', 'trade', 'trade items', 'dump', 'dump items', 'train', 'train items'))
|
||||||
|
local function uline(len) return ('-'):rep(len) end
|
||||||
|
print(fmt:format(uline(6), uline(name_len), uline(4), uline(10), uline(5), uline(11), uline(4), uline(10), uline(5), 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_dstat(sp.melt), get_enab(sp.trade), get_dstat(sp.trade), get_enab(sp.dump), get_dstat(sp.dump), get_enab(sp.train), get_dstat(sp.train)))
|
||||||
|
end
|
||||||
|
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!'
|
||||||
|
else
|
||||||
|
print_stockpile_data(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
local global_stats = logistics_getGlobalCounts()
|
||||||
|
print()
|
||||||
|
print(('Total items marked for melting: %5d'):format(global_stats.total_melt))
|
||||||
|
print(('Total items marked for trading: %5d'):format(global_stats.total_trade))
|
||||||
|
print(('Total items marked for dumping: %5d'):format(global_stats.total_dump))
|
||||||
|
print(('Total animals marked for training: %5d'):format(global_stats.total_train))
|
||||||
|
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 == 1,
|
||||||
|
features.trade or config.trade == 1,
|
||||||
|
features.dump or config.dump == 1,
|
||||||
|
features.train or config.train == 1)
|
||||||
|
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
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue