dfhack/plugins/buildingplan.cpp

402 lines
13 KiB
C++

#include "buildingplan-lib.h"
DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.14
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_build_selector);
REQUIRE_GLOBAL(world);
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
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<Units::NoblePosition> getNoblePositionOfSelectedBuildingOwner()
{
std::vector<Units::NoblePosition> 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<df::interface_key> *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])
{
Gui::refreshSidebar();
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))
{
Gui::refreshSidebar();
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)), plugin_self);
}
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())
{
if (Gui::inRenameBuilding())
return false;
auto np = getNoblePositionOfSelectedBuildingOwner();
df::interface_key last_token = get_string_key(input);
if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058)
{
size_t 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<df::interface_key> *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 (size_t 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 <string> & 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 <PluginCommand> &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;
}