#include "buildingplan-lib.h" DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.14 DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } static void enable_quickfort_fn(pair& pair) { pair.second = true; } static bool is_planmode_enabled(df::building_type type) { if (planmode_enabled.find(type) == planmode_enabled.end()) { return false; } return planmode_enabled[type]; } #define DAY_TICKS 1200 DFhackCExport command_result plugin_onupdate(color_ostream &out) { static decltype(world->frame_counter) last_frame_count = 0; if ((world->frame_counter - last_frame_count) >= DAY_TICKS/2) { last_frame_count = world->frame_counter; planner.doCycle(); roomMonitor.doCycle(); } return CR_OK; } //START Viewscreen Hook struct buildingplan_hook : public df::viewscreen_dwarfmodest { //START UI Methods typedef df::viewscreen_dwarfmodest interpose_base; void send_key(const df::interface_key &key) { set< df::interface_key > keys; keys.insert(key); this->feed(&keys); } bool isInPlannedBuildingQueryMode() { return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || ui->main.mode == df::ui_sidebar_mode::BuildingItems) && planner.getSelectedPlannedBuilding(); } bool isInPlannedBuildingPlacementMode() { return ui->main.mode == ui_sidebar_mode::Build && ui_build_selector && ui_build_selector->stage < 2 && planner.isPlanableBuilding(ui_build_selector->building_type); } std::vector getNoblePositionOfSelectedBuildingOwner() { std::vector np; if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || !world->selected_building || !world->selected_building->owner) { return np; } switch (world->selected_building->getType()) { case building_type::Bed: case building_type::Chair: case building_type::Table: break; default: return np; } return getUniqueNoblePositions(world->selected_building->owner); } bool isInNobleRoomQueryMode() { if (getNoblePositionOfSelectedBuildingOwner().size() > 0) return canReserveRoom(world->selected_building); else return false; } bool handleInput(set *input) { if (isInPlannedBuildingPlacementMode()) { auto type = ui_build_selector->building_type; if (input->count(interface_key::CUSTOM_SHIFT_P)) { planmode_enabled[type] = !planmode_enabled[type]; if (!planmode_enabled[type]) { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); planner.in_dummmy_screen = false; } return true; } else if (input->count(interface_key::CUSTOM_P) || input->count(interface_key::CUSTOM_F) || input->count(interface_key::CUSTOM_Q) || input->count(interface_key::CUSTOM_D) || input->count(interface_key::CUSTOM_N)) { show_help = true; } if (is_planmode_enabled(type)) { if (planner.inQuickFortMode() && planner.in_dummmy_screen) { if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) || input->count(interface_key::LEAVESCREEN)) { planner.in_dummmy_screen = false; send_key(interface_key::LEAVESCREEN); } return true; } if (input->count(interface_key::SELECT)) { if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); if (planner.inQuickFortMode()) { planner.in_dummmy_screen = true; } } return true; } else if (input->count(interface_key::CUSTOM_SHIFT_F)) { if (!planner.inQuickFortMode()) { planner.enableQuickfortMode(); } else { planner.disableQuickfortMode(); } } else if (input->count(interface_key::CUSTOM_SHIFT_M)) { Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type))); } else if (input->count(interface_key::CUSTOM_SHIFT_Q)) { planner.cycleDefaultQuality(type); } else if (input->count(interface_key::CUSTOM_SHIFT_D)) { planner.getDefaultItemFilterForType(type)->decorated_only = !planner.getDefaultItemFilterForType(type)->decorated_only; } } } else if (isInPlannedBuildingQueryMode()) { if (input->count(interface_key::SUSPENDBUILDING)) { return true; // Don't unsuspend planned buildings } else if (input->count(interface_key::DESTROYBUILDING)) { planner.removeSelectedPlannedBuilding(); // Remove persistent data } } else if (isInNobleRoomQueryMode()) { auto np = getNoblePositionOfSelectedBuildingOwner(); df::interface_key last_token = *input->rbegin(); if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) { int selection = last_token - interface_key::STRING_A048; if (np.size() < selection) return false; roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); return true; } } return false; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { if (!handleInput(input)) INTERPOSE_NEXT(feed)(input); } DEFINE_VMETHOD_INTERPOSE(void, render, ()) { bool plannable = isInPlannedBuildingPlacementMode(); if (plannable && is_planmode_enabled(ui_build_selector->building_type)) { if (ui_build_selector->stage < 1) { // No materials but turn on cursor ui_build_selector->stage = 1; } for (auto iter = ui_build_selector->errors.begin(); iter != ui_build_selector->errors.end();) { //FIXME Hide bags if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") || (*iter)->find("No access") != string::npos) { iter = ui_build_selector->errors.erase(iter); } else { ++iter; } } } INTERPOSE_NEXT(render)(); auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; auto type = ui_build_selector->building_type; if (plannable) { if (planner.inQuickFortMode() && planner.in_dummmy_screen) { Screen::Pen pen(' ',COLOR_BLACK); int y = dims.y1 + 1; Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); ++y; OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin); OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); } else { int y = 23; if (show_help) { OutputString(COLOR_BROWN, x, y, "Note: "); OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); } OutputToggleString(x, y, "Planning Mode", "P", is_planmode_enabled(type), true, left_margin); if (is_planmode_enabled(type)) { OutputToggleString(x, y, "Quickfort Mode", "F", planner.inQuickFortMode(), true, left_margin); auto filter = planner.getDefaultItemFilterForType(type); OutputHotkeyString(x, y, "Min Quality: ", "Q"); OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); OutputToggleString(x, y, "Decorated Only: ", "D", filter->decorated_only, true, left_margin); OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); auto filter_descriptions = filter->getMaterialFilterAsVector(); for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); } else { planner.in_dummmy_screen = false; } } } else if (isInPlannedBuildingQueryMode()) { planner.in_dummmy_screen = false; // Hide suspend toggle option int y = 20; Screen::Pen pen(' ', COLOR_BLACK); Screen::fillRect(pen, x, y, dims.menu_x2, y); auto filter = planner.getSelectedPlannedBuilding()->getFilter(); y = 24; OutputString(COLOR_BROWN, x, y, "Planned Building Filter:", true, left_margin); OutputString(COLOR_BLUE, x, y, filter->getMinQuality(), true, left_margin); if (filter->decorated_only) OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); auto filters = filter->getMaterialFilterAsVector(); for (auto it = filters.begin(); it != filters.end(); ++it) OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); } else if (isInNobleRoomQueryMode()) { auto np = getNoblePositionOfSelectedBuildingOwner(); int y = 24; OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); for (int i = 0; i < np.size() && i < 9; i++) { bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id) == np[i].position->code); OutputToggleString(x, y, np[i].position->name[0].c_str(), int_to_string(i+1).c_str(), enabled, true, left_margin); } } else { planner.in_dummmy_screen = false; show_help = false; } } }; IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { if (!parameters.empty()) { if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') { out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl; } else if (parameters.size() == 2 && toLower(parameters[0]) == "debug") { show_debugging = (toLower(parameters[1]) == "on"); out << "Debugging " << ((show_debugging) ? "enabled" : "disabled") << endl; } } 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) { planner.reset(out); if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) || !INTERPOSE_HOOK(buildingplan_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( "buildingplan", "Place furniture before it's built", buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version.")); planner.initialize(); return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { case SC_MAP_LOADED: planner.reset(out); roomMonitor.reset(out); break; default: break; } return CR_OK; }