// Quick Dumper : Moves items marked as "dump" to cursor // FIXME: local item cache in map blocks needs to be fixed after teleporting items #include #include #include #include #include #include #include #include using namespace std; #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "modules/Items.h" #include "modules/Materials.h" #include "modules/MapCache.h" #include "DataDefs.h" #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::building_stockpilest; DFHACK_PLUGIN("autodump"); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(world); // 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; if (DFHack::World::ReadPauseState()) return CR_OK; if (world->frame_counter % DELTA_TICKS != 0) return CR_OK; monitor.doCycle(); return CR_OK; } struct dump_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; bool handleInput(set *input) { if (Gui::inRenameBuilding()) return false; 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 = dims.y2 - 7; 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, "D"); OutputString(state? COLOR_LIGHTGREEN: COLOR_GREY, x, y, "ump "); } else { OutputToggleString(x, y, "Auto dump", "D", state, true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } } }; 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 (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); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( "autodump", "Teleport items marked for dumping to the cursor.", df_autodump)); commands.push_back(PluginCommand( "autodump-destroy-here", "Destroy items marked for dumping under cursor.", df_autodump_destroy_here, Gui::cursor_hotkey)); commands.push_back(PluginCommand( "autodump-destroy-item", "Destroy the selected item.", df_autodump_destroy_item, Gui::any_item_hotkey)); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } typedef map coordmap; static command_result autodump_main(color_ostream &out, vector & parameters) { // Command line options bool destroy = false; bool here = false; bool need_visible = false; bool need_hidden = false; bool need_forbidden = false; for (size_t i = 0; i < parameters.size(); i++) { string & p = parameters[i]; if(p == "destroy") destroy = true; else if (p == "destroy-here") destroy = here = true; else if (p == "visible") need_visible = true; else if (p == "hidden") need_hidden = true; else if (p == "forbidden") need_forbidden = true; else return CR_WRONG_USAGE; } if (need_visible && need_hidden) { out.printerr("An item can't be both hidden and visible.\n"); return CR_WRONG_USAGE; } //DFHack::VersionInfo *mem = Core::getInstance().vinfo; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } size_t numItems = world->items.all.size(); MapCache MC; int dumped_total = 0; int cx, cy, cz; DFCoord pos_cursor; if(!destroy || here) { if (!Gui::getCursorCoords(cx,cy,cz)) { out.printerr("Cursor position not found. Please enable the cursor.\n"); return CR_FAILURE; } pos_cursor = DFCoord(cx,cy,cz); } if (!destroy) { { Block * b = MC.BlockAt(pos_cursor / 16); if(!b) { out.printerr("Cursor is in an invalid/uninitialized area. Place it over a floor.\n"); return CR_FAILURE; } df::tiletype ttype = MC.tiletypeAt(pos_cursor); if(!DFHack::isWalkable(ttype) || DFHack::isOpenTerrain(ttype)) { out.printerr("Cursor should be placed over a floor.\n"); return CR_FAILURE; } } } // proceed with the dumpification operation for(size_t i=0; i< numItems; i++) { df::item * itm = world->items.all[i]; DFCoord pos_item(itm->pos.x, itm->pos.y, itm->pos.z); // only dump the stuff marked for dumping and laying on the ground if ( !itm->flags.bits.dump // || !itm->flags.bits.on_ground || itm->flags.bits.construction || itm->flags.bits.in_building || itm->flags.bits.in_chest // || itm->flags.bits.in_inventory || itm->flags.bits.artifact ) continue; if (need_visible && itm->flags.bits.hidden) continue; if (need_hidden && !itm->flags.bits.hidden) continue; if (need_forbidden && !itm->flags.bits.forbid) continue; if (!need_forbidden && itm->flags.bits.forbid) continue; if(!destroy) // move to cursor { // Change flags to indicate the dump was completed, as if by super-dwarfs itm->flags.bits.dump = false; itm->flags.bits.forbid = true; // Don't move items if they're already at the cursor if (pos_cursor != pos_item) { if (!Items::moveToGround(MC, itm, pos_cursor)) out.print("Could not move item: %s\n", Items::getDescription(itm, 0, true).c_str()); } } else // destroy { if (here && pos_item != pos_cursor) continue; itm->flags.bits.garbage_collect = true; // Cosmetic changes: make them disappear from view instantly itm->flags.bits.forbid = true; itm->flags.bits.hidden = true; } dumped_total++; } // write map changes back to DF. if(!destroy) MC.WriteAll(); out.print("Done. %d items %s.\n", dumped_total, destroy ? "marked for destruction" : "quickdumped"); return CR_OK; } command_result df_autodump(color_ostream &out, vector & parameters) { CoreSuspender suspend; return autodump_main(out, parameters); } command_result df_autodump_destroy_here(color_ostream &out, vector & parameters) { // HOTKEY COMMAND; CORE ALREADY SUSPENDED if (!parameters.empty()) return CR_WRONG_USAGE; vector args; args.push_back("destroy-here"); return autodump_main(out, args); } static map pending_destroy; static int last_frame = 0; command_result df_autodump_destroy_item(color_ostream &out, vector & parameters) { // HOTKEY COMMAND; CORE ALREADY SUSPENDED if (!parameters.empty()) return CR_WRONG_USAGE; df::item *item = Gui::getSelectedItem(out); if (!item) return CR_FAILURE; // Allow undoing the destroy if (world->frame_counter != last_frame) { last_frame = world->frame_counter; pending_destroy.clear(); } if (pending_destroy.count(item->id)) { df::item_flags old_flags = pending_destroy[item->id]; pending_destroy.erase(item->id); item->flags.bits.garbage_collect = false; item->flags.bits.hidden = old_flags.bits.hidden; item->flags.bits.dump = old_flags.bits.dump; item->flags.bits.forbid = old_flags.bits.forbid; return CR_OK; } // Check the item is good to destroy if (item->flags.bits.garbage_collect) { out.printerr("Item is already marked for destroy.\n"); return CR_FAILURE; } if (item->flags.bits.construction || item->flags.bits.in_building || item->flags.bits.artifact) { out.printerr("Choosing not to destroy buildings, constructions and artifacts.\n"); return CR_FAILURE; } for (size_t i = 0; i < item->general_refs.size(); i++) { df::general_ref *ref = item->general_refs[i]; if (ref->getType() == general_ref_type::UNIT_HOLDER) { out.printerr("Choosing not to destroy items in unit inventory.\n"); return CR_FAILURE; } } // Set the flags pending_destroy[item->id] = item->flags; item->flags.bits.garbage_collect = true; item->flags.bits.hidden = true; item->flags.bits.dump = true; item->flags.bits.forbid = true; return CR_OK; }