diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3d36b95fc..e92bfaac7 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -83,7 +83,7 @@ dfhack_plugin(autofarm autofarm.cpp) #dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) #add_subdirectory(autolabor) #dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) -#dfhack_plugin(automelt automelt.cpp) +dfhack_plugin(automelt automelt.cpp LINK_LIBRARIES lua) #dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua) #dfhack_plugin(autotrade autotrade.cpp) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 9ca20e9ea..1808a6a59 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -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 +#include + 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 watched_stockpiles; +static unordered_map 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 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 &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 ¶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 &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(args_lambda), + std::forward(res_lambda)); +} + +static command_result do_command(color_ostream &out, vector ¶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 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 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 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(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 &item_counts, map &marked_item_counts, map &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 *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 *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 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 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 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 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 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 &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}; \ No newline at end of file diff --git a/plugins/lua/automelt.lua b/plugins/lua/automelt.lua new file mode 100644 index 000000000..bba93b3c4 --- /dev/null +++ b/plugins/lua/automelt.lua @@ -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