2020-10-24 10:19:15 -06:00
|
|
|
#include "df/construction_type.h"
|
2020-09-08 01:17:56 -06:00
|
|
|
#include "df/entity_position.h"
|
|
|
|
#include "df/interface_key.h"
|
|
|
|
#include "df/ui_build_selector.h"
|
|
|
|
#include "df/viewscreen_dwarfmodest.h"
|
|
|
|
|
|
|
|
#include "modules/Gui.h"
|
|
|
|
#include "modules/Maps.h"
|
|
|
|
#include "modules/World.h"
|
|
|
|
|
2020-10-25 03:37:22 -06:00
|
|
|
#include "Core.h"
|
2020-09-08 01:17:56 -06:00
|
|
|
#include "LuaTools.h"
|
|
|
|
#include "PluginManager.h"
|
|
|
|
|
|
|
|
#include "uicommon.h"
|
|
|
|
#include "listcolumn.h"
|
2020-10-16 14:52:23 -06:00
|
|
|
#include "buildingplan-lib.h"
|
2013-01-07 01:17:38 -07:00
|
|
|
|
|
|
|
DFHACK_PLUGIN("buildingplan");
|
2021-01-22 13:48:36 -07:00
|
|
|
#define PLUGIN_VERSION "2.0"
|
2014-12-02 19:44:20 -07:00
|
|
|
REQUIRE_GLOBAL(ui);
|
|
|
|
REQUIRE_GLOBAL(ui_build_selector);
|
2020-10-22 22:37:49 -06:00
|
|
|
REQUIRE_GLOBAL(world); // used in buildingplan library
|
2013-02-01 06:22:06 -07:00
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
#define MAX_MASK 10
|
|
|
|
#define MAX_MATERIAL 21
|
2013-01-18 23:20:37 -07:00
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
bool show_help = false;
|
2020-09-08 18:34:11 -06:00
|
|
|
bool quickfort_mode = false;
|
|
|
|
bool in_dummy_screen = false;
|
2020-10-16 14:52:23 -06:00
|
|
|
std::unordered_map<BuildingTypeKey, bool, BuildingTypeKeyHash> planmode_enabled;
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
class ViewscreenChooseMaterial : public dfhack_viewscreen
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
public:
|
2020-10-16 14:52:23 -06:00
|
|
|
ViewscreenChooseMaterial(ItemFilter &filter);
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
void feed(set<df::interface_key> *input);
|
|
|
|
|
|
|
|
void render();
|
|
|
|
|
|
|
|
std::string getFocusString() { return "buildingplan_choosemat"; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
ListColumn<df::dfhack_material_category> masks_column;
|
|
|
|
ListColumn<MaterialInfo> materials_column;
|
|
|
|
int selected_column;
|
2020-10-16 14:52:23 -06:00
|
|
|
ItemFilter &filter;
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
auto entry = ListEntry<df::dfhack_material_category>(pad_string(text, MAX_MASK, false), mask);
|
2020-10-16 14:52:23 -06:00
|
|
|
if (filter.matches(mask))
|
2020-09-08 01:17:56 -06:00
|
|
|
entry.selected = true;
|
|
|
|
|
|
|
|
masks_column.add(entry);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
void populateMasks()
|
|
|
|
{
|
|
|
|
masks_column.clear();
|
|
|
|
df::dfhack_material_category mask;
|
|
|
|
|
|
|
|
mask.whole = 0;
|
|
|
|
mask.bits.stone = true;
|
|
|
|
addMaskEntry(mask, "Stone");
|
|
|
|
|
|
|
|
mask.whole = 0;
|
|
|
|
mask.bits.wood = true;
|
|
|
|
addMaskEntry(mask, "Wood");
|
|
|
|
|
|
|
|
mask.whole = 0;
|
|
|
|
mask.bits.metal = true;
|
|
|
|
addMaskEntry(mask, "Metal");
|
|
|
|
|
|
|
|
mask.whole = 0;
|
|
|
|
mask.bits.soap = true;
|
|
|
|
addMaskEntry(mask, "Soap");
|
|
|
|
|
|
|
|
masks_column.filterDisplay();
|
|
|
|
}
|
|
|
|
|
|
|
|
void populateMaterials()
|
|
|
|
{
|
|
|
|
materials_column.clear();
|
|
|
|
df::dfhack_material_category selected_category;
|
|
|
|
std::vector<df::dfhack_material_category> selected_masks = masks_column.getSelectedElems();
|
|
|
|
if (selected_masks.size() == 1)
|
|
|
|
selected_category = selected_masks[0];
|
|
|
|
else if (selected_masks.size() > 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
df::world_raws &raws = world->raws;
|
|
|
|
for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++)
|
|
|
|
{
|
|
|
|
auto obj = raws.mat_table.builtin[i];
|
|
|
|
if (obj)
|
|
|
|
{
|
|
|
|
MaterialInfo material;
|
|
|
|
material.decode(i, -1);
|
|
|
|
addMaterialEntry(selected_category, material, material.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < raws.inorganics.size(); i++)
|
|
|
|
{
|
|
|
|
df::inorganic_raw *p = raws.inorganics[i];
|
|
|
|
MaterialInfo material;
|
|
|
|
material.decode(0, i);
|
|
|
|
addMaterialEntry(selected_category, material, material.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
decltype(selected_category) wood_flag;
|
|
|
|
wood_flag.bits.wood = true;
|
|
|
|
if (!selected_category.whole || selected_category.bits.wood)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < raws.plants.all.size(); i++)
|
|
|
|
{
|
|
|
|
df::plant_raw *p = raws.plants.all[i];
|
|
|
|
for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++)
|
|
|
|
{
|
|
|
|
auto t = p->material[j];
|
|
|
|
if (p->material[j]->id != "WOOD")
|
|
|
|
continue;
|
|
|
|
|
|
|
|
MaterialInfo material;
|
|
|
|
material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i);
|
|
|
|
auto name = material.toString();
|
|
|
|
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
|
2020-10-16 14:52:23 -06:00
|
|
|
if (filter.matches(material))
|
2020-09-08 01:17:56 -06:00
|
|
|
entry.selected = true;
|
|
|
|
|
|
|
|
materials_column.add(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
materials_column.sort();
|
|
|
|
}
|
|
|
|
|
|
|
|
void addMaterialEntry(df::dfhack_material_category &selected_category,
|
|
|
|
MaterialInfo &material, std::string name)
|
|
|
|
{
|
|
|
|
if (!selected_category.whole || material.matches(selected_category))
|
|
|
|
{
|
|
|
|
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
|
2020-10-16 14:52:23 -06:00
|
|
|
if (filter.matches(material))
|
2020-09-08 01:17:56 -06:00
|
|
|
entry.selected = true;
|
|
|
|
|
|
|
|
materials_column.add(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void validateColumn()
|
|
|
|
{
|
|
|
|
set_to_limit(selected_column, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void resize(int32_t x, int32_t y)
|
|
|
|
{
|
|
|
|
dfhack_viewscreen::resize(x, y);
|
|
|
|
masks_column.resize();
|
|
|
|
materials_column.resize();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
const DFHack::MaterialInfo &material_info_identity_fn(const DFHack::MaterialInfo &m) { return m; }
|
2020-09-08 01:17:56 -06:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter &filter)
|
|
|
|
: filter(filter)
|
2020-09-08 01:17:56 -06:00
|
|
|
{
|
|
|
|
selected_column = 0;
|
|
|
|
masks_column.setTitle("Type");
|
|
|
|
masks_column.multiselect = true;
|
|
|
|
masks_column.allow_search = false;
|
|
|
|
masks_column.left_margin = 2;
|
|
|
|
materials_column.left_margin = MAX_MASK + 3;
|
|
|
|
materials_column.setTitle("Material");
|
|
|
|
materials_column.multiselect = true;
|
|
|
|
|
|
|
|
masks_column.changeHighlight(0);
|
|
|
|
|
|
|
|
populateMasks();
|
|
|
|
populateMaterials();
|
|
|
|
|
|
|
|
masks_column.selectDefaultEntry();
|
|
|
|
materials_column.selectDefaultEntry();
|
|
|
|
materials_column.changeHighlight(0);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
bool key_processed = false;
|
|
|
|
switch (selected_column)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
case 0:
|
|
|
|
key_processed = masks_column.feed(input);
|
|
|
|
if (input->count(interface_key::SELECT))
|
|
|
|
populateMaterials(); // Redo materials lists based on category selection
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
key_processed = materials_column.feed(input);
|
|
|
|
break;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
if (key_processed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (input->count(interface_key::LEAVESCREEN))
|
|
|
|
{
|
|
|
|
input->clear();
|
|
|
|
Screen::dismiss(this);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_C))
|
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
filter.clear();
|
2020-09-08 01:17:56 -06:00
|
|
|
masks_column.clearSelection();
|
|
|
|
materials_column.clearSelection();
|
|
|
|
populateMaterials();
|
|
|
|
}
|
|
|
|
else if (input->count(interface_key::SEC_SELECT))
|
|
|
|
{
|
|
|
|
// Convert list selections to material filters
|
2020-10-16 14:52:23 -06:00
|
|
|
filter.clearMaterialMask();
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
// Category masks
|
|
|
|
auto masks = masks_column.getSelectedElems();
|
|
|
|
for (auto it = masks.begin(); it != masks.end(); ++it)
|
2020-10-16 14:52:23 -06:00
|
|
|
filter.addMaterialMask(it->whole);
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
// Specific materials
|
|
|
|
auto materials = materials_column.getSelectedElems();
|
2020-10-16 14:52:23 -06:00
|
|
|
std::vector<DFHack::MaterialInfo> materialInfos;
|
|
|
|
transform_(materials, materialInfos, material_info_identity_fn);
|
|
|
|
filter.setMaterials(materialInfos);
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
Screen::dismiss(this);
|
|
|
|
}
|
|
|
|
else if (input->count(interface_key::CURSOR_LEFT))
|
|
|
|
{
|
|
|
|
--selected_column;
|
|
|
|
validateColumn();
|
|
|
|
}
|
|
|
|
else if (input->count(interface_key::CURSOR_RIGHT))
|
|
|
|
{
|
|
|
|
selected_column++;
|
|
|
|
validateColumn();
|
|
|
|
}
|
|
|
|
else if (enabler->tracking_on && enabler->mouse_lbut)
|
|
|
|
{
|
|
|
|
if (masks_column.setHighlightByMouse())
|
|
|
|
selected_column = 0;
|
|
|
|
else if (materials_column.setHighlightByMouse())
|
|
|
|
selected_column = 1;
|
|
|
|
|
|
|
|
enabler->mouse_lbut = enabler->mouse_rbut = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ViewscreenChooseMaterial::render()
|
|
|
|
{
|
|
|
|
if (Screen::isDismissed(this))
|
|
|
|
return;
|
|
|
|
|
|
|
|
dfhack_viewscreen::render();
|
|
|
|
|
|
|
|
Screen::clear();
|
|
|
|
Screen::drawBorder(" Building Material ");
|
|
|
|
|
|
|
|
masks_column.display(selected_column == 0);
|
|
|
|
materials_column.display(selected_column == 1);
|
|
|
|
|
|
|
|
int32_t y = gps->dimy - 3;
|
|
|
|
int32_t x = 2;
|
|
|
|
OutputHotkeyString(x, y, "Toggle", interface_key::SELECT);
|
|
|
|
x += 3;
|
|
|
|
OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT);
|
|
|
|
x += 3;
|
|
|
|
OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C);
|
|
|
|
x += 3;
|
|
|
|
OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//START Viewscreen Hook
|
2020-10-16 14:52:23 -06:00
|
|
|
static bool is_planmode_enabled(BuildingTypeKey key)
|
2020-09-08 01:17:56 -06:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
return planmode_enabled[key] || quickfort_mode;
|
2020-09-08 01:17:56 -06:00
|
|
|
}
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
static std::string get_item_label(const BuildingTypeKey &key, int item_idx)
|
|
|
|
{
|
2020-10-22 22:37:49 -06:00
|
|
|
auto L = Lua::Core::State;
|
|
|
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
|
|
|
Lua::StackUnwinder top(L);
|
2020-10-16 15:08:52 -06:00
|
|
|
|
|
|
|
if (!lua_checkstack(L, 5) ||
|
|
|
|
!Lua::PushModulePublic(
|
|
|
|
out, L, "plugins.buildingplan", "get_item_label"))
|
|
|
|
return "Failed push";
|
|
|
|
|
|
|
|
Lua::Push(L, std::get<0>(key));
|
|
|
|
Lua::Push(L, std::get<1>(key));
|
|
|
|
Lua::Push(L, std::get<2>(key));
|
|
|
|
Lua::Push(L, item_idx);
|
|
|
|
|
|
|
|
if (!Lua::SafeCall(out, L, 4, 1))
|
|
|
|
return "Failed call";
|
|
|
|
|
|
|
|
const char *s = lua_tostring(L, -1);
|
|
|
|
if (!s)
|
|
|
|
return "No string";
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2020-10-31 03:25:26 -06:00
|
|
|
static bool item_can_be_improved(const BuildingTypeKey &key, int item_idx)
|
|
|
|
{
|
|
|
|
auto L = Lua::Core::State;
|
|
|
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
|
|
|
Lua::StackUnwinder top(L);
|
|
|
|
|
|
|
|
if (!lua_checkstack(L, 5) ||
|
|
|
|
!Lua::PushModulePublic(
|
|
|
|
out, L, "plugins.buildingplan", "item_can_be_improved"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
Lua::Push(L, std::get<0>(key));
|
|
|
|
Lua::Push(L, std::get<1>(key));
|
|
|
|
Lua::Push(L, std::get<2>(key));
|
|
|
|
Lua::Push(L, item_idx);
|
|
|
|
|
|
|
|
if (!Lua::SafeCall(out, L, 4, 1))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return lua_toboolean(L, -1);
|
|
|
|
}
|
|
|
|
|
2020-10-16 15:03:05 -06:00
|
|
|
static bool construct_planned_building()
|
|
|
|
{
|
2020-10-22 22:37:49 -06:00
|
|
|
auto L = Lua::Core::State;
|
|
|
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
2020-10-16 15:03:05 -06:00
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
CoreSuspendClaimer suspend;
|
|
|
|
Lua::StackUnwinder top(L);
|
2020-10-16 15:03:05 -06:00
|
|
|
|
|
|
|
if (!(lua_checkstack(L, 1) &&
|
|
|
|
Lua::PushModulePublic(out, L, "plugins.buildingplan",
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
"construct_buildings_from_ui_state") &&
|
2020-10-16 15:03:05 -06:00
|
|
|
Lua::SafeCall(out, L, 0, 1)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
// register all returned buildings with planner
|
|
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, -2) != 0)
|
2020-10-24 12:27:33 -06:00
|
|
|
{
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
auto bld = Lua::GetDFObject<df::building>(L, -1);
|
|
|
|
if (!bld)
|
|
|
|
{
|
|
|
|
out.printerr(
|
|
|
|
"buildingplan: construct_buildings_from_ui_state() failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-16 15:03:05 -06:00
|
|
|
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
planner.addPlannedBuilding(bld);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
2020-10-16 15:03:05 -06:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
static void show_global_settings_dialog()
|
|
|
|
{
|
|
|
|
auto L = Lua::Core::State;
|
|
|
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
|
|
|
Lua::StackUnwinder top(L);
|
|
|
|
|
|
|
|
if (!lua_checkstack(L, 2) ||
|
|
|
|
!Lua::PushModulePublic(
|
|
|
|
out, L, "plugins.buildingplan", "show_global_settings_dialog"))
|
|
|
|
{
|
|
|
|
debug("Failed to push the module");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_newtable(L);
|
|
|
|
int ctable = lua_gettop(L);
|
|
|
|
Lua::SetField(L, quickfort_mode, ctable, "quickfort_mode");
|
|
|
|
|
|
|
|
for (auto & setting : planner.getGlobalSettings())
|
|
|
|
{
|
|
|
|
Lua::SetField(L, setting.second, ctable, setting.first.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Lua::SafeCall(out, L, 1, 0))
|
|
|
|
{
|
|
|
|
debug("Failed call to show_global_settings_dialog");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
static bool is_automaterial_enabled()
|
|
|
|
{
|
|
|
|
auto L = Lua::Core::State;
|
|
|
|
color_ostream_proxy out(Core::getInstance().getConsole());
|
|
|
|
Lua::StackUnwinder top(L);
|
|
|
|
|
|
|
|
if (!(lua_checkstack(L, 1) &&
|
|
|
|
Lua::PushModulePublic(out, L, "plugins.automaterial", "isEnabled") &&
|
|
|
|
Lua::SafeCall(out, L, 0, 1)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lua_toboolean(L, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_automaterial_managed(df::building_type type, int16_t subtype)
|
|
|
|
{
|
|
|
|
return is_automaterial_enabled()
|
|
|
|
&& type == df::building_type::Construction
|
|
|
|
&& subtype < df::construction_type::TrackN;
|
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
struct buildingplan_query_hook : public df::viewscreen_dwarfmodest
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
// no non-static fields allowed (according to VTableInterpose.h)
|
|
|
|
static df::building *bld;
|
|
|
|
static PlannedBuilding *pb;
|
|
|
|
static int filter_count;
|
|
|
|
static int filter_idx;
|
|
|
|
|
|
|
|
// logic is reversed since we're starting at the last filter
|
|
|
|
bool hasNextFilter() const { return filter_idx > 0; }
|
|
|
|
bool hasPrevFilter() const { return filter_idx + 1 < filter_count; }
|
|
|
|
|
2013-01-07 01:17:38 -07:00
|
|
|
bool isInPlannedBuildingQueryMode()
|
|
|
|
{
|
2015-02-14 20:53:06 -07:00
|
|
|
return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding ||
|
2013-01-07 01:17:38 -07:00
|
|
|
ui->main.mode == df::ui_sidebar_mode::BuildingItems) &&
|
2020-10-16 14:52:23 -06:00
|
|
|
planner.getPlannedBuilding(world->selected_building);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
// reinit static fields when selected building changes
|
|
|
|
void initStatics()
|
|
|
|
{
|
|
|
|
df::building *cur_bld = world->selected_building;
|
|
|
|
if (bld != cur_bld)
|
|
|
|
{
|
|
|
|
bld = cur_bld;
|
|
|
|
pb = planner.getPlannedBuilding(bld);
|
|
|
|
filter_count = pb->getFilters().size();
|
|
|
|
filter_idx = filter_count - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-12 01:11:13 -07:00
|
|
|
static void invalidateStatics()
|
|
|
|
{
|
|
|
|
bld = NULL;
|
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
bool handleInput(set<df::interface_key> *input)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-11-11 18:12:56 -07:00
|
|
|
if (!isInPlannedBuildingQueryMode() || Gui::inRenameBuilding())
|
2020-10-16 14:52:23 -06:00
|
|
|
return false;
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
initStatics();
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (input->count(interface_key::SUSPENDBUILDING))
|
|
|
|
return true; // Don't unsuspend planned buildings
|
|
|
|
if (input->count(interface_key::DESTROYBUILDING))
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-16 15:08:52 -06:00
|
|
|
// remove persistent data
|
|
|
|
pb->remove();
|
|
|
|
// still allow the building to be removed
|
|
|
|
return false;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
// ctrl+Right
|
|
|
|
if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter())
|
|
|
|
--filter_idx;
|
|
|
|
// ctrl+Left
|
|
|
|
else if (input->count(interface_key::A_MOVE_W_DOWN) && hasPrevFilter())
|
|
|
|
++filter_idx;
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
return true;
|
2020-10-16 14:52:23 -06:00
|
|
|
}
|
2014-06-14 05:50:47 -06:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
|
|
{
|
|
|
|
if (!handleInput(input))
|
|
|
|
INTERPOSE_NEXT(feed)(input);
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
|
|
|
|
if (!isInPlannedBuildingQueryMode())
|
|
|
|
return;
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
initStatics();
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
// Hide suspend toggle option
|
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
|
|
int left_margin = dims.menu_x1 + 1;
|
|
|
|
int x = left_margin;
|
|
|
|
int y = 20;
|
|
|
|
Screen::Pen pen(' ', COLOR_BLACK);
|
|
|
|
Screen::fillRect(pen, x, y, dims.menu_x2, y);
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
auto & filter = pb->getFilters()[filter_idx];
|
2020-10-16 14:52:23 -06:00
|
|
|
y = 24;
|
2020-10-16 15:08:52 -06:00
|
|
|
std::string item_label =
|
|
|
|
stl_sprintf("Item %d of %d", filter_count - filter_idx, filter_count);
|
|
|
|
OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin + 1);
|
|
|
|
OutputString(COLOR_WHITE, x, y, item_label.c_str(), true, left_margin + 1);
|
|
|
|
OutputString(COLOR_WHITE, x, y, get_item_label(toBuildingTypeKey(bld), filter_idx).c_str(), true, left_margin);
|
2020-10-16 14:52:23 -06:00
|
|
|
++y;
|
2020-10-31 03:25:26 -06:00
|
|
|
if (item_can_be_improved(toBuildingTypeKey(bld), filter_idx))
|
|
|
|
{
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin);
|
|
|
|
OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin);
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin);
|
|
|
|
OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin);
|
|
|
|
if (filter.getDecoratedOnly())
|
|
|
|
OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin);
|
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin);
|
|
|
|
auto filters = filter.getMaterials();
|
|
|
|
for (auto it = filters.begin(); it != filters.end(); ++it)
|
|
|
|
OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin);
|
2020-10-16 15:08:52 -06:00
|
|
|
|
|
|
|
++y;
|
|
|
|
if (hasPrevFilter())
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-16 15:08:52 -06:00
|
|
|
if (hasNextFilter())
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-16 14:52:23 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
df::building * buildingplan_query_hook::bld;
|
|
|
|
PlannedBuilding * buildingplan_query_hook::pb;
|
|
|
|
int buildingplan_query_hook::filter_count;
|
|
|
|
int buildingplan_query_hook::filter_idx;
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
struct buildingplan_place_hook : public df::viewscreen_dwarfmodest
|
|
|
|
{
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
// no non-static fields allowed (according to VTableInterpose.h)
|
|
|
|
static BuildingTypeKey key;
|
|
|
|
static std::vector<ItemFilter>::reverse_iterator filter_rbegin;
|
|
|
|
static std::vector<ItemFilter>::reverse_iterator filter_rend;
|
|
|
|
static std::vector<ItemFilter>::reverse_iterator filter;
|
|
|
|
static int filter_count;
|
|
|
|
static int filter_idx;
|
|
|
|
|
|
|
|
bool hasNextFilter() const { return filter + 1 != filter_rend; }
|
|
|
|
bool hasPrevFilter() const { return filter != filter_rbegin; }
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
bool isInPlannedBuildingPlacementMode()
|
|
|
|
{
|
|
|
|
return ui->main.mode == ui_sidebar_mode::Build &&
|
|
|
|
df::global::ui_build_selector &&
|
|
|
|
df::global::ui_build_selector->stage < 2 &&
|
|
|
|
planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector));
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
// reinit static fields when selected building type changes
|
|
|
|
void initStatics()
|
|
|
|
{
|
|
|
|
BuildingTypeKey cur_key = toBuildingTypeKey(ui_build_selector);
|
|
|
|
if (key != cur_key)
|
|
|
|
{
|
|
|
|
key = cur_key;
|
|
|
|
auto wrapper = planner.getItemFilters(key);
|
|
|
|
filter_rbegin = wrapper.rbegin();
|
|
|
|
filter_rend = wrapper.rend();
|
|
|
|
filter = filter_rbegin;
|
|
|
|
filter_count = wrapper.get().size();
|
|
|
|
filter_idx = filter_count - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-12 01:11:13 -07:00
|
|
|
static void invalidateStatics()
|
|
|
|
{
|
|
|
|
key = BuildingTypeKey();
|
|
|
|
}
|
|
|
|
|
2013-01-18 23:20:37 -07:00
|
|
|
bool handleInput(set<df::interface_key> *input)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
if (!isInPlannedBuildingPlacementMode())
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
show_help = false;
|
|
|
|
return false;
|
|
|
|
}
|
2020-11-11 14:04:40 -07:00
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
initStatics();
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (in_dummy_screen)
|
|
|
|
{
|
|
|
|
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)
|
|
|
|
|| input->count(interface_key::LEAVESCREEN))
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
in_dummy_screen = false;
|
|
|
|
// pass LEAVESCREEN up to parent view
|
|
|
|
input->clear();
|
|
|
|
input->insert(interface_key::LEAVESCREEN);
|
|
|
|
return false;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
return true;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
|
|
|
|
if (input->count(interface_key::CUSTOM_P) ||
|
2020-10-22 22:37:49 -06:00
|
|
|
input->count(interface_key::CUSTOM_G) ||
|
2020-10-16 14:52:23 -06:00
|
|
|
input->count(interface_key::CUSTOM_D) ||
|
2020-10-26 17:11:34 -06:00
|
|
|
input->count(interface_key::CUSTOM_Q) ||
|
|
|
|
input->count(interface_key::CUSTOM_W) ||
|
|
|
|
input->count(interface_key::CUSTOM_A) ||
|
|
|
|
input->count(interface_key::CUSTOM_S) ||
|
2020-10-16 14:52:23 -06:00
|
|
|
input->count(interface_key::CUSTOM_M))
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
show_help = true;
|
|
|
|
}
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_P))
|
|
|
|
{
|
|
|
|
planmode_enabled[key] = !planmode_enabled[key];
|
|
|
|
if (!is_planmode_enabled(key))
|
|
|
|
Gui::refreshSidebar();
|
|
|
|
return true;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-22 22:37:49 -06:00
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_G))
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-22 22:37:49 -06:00
|
|
|
show_global_settings_dialog();
|
2020-10-16 14:52:23 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_planmode_enabled(key))
|
|
|
|
return false;
|
|
|
|
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
// if automaterial is enabled, let it handle building allocation and
|
|
|
|
// registration with planner
|
|
|
|
if (input->count(interface_key::SELECT) &&
|
|
|
|
!is_automaterial_managed(ui_build_selector->building_type,
|
|
|
|
ui_build_selector->building_subtype))
|
2020-10-16 14:52:23 -06:00
|
|
|
{
|
2020-10-16 15:03:05 -06:00
|
|
|
if (ui_build_selector->errors.size() == 0 && construct_planned_building())
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
Gui::refreshSidebar();
|
|
|
|
if (quickfort_mode)
|
|
|
|
in_dummy_screen = true;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
return true;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-31 03:25:26 -06:00
|
|
|
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_M))
|
|
|
|
Screen::show(dts::make_unique<ViewscreenChooseMaterial>(*filter), plugin_self);
|
2020-10-31 03:25:26 -06:00
|
|
|
|
|
|
|
if (item_can_be_improved(key, filter_idx))
|
|
|
|
{
|
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_Q))
|
|
|
|
filter->decMinQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_W))
|
|
|
|
filter->incMinQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_A))
|
|
|
|
filter->decMaxQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_S))
|
|
|
|
filter->incMaxQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_D))
|
|
|
|
filter->toggleDecoratedOnly();
|
|
|
|
}
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
// ctrl+Right
|
2020-10-31 03:25:26 -06:00
|
|
|
if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter())
|
2020-10-16 15:08:52 -06:00
|
|
|
{
|
|
|
|
++filter;
|
|
|
|
--filter_idx;
|
|
|
|
}
|
|
|
|
// ctrl+Left
|
|
|
|
else if (input->count(interface_key::A_MOVE_W_DOWN) && hasPrevFilter())
|
|
|
|
{
|
|
|
|
--filter;
|
|
|
|
++filter_idx;
|
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
else
|
|
|
|
return false;
|
|
|
|
return true;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
|
|
{
|
2013-01-18 23:20:37 -07:00
|
|
|
if (!handleInput(input))
|
2013-01-07 01:17:38 -07:00
|
|
|
INTERPOSE_NEXT(feed)(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
|
|
|
{
|
|
|
|
bool plannable = isInPlannedBuildingPlacementMode();
|
2020-10-16 14:52:23 -06:00
|
|
|
if (plannable && is_planmode_enabled(key))
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
|
|
|
if (ui_build_selector->stage < 1)
|
|
|
|
// No materials but turn on cursor
|
|
|
|
ui_build_selector->stage = 1;
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
for (auto iter = ui_build_selector->errors.begin();
|
|
|
|
iter != ui_build_selector->errors.end();)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
// FIXME Hide bags
|
|
|
|
if (((*iter)->find("Needs") != string::npos
|
|
|
|
&& **iter != "Needs adjacent wall")
|
|
|
|
|| (*iter)->find("No access") != string::npos)
|
2013-01-07 01:17:38 -07:00
|
|
|
iter = ui_build_selector->errors.erase(iter);
|
|
|
|
else
|
|
|
|
++iter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (!plannable)
|
|
|
|
return;
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
initStatics();
|
|
|
|
|
2013-01-07 01:17:38 -07:00
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
|
|
int left_margin = dims.menu_x1 + 1;
|
|
|
|
int x = left_margin;
|
2020-10-16 14:52:23 -06:00
|
|
|
|
|
|
|
if (in_dummy_screen)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
Screen::Pen pen(' ',COLOR_BLACK);
|
|
|
|
int y = dims.y1 + 1;
|
|
|
|
Screen::fillRect(pen, x, y, dims.menu_x2, y + 20);
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
++y;
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
OutputString(COLOR_BROWN, x, y,
|
|
|
|
"Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.",
|
|
|
|
true, left_margin);
|
|
|
|
OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin);
|
|
|
|
return;
|
|
|
|
}
|
2013-02-07 02:57:07 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
int y = 23;
|
2013-01-18 23:20:37 -07:00
|
|
|
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
if (is_automaterial_managed(ui_build_selector->building_type,
|
|
|
|
ui_build_selector->building_subtype))
|
2020-10-22 23:01:45 -06:00
|
|
|
{
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
// avoid conflict with the automaterial plugin UI
|
|
|
|
y = 36;
|
2020-10-22 23:01:45 -06:00
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (show_help)
|
|
|
|
{
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Note: ");
|
|
|
|
OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin);
|
|
|
|
}
|
2013-01-18 23:20:37 -07:00
|
|
|
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputToggleString(x, y, "Planning Mode", interface_key::CUSTOM_SHIFT_P,
|
|
|
|
planmode_enabled[key], true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
|
|
|
OutputHotkeyString(x, y, "Global Settings", interface_key::CUSTOM_SHIFT_G,
|
|
|
|
true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2013-02-07 04:04:52 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
if (!is_planmode_enabled(key))
|
|
|
|
return;
|
2013-02-07 04:04:52 -07:00
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
y += 2;
|
2020-10-16 15:08:52 -06:00
|
|
|
std::string title =
|
|
|
|
stl_sprintf("Filter for Item %d of %d:",
|
|
|
|
filter_count - filter_idx, filter_count);
|
|
|
|
OutputString(COLOR_WHITE, x, y, title.c_str(), true, left_margin + 1);
|
|
|
|
OutputString(COLOR_WHITE, x, y, get_item_label(key, filter_idx).c_str(), true, left_margin);
|
|
|
|
|
2020-10-31 03:25:26 -06:00
|
|
|
if (item_can_be_improved(key, filter_idx))
|
|
|
|
{
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Min Quality: ", "QW", false, 0, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-31 03:25:26 -06:00
|
|
|
OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin);
|
2018-05-20 15:25:59 -06:00
|
|
|
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Max Quality: ", "AS", false, 0, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-31 03:25:26 -06:00
|
|
|
OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin);
|
2013-02-07 04:04:52 -07:00
|
|
|
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputToggleString(x, y, "Decorated Only", interface_key::CUSTOM_SHIFT_D,
|
|
|
|
filter->getDecoratedOnly(), true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-31 03:25:26 -06:00
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Material Filter:", interface_key::CUSTOM_SHIFT_M, true,
|
|
|
|
left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-16 14:52:23 -06:00
|
|
|
auto filter_descriptions = filter->getMaterials();
|
|
|
|
for (auto it = filter_descriptions.begin();
|
|
|
|
it != filter_descriptions.end(); ++it)
|
|
|
|
OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin);
|
2020-10-16 15:08:52 -06:00
|
|
|
|
|
|
|
y += 2;
|
|
|
|
if (hasPrevFilter())
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true,
|
|
|
|
left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-16 15:08:52 -06:00
|
|
|
if (hasNextFilter())
|
2020-11-11 14:04:40 -07:00
|
|
|
OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true,
|
|
|
|
left_margin, COLOR_WHITE, COLOR_LIGHTRED);
|
2020-10-16 14:52:23 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
BuildingTypeKey buildingplan_place_hook::key;
|
|
|
|
std::vector<ItemFilter>::reverse_iterator buildingplan_place_hook::filter_rbegin;
|
|
|
|
std::vector<ItemFilter>::reverse_iterator buildingplan_place_hook::filter_rend;
|
|
|
|
std::vector<ItemFilter>::reverse_iterator buildingplan_place_hook::filter;
|
|
|
|
int buildingplan_place_hook::filter_count;
|
|
|
|
int buildingplan_place_hook::filter_idx;
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
struct buildingplan_room_hook : public df::viewscreen_dwarfmodest
|
|
|
|
{
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
|
|
|
|
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)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
return np;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
|
|
|
|
switch (world->selected_building->getType())
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-16 14:52:23 -06:00
|
|
|
case building_type::Bed:
|
|
|
|
case building_type::Chair:
|
|
|
|
case building_type::Table:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return np;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
2020-10-16 14:52:23 -06:00
|
|
|
|
|
|
|
return getUniqueNoblePositions(world->selected_building->owner);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isInNobleRoomQueryMode()
|
|
|
|
{
|
|
|
|
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
|
|
|
|
return canReserveRoom(world->selected_building);
|
2013-02-07 04:04:52 -07:00
|
|
|
else
|
2020-10-16 14:52:23 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool handleInput(set<df::interface_key> *input)
|
|
|
|
{
|
|
|
|
if (!isInNobleRoomQueryMode())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (Gui::inRenameBuilding())
|
|
|
|
return false;
|
|
|
|
auto np = getNoblePositionOfSelectedBuildingOwner();
|
|
|
|
df::interface_key last_token = get_string_key(input);
|
2020-11-17 21:33:18 -07:00
|
|
|
if (last_token >= Screen::charToKey('1')
|
|
|
|
&& last_token <= Screen::charToKey('9'))
|
2013-02-07 04:04:52 -07:00
|
|
|
{
|
2020-11-17 21:33:18 -07:00
|
|
|
size_t index = last_token - Screen::charToKey('1');
|
|
|
|
if (index >= np.size())
|
2020-10-16 14:52:23 -06:00
|
|
|
return false;
|
2020-11-17 21:33:18 -07:00
|
|
|
roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(index).position->code);
|
2020-10-16 14:52:23 -06:00
|
|
|
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, ())
|
|
|
|
{
|
|
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
|
|
|
|
if (!isInNobleRoomQueryMode())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto np = getNoblePositionOfSelectedBuildingOwner();
|
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
|
|
int left_margin = dims.menu_x1 + 1;
|
|
|
|
int x = left_margin;
|
|
|
|
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);
|
2013-02-07 04:04:52 -07:00
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, feed);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, feed);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, feed);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render);
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-11-03 20:09:58 -07:00
|
|
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
|
|
|
|
2021-01-22 13:48:36 -07:00
|
|
|
static bool setSetting(std::string name, bool value);
|
2020-11-03 20:09:58 -07:00
|
|
|
|
|
|
|
static bool isTrue(std::string val)
|
|
|
|
{
|
2021-01-22 13:48:36 -07:00
|
|
|
val = toLower(val);
|
|
|
|
return val == "on" || val == "true" || val == "y" || val == "yes"
|
|
|
|
|| val == "1";
|
2020-11-03 20:09:58 -07:00
|
|
|
}
|
|
|
|
|
2013-01-07 01:17:38 -07:00
|
|
|
static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters)
|
|
|
|
{
|
2020-11-03 20:09:58 -07:00
|
|
|
if (parameters.empty())
|
|
|
|
return CR_OK;
|
|
|
|
|
|
|
|
std::string cmd = toLower(parameters[0]);
|
|
|
|
|
|
|
|
if (cmd.size() >= 1 && cmd[0] == 'v')
|
|
|
|
{
|
2021-01-22 13:48:36 -07:00
|
|
|
out.print("buildingplan version: %s\n", PLUGIN_VERSION);
|
2020-11-03 20:09:58 -07:00
|
|
|
}
|
|
|
|
else if (parameters.size() >= 2 && cmd == "debug")
|
|
|
|
{
|
2021-01-22 13:48:36 -07:00
|
|
|
show_debugging = isTrue(parameters[1]);
|
|
|
|
out.print("buildingplan debugging: %s\n",
|
|
|
|
show_debugging ? "enabled" : "disabled");
|
2020-11-03 20:09:58 -07:00
|
|
|
}
|
|
|
|
else if (cmd == "set")
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-11-03 20:09:58 -07:00
|
|
|
if (!is_enabled)
|
2013-03-09 20:14:00 -07:00
|
|
|
{
|
2021-01-22 13:48:36 -07:00
|
|
|
out.printerr(
|
|
|
|
"ERROR: buildingplan must be enabled before you can"
|
|
|
|
" read or set buildingplan global settings.");
|
2020-11-03 20:09:58 -07:00
|
|
|
return CR_FAILURE;
|
2013-03-09 20:14:00 -07:00
|
|
|
}
|
2020-11-03 20:09:58 -07:00
|
|
|
|
|
|
|
if (!DFHack::Core::getInstance().isMapLoaded())
|
2013-03-09 20:14:00 -07:00
|
|
|
{
|
2021-01-22 13:48:36 -07:00
|
|
|
out.printerr(
|
|
|
|
"ERROR: A map must be loaded before you can read or set"
|
|
|
|
"buildingplan global settings. Try adding your"
|
2021-01-22 19:05:20 -07:00
|
|
|
"'buildingplan set' commands to the onMapLoad.init file.\n");
|
2020-11-03 20:09:58 -07:00
|
|
|
return CR_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parameters.size() == 1)
|
|
|
|
{
|
|
|
|
// display current settings
|
2021-01-22 13:48:36 -07:00
|
|
|
out.print("active settings:\n");
|
|
|
|
|
2020-11-03 20:09:58 -07:00
|
|
|
for (auto & setting : planner.getGlobalSettings())
|
|
|
|
{
|
2021-01-22 13:48:36 -07:00
|
|
|
out.print(" %s = %s\n", setting.first.c_str(),
|
|
|
|
setting.second ? "true" : "false");
|
2020-11-03 20:09:58 -07:00
|
|
|
}
|
|
|
|
|
2021-01-22 13:48:36 -07:00
|
|
|
out.print(" quickfort_mode = %s\n",
|
|
|
|
quickfort_mode ? "true" : "false");
|
2020-11-03 20:09:58 -07:00
|
|
|
}
|
|
|
|
else if (parameters.size() == 3)
|
|
|
|
{
|
|
|
|
// set a setting
|
|
|
|
std::string setting = toLower(parameters[1]);
|
2021-01-22 13:48:36 -07:00
|
|
|
bool val = isTrue(parameters[2]);
|
|
|
|
if (!setSetting(setting, val))
|
|
|
|
{
|
|
|
|
out.printerr("ERROR: invalid parameter: '%s'\n",
|
|
|
|
parameters[1].c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-01-22 19:05:20 -07:00
|
|
|
out.printerr("ERROR: invalid syntax\n");
|
2015-02-14 20:53:06 -07:00
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2013-09-30 03:19:51 -06:00
|
|
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2013-09-30 03:19:51 -06:00
|
|
|
if (!gps)
|
|
|
|
return CR_FAILURE;
|
|
|
|
|
|
|
|
if (enable != is_enabled)
|
|
|
|
{
|
2020-10-25 03:37:22 -06:00
|
|
|
if (DFHack::Core::getInstance().isMapLoaded())
|
|
|
|
planner.reset();
|
2020-10-16 14:52:23 -06:00
|
|
|
|
|
|
|
if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_room_hook, feed).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_query_hook, render).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_place_hook, render).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_room_hook, render).apply(enable))
|
2013-09-30 03:19:51 -06:00
|
|
|
return CR_FAILURE;
|
|
|
|
|
|
|
|
is_enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2013-09-30 03:19:51 -06:00
|
|
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
|
|
|
{
|
2013-01-07 01:17:38 -07:00
|
|
|
commands.push_back(
|
|
|
|
PluginCommand(
|
2020-10-16 14:52:23 -06:00
|
|
|
"buildingplan", "Plan building construction before you have materials",
|
2013-10-30 14:58:14 -06:00
|
|
|
buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version."));
|
2013-01-07 01:17:38 -07:00
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
|
|
|
{
|
|
|
|
switch (event) {
|
|
|
|
case SC_MAP_LOADED:
|
2020-11-12 01:11:13 -07:00
|
|
|
buildingplan_place_hook::invalidateStatics();
|
|
|
|
buildingplan_query_hook::invalidateStatics();
|
2020-10-16 14:52:23 -06:00
|
|
|
planner.reset();
|
2014-06-14 05:50:47 -06:00
|
|
|
roomMonitor.reset(out);
|
2013-01-07 01:17:38 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
2013-01-29 00:44:56 -07:00
|
|
|
}
|
2020-08-12 16:12:53 -06:00
|
|
|
|
2020-10-31 04:03:05 -06:00
|
|
|
static bool is_paused()
|
|
|
|
{
|
|
|
|
return World::ReadPauseState() ||
|
|
|
|
ui->main.mode > df::ui_sidebar_mode::Squads ||
|
|
|
|
!strict_virtual_cast<df::viewscreen_dwarfmodest>(Gui::getCurViewscreen(true));
|
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
static bool cycle_requested = false;
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
#define DAY_TICKS 1200
|
|
|
|
DFhackCExport command_result plugin_onupdate(color_ostream &)
|
|
|
|
{
|
2020-10-31 04:03:05 -06:00
|
|
|
if (Maps::IsValid() && !is_paused()
|
2020-10-16 14:52:23 -06:00
|
|
|
&& (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0))
|
2020-09-08 01:17:56 -06:00
|
|
|
{
|
|
|
|
planner.doCycle();
|
|
|
|
roomMonitor.doCycle();
|
2020-10-16 14:52:23 -06:00
|
|
|
cycle_requested = false;
|
2020-09-08 01:17:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_shutdown(color_ostream &)
|
|
|
|
{
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:12:53 -06:00
|
|
|
// Lua API section
|
|
|
|
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
static bool isPlanModeEnabled(df::building_type type,
|
|
|
|
int16_t subtype,
|
|
|
|
int32_t custom) {
|
|
|
|
return planmode_enabled[toBuildingTypeKey(type, subtype, custom)];
|
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
static bool isPlannableBuilding(df::building_type type,
|
|
|
|
int16_t subtype,
|
|
|
|
int32_t custom) {
|
|
|
|
return planner.isPlannableBuilding(
|
|
|
|
toBuildingTypeKey(type, subtype, custom));
|
2020-08-12 16:12:53 -06:00
|
|
|
}
|
|
|
|
|
2020-11-13 11:18:54 -07:00
|
|
|
static bool isPlannedBuilding(df::building *bld) {
|
|
|
|
return !!planner.getPlannedBuilding(bld);
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:12:53 -06:00
|
|
|
static void addPlannedBuilding(df::building *bld) {
|
|
|
|
planner.addPlannedBuilding(bld);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doCycle() {
|
|
|
|
planner.doCycle();
|
|
|
|
}
|
|
|
|
|
2020-10-16 14:52:23 -06:00
|
|
|
static void scheduleCycle() {
|
|
|
|
cycle_requested = true;
|
|
|
|
}
|
|
|
|
|
2021-01-22 13:48:36 -07:00
|
|
|
static bool setSetting(std::string name, bool value) {
|
2020-10-22 22:37:49 -06:00
|
|
|
if (name == "quickfort_mode")
|
|
|
|
{
|
|
|
|
debug("setting quickfort_mode %d -> %d", quickfort_mode, value);
|
|
|
|
quickfort_mode = value;
|
2021-01-22 13:48:36 -07:00
|
|
|
return true;
|
2020-10-22 22:37:49 -06:00
|
|
|
}
|
2021-01-22 13:48:36 -07:00
|
|
|
return planner.setGlobalSetting(name, value);
|
2020-10-22 22:37:49 -06:00
|
|
|
}
|
|
|
|
|
2020-08-12 16:12:53 -06:00
|
|
|
DFHACK_PLUGIN_LUA_FUNCTIONS {
|
improve UX between automaterial and buildingplan
solves the confusing behavior when both automaterial and buildingplan
are enabled for constructions. the two plugins now communicate with each
other over the Lua layer to negotiate consistent behavior.
if neither plugin is enabled, the standard DF UI acts as normal
if automaterial is enabled but buildingplan is not, then automaterial
behavior is unchanged.
if buildingplan is enabled and automaterial is not then behavior is
the same as other buildings with buildingplan (no material selection
screen, screen stays on building placement screen after placement).
this commit fixes a bug, though, where buildingplan would only lay
down a single tile of contruction instead of a solid block when a
block is requested.
if both plugins are enabled but buildingplan is not enabled for the
building type then automaterial is unchanged from previous behavior,
execpt for an additional header showing the separation between
automaterial hotkeys and buildingplan hotkeys.
finally, if both plugins are enabled and buildingplan is enabled for the
building type then buildingplan behavior prevails, but the box select and
hollow designations features of automaterial are still usable and
useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement"
automaterial hotkeys are hidden in the UI and ignored in the feed. This
is because buildingplan takes over material selection, so 'Auto
Mat-select' doesn't make sense. Buildingplan also already stays on the
placement screen after placement, so 'Reselect Type' is not necessary.
And all buildingplan-placed buildings have relaxed placement
restrictions (e.g. they can be built in mid-air) so 'Open Placement' is
also not necessary. The missing options are replaced with blank lines so
the vertical alignment of all other options stays constant.
we also remove a few extra lua_pop() calls that are made superfluous by
the StackUnwinder.
2020-10-29 12:00:49 -06:00
|
|
|
DFHACK_LUA_FUNCTION(isPlanModeEnabled),
|
2020-08-12 16:12:53 -06:00
|
|
|
DFHACK_LUA_FUNCTION(isPlannableBuilding),
|
2020-11-13 11:18:54 -07:00
|
|
|
DFHACK_LUA_FUNCTION(isPlannedBuilding),
|
2020-08-12 16:12:53 -06:00
|
|
|
DFHACK_LUA_FUNCTION(addPlannedBuilding),
|
|
|
|
DFHACK_LUA_FUNCTION(doCycle),
|
2020-10-16 14:52:23 -06:00
|
|
|
DFHACK_LUA_FUNCTION(scheduleCycle),
|
2020-10-22 22:37:49 -06:00
|
|
|
DFHACK_LUA_FUNCTION(setSetting),
|
2020-08-12 16:12:53 -06:00
|
|
|
DFHACK_LUA_END
|
|
|
|
};
|