#include "Debug.h" #include "PluginManager.h" #include "modules/Burrows.h" #include "modules/Items.h" #include "modules/Job.h" #include "modules/Persistence.h" #include "modules/World.h" #include "df/buildingitemst.h" #include "df/building_nest_boxst.h" #include "df/burrow.h" #include "df/item.h" #include "df/item_eggst.h" #include "df/unit.h" #include "df/world.h" using std::string; using namespace DFHack; using namespace df::enums; DFHACK_PLUGIN("nestboxes"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); namespace DFHack { // for configuration-related logging DBG_DECLARE(nestboxes, control, DebugCategory::LINFO); // for logging during the periodic scan DBG_DECLARE(nestboxes, cycle, DebugCategory::LINFO); } static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; enum ConfigValues { CONFIG_IS_ENABLED = 0, CONFIG_BURROW = 1 }; static const int32_t CYCLE_TICKS = 7; // need to react quickly when eggs are laid/unforbidden static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static void do_cycle(color_ostream &out); static command_result do_command(color_ostream &out, std::vector ¶meters); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(control,out).print("initializing {}\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( plugin_name, "Protect fertile eggs incubating in a nestbox.", do_command)); return CR_OK; } DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { out.printerr("Cannot enable {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; DEBUG(control,out).print("{} from the API; persisting\n", is_enabled ? "enabled" : "disabled"); config.set_bool(CONFIG_IS_ENABLED, is_enabled); if (enable) do_cycle(out); } else { DEBUG(control,out).print("{} from the API, but already {}; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } return CR_OK; } DFhackCExport command_result plugin_shutdown (color_ostream &out) { DEBUG(control,out).print("shutting down {}\n", plugin_name); return CR_OK; } DFhackCExport command_result plugin_load_site_data (color_ostream &out) { cycle_timestamp = 0; config = World::GetPersistentSiteData(CONFIG_KEY); if (!config.isValid()) { DEBUG(control,out).print("no config found in this save; initializing\n"); config = World::AddPersistentSiteData(CONFIG_KEY); config.set_bool(CONFIG_IS_ENABLED, is_enabled); } is_enabled = config.get_bool(CONFIG_IS_ENABLED); DEBUG(control,out).print("loading persisted enabled state: {}\n", is_enabled ? "true" : "false"); return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { DEBUG(control,out).print("world unloaded; disabling {}\n", plugin_name); is_enabled = false; } } return CR_OK; } DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (world->frame_counter - cycle_timestamp >= CYCLE_TICKS) do_cycle(out); return CR_OK; } ///////////////////////////////////////////////////// // configuration logic // static void setBurrow(color_ostream &out, const string &burrow_name){ auto burrow = Burrows::findByName(burrow_name); if (burrow) { config.set_int(CONFIG_BURROW, burrow->id); } else { config.set_int(CONFIG_BURROW, -1); } } static df::burrow* getBurrow(color_ostream &out){ int id = config.get_int(CONFIG_BURROW); auto burrow = df::burrow::find(id); if (!burrow) { config.set_int(CONFIG_BURROW, -1); } return burrow; } static void printStatus(color_ostream &out){ if (!is_enabled) out.print("{} is disabled\n", plugin_name); else { out.print("{} is enabled\n", plugin_name); auto burrow = getBurrow(out); if (burrow) { out.print("only protecting eggs inside burrow: {}\n", burrow->name); } else { out.print("protecting all fertile eggs\n"); } } } static command_result do_command(color_ostream &out, std::vector ¶meters){ if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) { out.printerr("Cannot run {} without a loaded fort.\n", plugin_name); return CR_FAILURE; } if (parameters.size() == 0 || parameters[0] == "status") { printStatus(out); return CR_OK; } else if (parameters.size() > 1 && parameters[0] == "burrow") { setBurrow(out, parameters[1]); return CR_OK; } else if (parameters[0] == "all") { config.set_int(CONFIG_BURROW, -1); return CR_OK; } else { return CR_WRONG_USAGE; } } ///////////////////////////////////////////////////// // cycle logic // static void do_cycle(color_ostream &out) { DEBUG(cycle,out).print("running {} cycle\n", plugin_name); // mark that we have recently run cycle_timestamp = world->frame_counter; // see if egg protection is limited to a burrow auto burrow = getBurrow(out); for (df::building_nest_boxst *nb : world->buildings.other.NEST_BOX) { for (auto &contained_item : nb->contained_items) { if (contained_item->use_mode == df::building_item_role_type::PERM) continue; if (auto *item = virtual_cast(contained_item->item)) { bool fertile = item->egg_flags.bits.fertile; if (item->flags.bits.forbid == fertile) continue; if (burrow && !Burrows::isAssignedTile(burrow, Items::getPosition(item))) { continue; } item->flags.bits.forbid = fertile; if (fertile && item->flags.bits.in_job) { // cancel any job involving the egg df::specific_ref *sref = Items::getSpecificRef( item, df::specific_ref_type::JOB); if (sref && sref->data.job) Job::removeJob(sref->data.job); } out.print("nestboxes: {} eggs {}\n", item->getStackSize(), fertile ? "forbidden" : "unforbidden"); } } } }