dfhack/plugins/lua/buildingplan.lua

263 lines
9.8 KiB
Lua

local _ENV = mkmodule('plugins.buildingplan')
2020-08-16 00:03:49 -06:00
--[[
Native functions:
* void setSetting(string name, boolean value)
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
* bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)
prep buildingplan for core algorithm changes Lots of refactoring and reorganizing, with only cosmetic player-visible changes. - show quickfort mode hotlkey label regardless of whether the current building type has buildingplan enabled. before, it was only shown after the user enabled buildingplan for the current building. this eliminates the extra step when enabling quickfort mode, which force-enables all building types. - changed signature of lua-exported isPlannableBuilding to take subtype and custom type in addition to building type. this is only used by quickfort, and it already sends all three params in preparation for this change - added lua-exported scheduleCycle(), which is like doCycle(), but only takes effect on the next non-paused frame. this lets quickfort run only one buildingplan cycle regardless of how many #build blueprints were run - declared a few dfhack library methods and params const so buildingplan could call them from const methods - converted buildingplan internal debug logging fn to have a printf api - reshaped buildingplan-planner API and refactored implementation in preparation for upcoming core algorithm changes for supporing all building types (no externally-visible functionality changes) - changed df::building_type params to type, subtype, custom tuple keys - introduced capability to return multiple filters per building type (though the current buildings all only have one filter per) - split monolith hook functions in buildingplan.cpp into one per scope. this significantly cleans up the code and preps the hooks to handle iterating through multiple item filters. - got rid of send_key function and replaced with better reporting of whether keys have been handled
2020-10-16 14:52:23 -06:00
* bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)
2020-11-13 11:18:54 -07:00
* bool isPlannedBuilding(df::building *bld)
2020-08-16 00:03:49 -06:00
* void addPlannedBuilding(df::building *bld)
* void doCycle()
prep buildingplan for core algorithm changes Lots of refactoring and reorganizing, with only cosmetic player-visible changes. - show quickfort mode hotlkey label regardless of whether the current building type has buildingplan enabled. before, it was only shown after the user enabled buildingplan for the current building. this eliminates the extra step when enabling quickfort mode, which force-enables all building types. - changed signature of lua-exported isPlannableBuilding to take subtype and custom type in addition to building type. this is only used by quickfort, and it already sends all three params in preparation for this change - added lua-exported scheduleCycle(), which is like doCycle(), but only takes effect on the next non-paused frame. this lets quickfort run only one buildingplan cycle regardless of how many #build blueprints were run - declared a few dfhack library methods and params const so buildingplan could call them from const methods - converted buildingplan internal debug logging fn to have a printf api - reshaped buildingplan-planner API and refactored implementation in preparation for upcoming core algorithm changes for supporing all building types (no externally-visible functionality changes) - changed df::building_type params to type, subtype, custom tuple keys - introduced capability to return multiple filters per building type (though the current buildings all only have one filter per) - split monolith hook functions in buildingplan.cpp into one per scope. this significantly cleans up the code and preps the hooks to handle iterating through multiple item filters. - got rid of send_key function and replaced with better reporting of whether keys have been handled
2020-10-16 14:52:23 -06:00
* void scheduleCycle()
2020-08-16 00:03:49 -06:00
--]]
local dialogs = require('gui.dialogs')
local guidm = require('gui.dwarfmode')
require('dfhack.buildings')
-- does not need the core suspended
function get_num_filters(btype, subtype, custom)
local filters = dfhack.buildings.getFiltersByType(
{}, btype, subtype, custom)
if filters then return #filters end
return 0
end
local function to_title_case(str)
str = str:gsub('(%a)([%w_]*)',
function (first, rest) return first:upper()..rest:lower() end)
str = str:gsub('_', ' ')
return str
end
local function get_filter(btype, subtype, custom, reverse_idx)
local filters = dfhack.buildings.getFiltersByType(
{}, btype, subtype, custom)
if not filters or reverse_idx < 0 or reverse_idx >= #filters then
error(string.format('invalid index: %d', reverse_idx))
end
return filters[#filters-reverse_idx]
end
-- returns a reasonable label for the item based on the qualities of the filter
-- does not need the core suspended
-- reverse_idx is 0-based and is expected to be counted from the *last* filter
function get_item_label(btype, subtype, custom, reverse_idx)
local filter = get_filter(btype, subtype, custom, reverse_idx)
if filter.has_tool_use then
return to_title_case(df.tool_uses[filter.has_tool_use])
end
if filter.item_type then
return to_title_case(df.item_type[filter.item_type])
end
if filter.flags2 and filter.flags2.building_material then
if filter.flags2.fire_safe then
return "Fire-safe building material";
end
if filter.flags2.magma_safe then
return "Magma-safe building material";
end
return "Generic building material";
end
if filter.vector_id then
return to_title_case(df.job_item_vector_id[filter.vector_id])
end
return "Unknown";
end
-- returns whether the items matched by the specified filter can have a quality
-- rating. This also conveniently indicates whether an item can be decorated.
-- does not need the core suspended
-- reverse_idx is 0-based and is expected to be counted from the *last* filter
function item_can_be_improved(btype, subtype, custom, reverse_idx)
local filter = get_filter(btype, subtype, custom, reverse_idx)
if filter.flags2 and filter.flags2.building_material then
return false;
end
return filter.item_type ~= df.item_type.WOOD and
filter.item_type ~= df.item_type.BLOCKS and
filter.item_type ~= df.item_type.BAR and
filter.item_type ~= df.item_type.BOULDER
end
-- needs the core suspended
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
-- returns a vector of constructed buildings (usually of size 1, but potentially
-- more for constructions)
function construct_buildings_from_ui_state()
local uibs = df.global.ui_build_selector
local world = df.global.world
local direction = world.selected_direction
local _, width, height = dfhack.buildings.getCorrectSize(
world.building_width, world.building_height, uibs.building_type,
uibs.building_subtype, uibs.custom_type, direction)
-- the cursor is at the center of the building; we need the upper-left
-- corner of the building
local pos = guidm.getCursorPos()
pos.x = pos.x - math.floor(width/2)
pos.y = pos.y - math.floor(height/2)
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
local min_x, max_x = pos.x, pos.x
local min_y, max_y = pos.y, pos.y
if width == 1 and height == 1 and
(world.building_width > 1 or world.building_height > 1) then
min_x = math.ceil(pos.x - world.building_width/2)
max_x = math.floor(pos.x + world.building_width/2)
min_y = math.ceil(pos.y - world.building_height/2)
max_y = math.floor(pos.y + world.building_height/2)
end
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
local blds = {}
for y=min_y,max_y do for x=min_x,max_x do
local bld, err = dfhack.buildings.constructBuilding{
type=uibs.building_type, subtype=uibs.building_subtype,
custom=uibs.custom_type, pos=xyz2pos(x, y, pos.z),
width=width, height=height, direction=direction}
if err then
for _,b in ipairs(blds) do
dfhack.buildings.deconstruct(b)
end
error(err)
end
-- assign fields for the types that need them. we can't pass them all in
-- to the call to constructBuilding since attempting to assign unrelated
-- fields to building types that don't support them causes errors.
for k,v in pairs(bld) do
if k == 'friction' then bld.friction = uibs.friction end
if k == 'use_dump' then bld.use_dump = uibs.use_dump end
if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end
if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end
if k == 'speed' then bld.speed = uibs.speed end
end
table.insert(blds, bld)
end end
return blds
end
--
-- GlobalSettings dialog
--
local GlobalSettings = defclass(GlobalSettings, dialogs.MessageBox)
GlobalSettings.focus_path = 'buildingplan_globalsettings'
GlobalSettings.ATTRS{
settings = {}
}
function GlobalSettings:onDismiss()
for k,v in pairs(self.settings) do
-- call back into C++ to save changes
setSetting(k, v)
end
end
-- does not need the core suspended.
function show_global_settings_dialog(settings)
GlobalSettings{
frame_title="Buildingplan Global Settings",
settings=settings,
}:show()
end
function GlobalSettings:toggle_setting(name)
self.settings[name] = not self.settings[name]
end
function GlobalSettings:get_setting_string(name)
if self.settings[name] then return 'On' end
return 'Off'
end
2020-10-28 22:02:59 -06:00
function GlobalSettings:get_setting_pen(name)
if self.settings[name] then return COLOR_LIGHTGREEN end
return COLOR_LIGHTRED
end
function GlobalSettings:is_setting_enabled(name)
return self.settings[name]
end
function GlobalSettings:make_setting_label_token(text, key, name, width)
2020-10-28 22:02:59 -06:00
return {text=text, key=key, key_sep=': ', key_pen=COLOR_LIGHTGREEN,
on_activate=self:callback('toggle_setting', name), width=width}
end
function GlobalSettings:make_setting_value_token(name)
return {text=self:callback('get_setting_string', name),
enabled=self:callback('is_setting_enabled', name),
2020-10-28 22:02:59 -06:00
pen=self:callback('get_setting_pen', name),
dpen=COLOR_GRAY}
end
-- mockup:
--[[
Buildingplan Global Settings
e: Enable all: Off
Enables buildingplan for all building types. Use this to avoid having to
manually enable buildingplan for each building type that you want to plan.
Note that DFHack quickfort will use buildingplan to manage buildings
regardless of whether buildingplan is "enabled" for the building type.
Allowed types for generic, fire-safe, and magma-safe building material:
b: Blocks: On
s: Boulders: On
w: Wood: On
r: Bars: Off
Changes to these settings will be applied to newly-planned buildings.
A: Apply building material filter settings to existing planned buildings
Use this if your planned buildings can't be completed because the settings
above were too restrictive when the buildings were originally planned.
M: Edit list of materials to avoid
potash
pearlash
ash
coal
Buildingplan will avoid using these material types when a planned building's
material filter is set to 'any'. They can stil be matched when they are
explicitly allowed by a planned building's material filter. Changes to this
list take effect for existing buildings immediately.
g: Allow bags: Off
This allows bags to be placed where a 'coffer' is planned.
f: Legacy Quickfort Mode: Off
Compatibility mode for the legacy Python-based Quickfort application. This
setting is not needed for DFHack quickfort.
--]]
function GlobalSettings:init()
self.subviews.label:setText{
self:make_setting_label_token('Enable all', 'CUSTOM_E', 'all_enabled', 12),
self:make_setting_value_token('all_enabled'), '\n',
' Enables buildingplan for all building types. Use this to avoid having\n',
' to manually enable buildingplan for each building type that you want\n',
' to plan. Note that DFHack quickfort will use buildingplan to manage\n',
' buildings regardless of whether buildingplan is "enabled" for the\n',
' building type.\n',
'\n',
'Allowed types for generic, fire-safe, and magma-safe building material:\n',
self:make_setting_label_token('Blocks', 'CUSTOM_B', 'blocks', 10),
self:make_setting_value_token('blocks'), '\n',
self:make_setting_label_token('Boulders', 'CUSTOM_S', 'boulders', 10),
self:make_setting_value_token('boulders'), '\n',
self:make_setting_label_token('Wood', 'CUSTOM_W', 'logs', 10),
self:make_setting_value_token('logs'), '\n',
self:make_setting_label_token('Bars', 'CUSTOM_R', 'bars', 10),
self:make_setting_value_token('bars'), '\n',
' Changes to these settings will be applied to newly-planned buildings.\n',
' If no types are enabled above, then any building material is allowed.\n',
'\n',
self:make_setting_label_token('Legacy Quickfort Mode', 'CUSTOM_F',
'quickfort_mode', 23),
self:make_setting_value_token('quickfort_mode'), '\n',
' Compatibility mode for the legacy Python-based Quickfort application.\n',
' This setting is not needed for DFHack quickfort.'
}
end
return _ENV