initial automelt
parent
ac483ed105
commit
3fc4d056c3
@ -1,294 +1,704 @@
|
||||
#include "uicommon.h"
|
||||
|
||||
#include "modules/Gui.h"
|
||||
#include "Debug.h"
|
||||
#include "LuaTools.h"
|
||||
#include "PluginManager.h"
|
||||
#include "TileTypes.h"
|
||||
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/Items.h"
|
||||
#include "modules/World.h"
|
||||
#include "modules/Designations.h"
|
||||
#include "modules/Persistence.h"
|
||||
#include "modules/Units.h"
|
||||
|
||||
// #include "uicommon.h"
|
||||
|
||||
#include "df/world.h"
|
||||
#include "df/building.h"
|
||||
#include "df/world_raws.h"
|
||||
#include "df/building_def.h"
|
||||
#include "df/viewscreen_dwarfmodest.h"
|
||||
#include "df/building_stockpilest.h"
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Items.h"
|
||||
#include "df/plotinfost.h"
|
||||
#include "modules/Maps.h"
|
||||
#include "modules/World.h"
|
||||
#include "df/item_quality.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
using df::building_stockpilest;
|
||||
using std::map;
|
||||
using std::multimap;
|
||||
using std::pair;
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
DFHACK_PLUGIN("automelt");
|
||||
#define PLUGIN_VERSION 0.3
|
||||
REQUIRE_GLOBAL(gps);
|
||||
REQUIRE_GLOBAL(world);
|
||||
REQUIRE_GLOBAL(cursor);
|
||||
REQUIRE_GLOBAL(plotinfo);
|
||||
|
||||
static const string PERSISTENCE_KEY = "automelt/stockpiles";
|
||||
namespace DFHack
|
||||
{
|
||||
DBG_DECLARE(automelt, status, DebugCategory::LINFO);
|
||||
DBG_DECLARE(automelt, cycle, DebugCategory::LINFO);
|
||||
DBG_DECLARE(automelt, perf, DebugCategory::LTRACE);
|
||||
}
|
||||
|
||||
static const string CONFIG_KEY = string(plugin_name) + "/config";
|
||||
static const string STOCKPILE_CONFIG_KEY_PREFIX = string(plugin_name) + "/stockpile/";
|
||||
static PersistentDataItem config;
|
||||
|
||||
static vector<PersistentDataItem> watched_stockpiles;
|
||||
static unordered_map<int, size_t> watched_stockpiles_indices;
|
||||
|
||||
|
||||
static int mark_item(df::item *item, df::item_flags bad_flags, int32_t stockpile_id)
|
||||
enum ConfigValues
|
||||
{
|
||||
if (item->flags.whole & bad_flags.whole)
|
||||
return 0;
|
||||
CONFIG_IS_ENABLED = 0,
|
||||
|
||||
if (item->isAssignedToThisStockpile(stockpile_id)) {
|
||||
size_t marked_count = 0;
|
||||
std::vector<df::item*> contents;
|
||||
Items::getContainedItems(item, &contents);
|
||||
for (auto child = contents.begin(); child != contents.end(); child++)
|
||||
{
|
||||
marked_count += mark_item(*child, bad_flags, stockpile_id);
|
||||
}
|
||||
};
|
||||
|
||||
return marked_count;
|
||||
}
|
||||
enum StockpileConfigValues
|
||||
{
|
||||
STOCKPILE_CONFIG_ID = 0,
|
||||
STOCKPILE_CONFIG_MONITORED = 1,
|
||||
STOCKPILE_CONFIG_MELT = 2,
|
||||
|
||||
if (!can_melt(item))
|
||||
return 0;
|
||||
};
|
||||
|
||||
if (is_set_to_melt(item))
|
||||
return 0;
|
||||
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);
|
||||
}
|
||||
|
||||
insert_into_vector(world->items.other[items_other_id::ANY_MELT_DESIGNATED], &df::item::id, item);
|
||||
item->flags.bits.melt = true;
|
||||
return 1;
|
||||
static PersistentDataItem &ensure_stockpile_config(color_ostream &out, int id)
|
||||
{
|
||||
if (watched_stockpiles_indices.count(id))
|
||||
return watched_stockpiles[watched_stockpiles_indices[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_back(World::GetPersistentData(keyname, NULL));
|
||||
size_t idx = watched_stockpiles.size() - 1;
|
||||
watched_stockpiles_indices.emplace(id, idx);
|
||||
return watched_stockpiles[idx];
|
||||
}
|
||||
|
||||
static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
|
||||
static void remove_stockpile_config(color_ostream &out, int id)
|
||||
{
|
||||
// Precompute a bitmask with the bad flags
|
||||
df::item_flags bad_flags;
|
||||
bad_flags.whole = 0;
|
||||
if (!watched_stockpiles_indices.count(id))
|
||||
return;
|
||||
DEBUG(status, out).print("removing persistent key for stockpile %d\n", id);
|
||||
size_t idx = watched_stockpiles_indices[id];
|
||||
World::DeletePersistentData(watched_stockpiles[idx]);
|
||||
watched_stockpiles.erase(watched_stockpiles.begin() + idx);
|
||||
watched_stockpiles_indices.erase(id);
|
||||
}
|
||||
|
||||
#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
|
||||
static void validate_stockpile_configs(color_ostream &out)
|
||||
{
|
||||
for (auto &c : watched_stockpiles) {
|
||||
int id = get_config_val(c, STOCKPILE_CONFIG_ID);
|
||||
if (!df::building::find(id)){
|
||||
remove_stockpile_config(out, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t marked_count = 0;
|
||||
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
|
||||
{
|
||||
if (!it->isValid())
|
||||
continue;
|
||||
static const int32_t CYCLE_TICKS = 1200;
|
||||
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
||||
|
||||
auto spid = it->getId();
|
||||
Buildings::StockpileIterator stored;
|
||||
for (stored.begin(it->getStockpile()); !stored.done(); ++stored)
|
||||
{
|
||||
marked_count += mark_item(*stored, bad_flags, spid);
|
||||
}
|
||||
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
||||
static int32_t do_cycle(color_ostream &out);
|
||||
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
|
||||
DFhackCExport command_result plugin_init(color_ostream &out, std::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 (!Core::getInstance().isWorldLoaded())
|
||||
{
|
||||
out.printerr("Cannot enable %s without a loaded world.\n", plugin_name);
|
||||
return CR_FAILURE;
|
||||
}
|
||||
|
||||
if (marked_count)
|
||||
Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items to melt", COLOR_GREEN, false);
|
||||
if (enable != is_enabled)
|
||||
{
|
||||
is_enabled = enable;
|
||||
DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled");
|
||||
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stockpile Monitoring
|
||||
*/
|
||||
DFhackCExport command_result plugin_shutdown(color_ostream &out)
|
||||
{
|
||||
DEBUG(status, out).print("shutting down %s\n", plugin_name);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
class StockpileMonitor
|
||||
DFhackCExport command_result plugin_load_data(color_ostream &out)
|
||||
{
|
||||
public:
|
||||
bool isMonitored(df::building_stockpilest *sp)
|
||||
config = World::GetPersistentData(CONFIG_KEY);
|
||||
|
||||
if (!config.isValid())
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
{
|
||||
if (it->matches(sp))
|
||||
return true;
|
||||
}
|
||||
DEBUG(status, out).print("no config found in this save; initializing\n");
|
||||
config = World::AddPersistentData(CONFIG_KEY);
|
||||
set_config_bool(config, CONFIG_IS_ENABLED, is_enabled);
|
||||
}
|
||||
|
||||
return false;
|
||||
// we have to copy our enabled flag into the global plugin variable, but
|
||||
// all the other state we can directly read/modify from the persistent
|
||||
// data structure.
|
||||
is_enabled = get_config_bool(config, CONFIG_IS_ENABLED);
|
||||
DEBUG(status, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false");
|
||||
World::GetPersistentData(&watched_stockpiles, STOCKPILE_CONFIG_KEY_PREFIX, true);
|
||||
watched_stockpiles_indices.clear();
|
||||
const size_t num_watched_stockpiles = watched_stockpiles.size();
|
||||
for (size_t idx = 0; idx < num_watched_stockpiles; ++idx)
|
||||
{
|
||||
auto &c = watched_stockpiles[idx];
|
||||
watched_stockpiles_indices.emplace(get_config_val(c, STOCKPILE_CONFIG_ID), idx);
|
||||
}
|
||||
validate_stockpile_configs(out);
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
void add(df::building_stockpilest *sp)
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
if (event == DFHack::SC_WORLD_UNLOADED)
|
||||
{
|
||||
auto pile = PersistentStockpileInfo(sp, PERSISTENCE_KEY);
|
||||
if (pile.isValid())
|
||||
if (is_enabled)
|
||||
{
|
||||
monitored_stockpiles.push_back(PersistentStockpileInfo(pile));
|
||||
monitored_stockpiles.back().save();
|
||||
DEBUG(status, out).print("world unloaded; disabling %s\n", plugin_name);
|
||||
is_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onupdate(color_ostream &out)
|
||||
{
|
||||
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)
|
||||
{
|
||||
MaterialInfo mat(item);
|
||||
return (mat.getCraftClass() == craft_material_class::Metal);
|
||||
}
|
||||
|
||||
static bool isStockpile(df::building * building) {
|
||||
return building->getType() == df::building_type::Stockpile;
|
||||
}
|
||||
|
||||
// Copied from Kelly Martin's code
|
||||
static inline bool can_melt(df::item *item)
|
||||
{
|
||||
|
||||
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(in_job);
|
||||
F(hostile);
|
||||
F(on_fire);
|
||||
F(rotten);
|
||||
F(trader);
|
||||
F(in_building);
|
||||
F(construction);
|
||||
F(artifact);
|
||||
F(melt);
|
||||
#undef F
|
||||
|
||||
if (item->flags.whole & bad_flags.whole)
|
||||
return false;
|
||||
|
||||
df::item_type t = item->getType();
|
||||
|
||||
if (t == df::enums::item_type::BOX || t == df::enums::item_type::BAR)
|
||||
return false;
|
||||
|
||||
if (!is_metal_item(item))
|
||||
return false;
|
||||
|
||||
void remove(df::building_stockpilest *sp)
|
||||
for (auto g = item->general_refs.begin(); g != item->general_refs.end(); g++)
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
|
||||
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:
|
||||
{
|
||||
if (it->matches(sp))
|
||||
df::item *c = (*g)->getItem();
|
||||
for (auto gg = c->general_refs.begin(); gg != c->general_refs.end(); gg++)
|
||||
{
|
||||
it->remove();
|
||||
monitored_stockpiles.erase(it);
|
||||
break;
|
||||
if ((*gg)->getType() == general_ref_type::UNIT_HOLDER)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void doCycle()
|
||||
{
|
||||
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();)
|
||||
{
|
||||
if (!it->isValid())
|
||||
it = monitored_stockpiles.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
mark_all_in_stockpiles(monitored_stockpiles);
|
||||
static int mark_item(color_ostream &out, df::item *item, df::item_flags bad_flags, int32_t stockpile_id, int32_t &premarked_item_count, int32_t &item_count, bool should_melt)
|
||||
{
|
||||
TRACE(perf,out).print("%s running mark_item\nshould_melt=%d\n", plugin_name,should_melt);
|
||||
string name = "";
|
||||
item->getItemDescription(&name, 0);
|
||||
TRACE(perf,out).print("item %s %d\n", name, item->id);
|
||||
if (item->flags.whole & bad_flags.whole){
|
||||
TRACE(perf,out).print("rejected flag check\n");
|
||||
item_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void reset()
|
||||
if (item->isAssignedToThisStockpile(stockpile_id))
|
||||
{
|
||||
monitored_stockpiles.clear();
|
||||
std::vector<PersistentDataItem> items;
|
||||
DFHack::World::GetPersistentData(&items, PERSISTENCE_KEY);
|
||||
|
||||
for (auto i = items.begin(); i != items.end(); i++)
|
||||
TRACE(perf,out).print("assignedToStockpile\n");
|
||||
size_t marked_count = 0;
|
||||
std::vector<df::item *> contents;
|
||||
Items::getContainedItems(item, &contents);
|
||||
for (auto child = contents.begin(); child != contents.end(); child++)
|
||||
{
|
||||
auto pile = PersistentStockpileInfo(*i, PERSISTENCE_KEY);
|
||||
if (pile.load())
|
||||
monitored_stockpiles.push_back(PersistentStockpileInfo(pile));
|
||||
else
|
||||
pile.remove();
|
||||
TRACE(perf,out).print("inside child loop\n");
|
||||
marked_count += mark_item(out, *child, bad_flags, stockpile_id, premarked_item_count, item_count, should_melt);
|
||||
}
|
||||
return marked_count;
|
||||
}
|
||||
|
||||
TRACE(perf,out).print("past recurse loop\n");
|
||||
|
||||
private:
|
||||
vector<PersistentStockpileInfo> monitored_stockpiles;
|
||||
};
|
||||
if (is_set_to_melt(item)) {
|
||||
TRACE(perf,out).print("already set to melt\n");
|
||||
premarked_item_count++;
|
||||
item_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!can_melt(item)) {
|
||||
TRACE(perf,out).print("cannot melt\n");
|
||||
item_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static StockpileMonitor monitor;
|
||||
TRACE(perf,out).print("increment item count\n");
|
||||
item_count++;
|
||||
|
||||
#define DELTA_TICKS 610
|
||||
//Only melt if told to, else count
|
||||
if (should_melt) {
|
||||
TRACE(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;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||
}
|
||||
|
||||
|
||||
static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & stockpile, int32_t &premarked_item_count, int32_t &item_count, bool should_melt)
|
||||
{
|
||||
if(!Maps::IsValid())
|
||||
return CR_OK;
|
||||
TRACE(perf,out).print("%s running mark_all_in_stockpile\nshould_melt=%d", plugin_name, should_melt);
|
||||
// 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
|
||||
|
||||
if (DFHack::World::ReadPauseState())
|
||||
return CR_OK;
|
||||
size_t marked_count = 0;
|
||||
|
||||
if(!stockpile.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (world->frame_counter % DELTA_TICKS != 0)
|
||||
return CR_OK;
|
||||
int spid = get_config_val(stockpile, STOCKPILE_CONFIG_ID);
|
||||
auto found = df::building::find(spid);
|
||||
if (!isStockpile(found)){
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
monitor.doCycle();
|
||||
df::building_stockpilest * pile_cast = virtual_cast<df::building_stockpilest>(found);
|
||||
|
||||
return CR_OK;
|
||||
if (!pile_cast)
|
||||
return 0;
|
||||
|
||||
Buildings::StockpileIterator stored;
|
||||
for (stored.begin(pile_cast); !stored.done(); ++stored) {
|
||||
|
||||
marked_count += mark_item(out, *stored, bad_flags, spid, premarked_item_count, item_count, should_melt);
|
||||
}
|
||||
TRACE(perf,out).print("marked_count %d\npremarked_count %d\n", marked_count, premarked_item_count);
|
||||
return marked_count;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Interface
|
||||
*/
|
||||
static int32_t scan_stockpiles(color_ostream &out, bool disable_melt, map<int32_t, int32_t> &item_counts, map<int32_t, int32_t> &marked_item_counts, map<int32_t, int32_t> &premarked_item_counts) {
|
||||
TRACE(perf, out).print("%s running scan_stockpiles\n", plugin_name);
|
||||
int32_t newly_marked = 0;
|
||||
|
||||
// if (item_counts)
|
||||
item_counts.clear();
|
||||
// if (marked_item_counts)
|
||||
marked_item_counts.clear();
|
||||
// if (premarked_item_counts)
|
||||
premarked_item_counts.clear();
|
||||
|
||||
struct melt_hook : public df::viewscreen_dwarfmodest
|
||||
{
|
||||
typedef df::viewscreen_dwarfmodest interpose_base;
|
||||
|
||||
bool handleInput(set<df::interface_key> *input)
|
||||
{
|
||||
if (Gui::inRenameBuilding())
|
||||
return false;
|
||||
//Parse all the watched piles
|
||||
for (auto &c : watched_stockpiles) {
|
||||
int id = get_config_val(c, STOCKPILE_CONFIG_ID);
|
||||
//Check for monitor status
|
||||
bool monitored = get_config_bool(c, STOCKPILE_CONFIG_MONITORED);
|
||||
//Check melt status
|
||||
bool melt_enabled = get_config_bool(c, STOCKPILE_CONFIG_MELT);
|
||||
|
||||
building_stockpilest *sp = get_selected_stockpile();
|
||||
if (!sp)
|
||||
return false;
|
||||
melt_enabled = melt_enabled && (!disable_melt);
|
||||
|
||||
if (input->count(interface_key::CUSTOM_SHIFT_M))
|
||||
{
|
||||
if (monitor.isMonitored(sp))
|
||||
monitor.remove(sp);
|
||||
else
|
||||
monitor.add(sp);
|
||||
}
|
||||
if (!monitored) continue;
|
||||
|
||||
TRACE(perf,out).print("%s past monitor check\nmelt_enabled=%d", plugin_name, melt_enabled);
|
||||
|
||||
int32_t item_count = 0;
|
||||
|
||||
int32_t premarked_count = 0;
|
||||
|
||||
int32_t marked = mark_all_in_stockpile(out, c, premarked_count, item_count, melt_enabled);
|
||||
|
||||
item_counts.emplace(id, item_count);
|
||||
|
||||
marked_item_counts.emplace(id, marked);
|
||||
|
||||
premarked_item_counts.emplace(id, premarked_count);
|
||||
|
||||
newly_marked += marked;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (!handleInput(input))
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
return newly_marked;
|
||||
}
|
||||
|
||||
|
||||
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_items_total;
|
||||
map<int32_t, int32_t> item_counts, marked_items_counts, premarked_item_counts;
|
||||
|
||||
marked_items_total = scan_stockpiles(out, false, item_counts, marked_items_counts, premarked_item_counts);
|
||||
|
||||
return marked_items_total;
|
||||
}
|
||||
|
||||
static void push_stockpile_config(lua_State *L, int id, bool monitored,
|
||||
bool melt) {
|
||||
map<string, int32_t> stockpile_config;
|
||||
stockpile_config.emplace("id", id);
|
||||
stockpile_config.emplace("monitored", monitored);
|
||||
stockpile_config.emplace("melt", melt);
|
||||
|
||||
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),
|
||||
get_config_bool(c, STOCKPILE_CONFIG_MELT));
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
map<int32_t, int32_t> item_counts, marked_item_counts, premarked_item_counts;
|
||||
int32_t marked_items = scan_stockpiles(out, true, item_counts, marked_item_counts, premarked_item_counts);
|
||||
|
||||
int32_t total_items_all_piles = 0;
|
||||
int32_t premarked_items_all_piles = 0;
|
||||
|
||||
|
||||
for (auto &i : watched_stockpiles) {
|
||||
int id = get_config_val(i, STOCKPILE_CONFIG_ID);
|
||||
total_items_all_piles+= item_counts[id];
|
||||
premarked_items_all_piles += premarked_item_counts[id];
|
||||
}
|
||||
|
||||
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 - 6;
|
||||
|
||||
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 += 5;
|
||||
OutputString(COLOR_LIGHTRED, x, y, "M");
|
||||
OutputString(state? COLOR_LIGHTGREEN: COLOR_GREY, x, y, "elt ");
|
||||
} else {
|
||||
OutputToggleString(x, y, "Auto melt", "M", state, true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
||||
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", premarked_items_all_piles);
|
||||
|
||||
int name_width = 11;
|
||||
for (auto &stockpile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(stockpile)) continue;
|
||||
name_width = std::max(name_width, (int)stockpile->name.size());
|
||||
}
|
||||
name_width = -name_width;
|
||||
|
||||
const char *fmt = "%*s %4s %4s %4s\n";
|
||||
out.print(fmt, name_width, "name", " id ", "monitored", "melt");
|
||||
out.print(fmt, name_width, "----", "----", "---------", "----");
|
||||
|
||||
for (auto &stockpile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(stockpile)) continue;
|
||||
bool melt = false;
|
||||
bool monitored = false;
|
||||
if (watched_stockpiles_indices.count(stockpile->id)) {
|
||||
auto &c = watched_stockpiles[watched_stockpiles_indices[stockpile->id]];
|
||||
melt = get_config_bool(c, STOCKPILE_CONFIG_MELT);
|
||||
monitored = get_config_bool(c, STOCKPILE_CONFIG_MONITORED);
|
||||
}
|
||||
|
||||
out.print(fmt, name_width, stockpile->name.c_str(), int_to_string(stockpile->id).c_str(),
|
||||
monitored ? "[x]" : "[ ]", melt ? "[x]": "[ ]");
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, feed);
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, render);
|
||||
}
|
||||
|
||||
static void automelt_setStockpileConfig(color_ostream &out, int id, bool monitored, bool melt) {
|
||||
DEBUG(status,out).print("entering automelt_setStockpileConfig\n");
|
||||
validate_stockpile_configs(out);
|
||||
bool isInvalidStockpile = !df::building::find(id);
|
||||
bool hasNoData = !melt && !monitored;
|
||||
if (isInvalidStockpile || hasNoData) {
|
||||
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);
|
||||
set_config_bool(c, STOCKPILE_CONFIG_MELT, melt);
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case DFHack::SC_MAP_LOADED:
|
||||
monitor.reset();
|
||||
break;
|
||||
case DFHack::SC_MAP_UNLOADED:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
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)) {
|
||||
id = lua_tointeger(L, -1);
|
||||
if (!df::building::find(id))
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
if (!found)
|
||||
return 0;
|
||||
}
|
||||
return CR_OK;
|
||||
|
||||
if (watched_stockpiles_indices.count(id)) {
|
||||
push_stockpile_config(L, watched_stockpiles[watched_stockpiles_indices[id]]);
|
||||
} else {
|
||||
push_stockpile_config(L, id, false, false);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||
//TODO
|
||||
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);
|
||||
|
||||
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
||||
{
|
||||
if (enable != is_enabled)
|
||||
{
|
||||
if (!INTERPOSE_HOOK(melt_hook, feed).apply(enable) ||
|
||||
!INTERPOSE_HOOK(melt_hook, render).apply(enable))
|
||||
return CR_FAILURE;
|
||||
map<int32_t, int32_t> item_counts, marked_item_counts, premarked_item_counts;
|
||||
int32_t marked_items = scan_stockpiles(*out, true, item_counts, marked_item_counts, premarked_item_counts);
|
||||
|
||||
is_enabled = enable;
|
||||
int32_t total_items_all_piles = 0;
|
||||
int32_t premarked_items_all_piles = 0;
|
||||
|
||||
|
||||
for (auto &i : watched_stockpiles) {
|
||||
int id = get_config_val(i, STOCKPILE_CONFIG_ID);
|
||||
total_items_all_piles+= item_counts[id];
|
||||
premarked_items_all_piles += premarked_item_counts[id];
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
map<string, int32_t> summary;
|
||||
summary.emplace("total_items", total_items_all_piles);
|
||||
summary.emplace("marked_items", marked_items);
|
||||
summary.emplace("premarked_items", premarked_items_all_piles);
|
||||
|
||||
Lua::Push(L, summary);
|
||||
Lua::Push(L, item_counts);
|
||||
Lua::Push(L, marked_item_counts);
|
||||
Lua::Push(L, premarked_item_counts);
|
||||
int32_t bldg_count = 0;
|
||||
|
||||
for (auto pile : world->buildings.other.STOCKPILE) {
|
||||
if (!isStockpile(pile))
|
||||
continue;
|
||||
bldg_count++;
|
||||
|
||||
int id = pile->id;
|
||||
if (watched_stockpiles_indices.count(id)) {
|
||||
push_stockpile_config(L, watched_stockpiles[watched_stockpiles_indices[id]]);
|
||||
} else {
|
||||
push_stockpile_config(L, id, false, false);
|
||||
}
|
||||
}
|
||||
return 4+bldg_count;
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
// ensure we disengage our hooks
|
||||
plugin_enable(out, false);
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
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_END};
|
@ -0,0 +1,75 @@
|
||||
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)
|
||||
config[var_name] = val
|
||||
automelt_setStockpileConfig(config.id, config.monitor, config.melt)
|
||||
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 == 'melt' then
|
||||
do_set_stockpile_config('melt', true, args[2])
|
||||
elseif command == 'nomelt' then
|
||||
do_set_stockpile_config('melt', false, args[2])
|
||||
elseif command == 'monitor' then
|
||||
do_set_stockpile_config('monitor', true, args[2])
|
||||
elseif command == 'nomonitor'then
|
||||
do_set_stockpile_config('monitor', false, args[2])
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- used by gui/autochop
|
||||
function setStockpileConfig(config)
|
||||
automelt_setStockpileConfig(config.id, config.monitored, config.melt)
|
||||
end
|
||||
|
||||
function getItemCountsAndStockpileConfigs()
|
||||
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)
|
||||
ret.stockpile_configs = data
|
||||
for _,c in ipairs(ret.stockpile_configs) do
|
||||
c.name = df.building.find(c.id).name
|
||||
c.monitored = c.monitored ~= 0
|
||||
c.melt = c.melt ~= 0
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
return _ENV
|
Loading…
Reference in New Issue