2020-08-12 16:12:53 -06:00
|
|
|
local _ENV = mkmodule('plugins.buildingplan')
|
|
|
|
|
2020-08-16 00:03:49 -06:00
|
|
|
--[[
|
|
|
|
|
|
|
|
Native functions:
|
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
* 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)
|
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()
|
2020-10-16 14:52:23 -06:00
|
|
|
* void scheduleCycle()
|
2020-08-16 00:03:49 -06:00
|
|
|
|
|
|
|
--]]
|
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
local dialogs = require('gui.dialogs')
|
2020-10-16 15:03:05 -06:00
|
|
|
local guidm = require('gui.dwarfmode')
|
|
|
|
require('dfhack.buildings')
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
-- 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
|
|
|
|
|
2020-10-31 03:25:26 -06:00
|
|
|
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
|
|
|
|
|
2020-10-16 15:08:52 -06:00
|
|
|
-- 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)
|
2020-10-31 03:25:26 -06:00
|
|
|
local filter = get_filter(btype, subtype, custom, reverse_idx)
|
2020-10-16 15:08:52 -06:00
|
|
|
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
|
|
|
|
|
2020-10-31 03:25:26 -06:00
|
|
|
-- 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
|
|
|
|
|
2020-10-16 15:03:05 -06:00
|
|
|
-- 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()
|
2020-10-16 15:03:05 -06:00
|
|
|
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)
|
2020-10-16 15:23:35 -06:00
|
|
|
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
|
2020-10-16 15:03:05 -06:00
|
|
|
end
|
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
--
|
|
|
|
-- 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
|
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
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,
|
2020-10-22 22:37:49 -06:00
|
|
|
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}
|
2020-10-22 22:37:49 -06:00
|
|
|
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()
|
2020-11-04 09:52:19 -07:00
|
|
|
|
2020-10-22 22:37:49 -06:00
|
|
|
self.subviews.label:setText{
|
2020-11-04 09:52:19 -07:00
|
|
|
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',
|
2020-10-22 22:37:49 -06:00
|
|
|
'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',
|
2020-10-26 22:13:11 -06:00
|
|
|
' If no types are enabled above, then any building material is allowed.\n',
|
2020-10-22 22:37:49 -06:00
|
|
|
'\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
|
|
|
|
|
2020-08-12 16:12:53 -06:00
|
|
|
return _ENV
|