diff --git a/Readme.rst b/Readme.rst index 21b2cd3ad..be3912d19 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2872,6 +2872,14 @@ globally active hotkey in dfhack.init, e.g.: ``keybinding add Ctrl-F1 hotkeys`` +Stockpile Automation +==================== +Enable the autodump plugin in your dfhack.init with + ``enable autodump`` + +When querying a stockpile an option will appear to toggle autodump for this stockpile. +Any items placed in this stockpile will be designated to be dumped. + gui/liquids =========== diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index c45b132ae..4d80309f3 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -24,16 +24,239 @@ using namespace std; #include "df/item.h" #include "df/world.h" #include "df/general_ref.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/building_stockpilest.h" +#include "uicommon.h" using namespace DFHack; using namespace df::enums; using MapExtras::Block; using MapExtras::MapCache; + using df::global::world; +using df::building_stockpilest; DFHACK_PLUGIN("autodump"); +// Stockpile interface START +static const string PERSISTENCE_KEY = "autodump/stockpiles"; + +static void mark_all_in_stockpiles(vector &stockpiles) +{ + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); + F(spider_web); F(owned); F(in_job); +#undef F + + size_t marked_count = 0; + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + for (auto it = stockpiles.begin(); it != stockpiles.end(); it++) + { + if (!it->inStockpile(item)) + continue; + + ++marked_count; + item->flags.bits.dump = true; + } + } + + if (marked_count) + Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items to dump", COLOR_GREEN, false); +} + +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(PersistentStockpileInfo(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() + { + for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();) + { + if (!it->isValid()) + it = monitored_stockpiles.erase(it); + else + ++it; + } + + mark_all_in_stockpiles(monitored_stockpiles); + } + + void reset() + { + monitored_stockpiles.clear(); + std::vector 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(PersistentStockpileInfo(pile)); + else + pile.remove(); + } + } + + +private: + vector monitored_stockpiles; +}; + +static StockpileMonitor monitor; + +#define DELTA_TICKS 620 + + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + if(!Maps::IsValid()) + return CR_OK; + + static decltype(world->frame_counter) last_frame_count = 0; + + if (DFHack::World::ReadPauseState()) + return CR_OK; + + if (world->frame_counter - last_frame_count < DELTA_TICKS) + return CR_OK; + + last_frame_count = world->frame_counter; + + monitor.doCycle(); + + return CR_OK; +} + +struct dump_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + bool handleInput(set *input) + { + building_stockpilest *sp = get_selected_stockpile(); + if (!sp) + return false; + + if (input->count(interface_key::CUSTOM_SHIFT_D)) + { + if (monitor.isMonitored(sp)) + monitor.remove(sp); + else + monitor.add(sp); + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *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 = 26; + + OutputToggleString(x, y, "Auto dump", "Shift-D", monitor.isMonitored(sp), true, left_margin); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dump_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(dump_hook, render); + +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; + } + return CR_OK; +} + +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (!gps) + return CR_FAILURE; + + if (enable != is_enabled) + { + if (!INTERPOSE_HOOK(dump_hook, feed).apply(enable) || + !INTERPOSE_HOOK(dump_hook, render).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + + return CR_OK; +} + +// Stockpile interface END + command_result df_autodump(color_ostream &out, vector & parameters); command_result df_autodump_destroy_here(color_ostream &out, vector & parameters); command_result df_autodump_destroy_item(color_ostream &out, vector & parameters);