diff --git a/NEWS b/NEWS index 71053df01..8312d9a44 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ DFHack future New plugins: - hotkeys (by Falconne): Shows ingame viewscreen with all dfhack keybindings active in current mode. + - automelt: allows marking stockpiles for automelt (i.e. any items placed in stocpile will be designated for melting) Misc Improvements: - now you can use @ to print things in interactive Lua with subtley different semantics diff --git a/Readme.html b/Readme.html index 7f3cdc7ab..10bc3b6c1 100644 --- a/Readme.html +++ b/Readme.html @@ -528,6 +528,7 @@ access DF memory and allow for easier development of new tools.

  • Dwarf Manipulator
  • Search
  • AutoMaterial
  • +
  • Stockpile Automation
  • gui/advfort
  • gui/assign-rack
  • gui/choose-weapons
  • @@ -3265,6 +3266,15 @@ toggle this option on, instead of returning you to the main construction menu af materials, it returns you back to this screen. If you use this along with several autoselect enabled materials, you should be able to place complex constructions more conveniently.

    +
    +

    Stockpile Automation

    +

    Enable the automelt plugin in your dfhack.init with:

    +
    +enable automelt
    +
    +

    When querying a stockpile an option will appear to toggle automelt for this stockpile. +Any items placed in this stockpile will be designated to be melted.

    +

    gui/advfort

    This script allows to perform jobs in adventure mode. For more complete help diff --git a/Readme.rst b/Readme.rst index b0143789e..b143b0c54 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2646,6 +2646,16 @@ toggle this option on, instead of returning you to the main construction menu af materials, it returns you back to this screen. If you use this along with several autoselect enabled materials, you should be able to place complex constructions more conveniently. +Stockpile Automation +==================== +Enable the automelt plugin in your dfhack.init with:: + + enable automelt + +When querying a stockpile an option will appear to toggle automelt for this stockpile. +Any items placed in this stockpile will be designated to be melted. + + gui/advfort =========== diff --git a/dfhack.init-example b/dfhack.init-example index 422b5aa9f..f0f380095 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -183,7 +183,7 @@ enable search enable automaterial # Other interface improvement tools -enable dwarfmonitor mousequery autotrade buildingplan resume zone +enable dwarfmonitor mousequery automelt autotrade buildingplan resume zone # allow the fortress bookkeeper to queue jobs through the manager stockflow enable diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5fe1d26ba..6ebe3ad6d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -89,6 +89,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(autodump autodump.cpp) DFHACK_PLUGIN(autolabor autolabor.cpp) DFHACK_PLUGIN(automaterial automaterial.cpp) + DFHACK_PLUGIN(automelt automelt.cpp) DFHACK_PLUGIN(autotrade autotrade.cpp) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp new file mode 100644 index 000000000..58cbdd20c --- /dev/null +++ b/plugins/automelt.cpp @@ -0,0 +1,296 @@ +#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/building_stockpilest.h" +#include "modules/Items.h" +#include "df/ui.h" +#include "modules/Maps.h" +#include "modules/World.h" +#include "df/item_quality.h" + +using df::global::world; +using df::global::cursor; +using df::global::ui; +using df::building_stockpilest; + +DFHACK_PLUGIN("automelt"); +#define PLUGIN_VERSION 0.3 + +static const string PERSISTENCE_KEY = "automelt/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; + + if (!can_melt(item)) + continue; + + if (is_set_to_melt(item)) + continue; + + auto &melting_items = world->items.other[items_other_id::ANY_MELT_DESIGNATED]; + for (auto it = stockpiles.begin(); it != stockpiles.end(); it++) + { + if (!it->inStockpile(item)) + continue; + + ++marked_count; + insert_into_vector(melting_items, &df::item::id, item); + item->flags.bits.melt = true; + } + } + + if (marked_count) + Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items to melt", COLOR_GREEN, false); +} + +/* + * 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(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 610 + +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; +} + + +/* + * Interface + */ + +struct melt_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_M)) + { + 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 = 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: "); + 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); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, render); + + +static command_result automelt_cmd(color_ostream &out, vector & parameters) +{ + if (!parameters.empty()) + { + if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') + { + out << "Automelt" << endl << "Version: " << PLUGIN_VERSION << endl; + } + } + + return CR_OK; +} + +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(melt_hook, feed).apply(enable) || + !INTERPOSE_HOOK(melt_hook, render).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back( + PluginCommand( + "automelt", "Automatically flag metal items in marked stockpiles for melting.", + automelt_cmd, false, "")); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index 5b28a9171..ac733c1c2 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -421,17 +421,24 @@ struct trade_hook : public df::viewscreen_dwarfmodest auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - int y = dims.y2 - 4; + 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(); - if (links + 12 >= y) - y += 4; + bool state = monitor.isMonitored(sp); - OutputToggleString(x, y, "Auto trade", "T", monitor.isMonitored(sp), true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); + if (links + 12 >= y) { + y = dims.y2; + OutputString(COLOR_WHITE, x, y, "Auto: "); + x += 5; + 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); + } } }; diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 64e4d4dba..44332a118 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -1376,7 +1376,7 @@ struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - int y = dims.y2 - 5; + int y = dims.y2 - 4; int links = 0; links += sp->links.give_to_pile.size();