local _ENV = mkmodule('plugins.buildingplan') --[[ Native functions: * void setSetting(string name, boolean value) * bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom) * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) * bool isPlannedBuilding(df::building *bld) * void addPlannedBuilding(df::building *bld) * void doCycle() * void scheduleCycle() --]] 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 -- 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) 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 = min_x + world.building_width - 1 min_y = math.ceil(pos.y - world.building_height/2) max_y = min_y + world.building_height - 1 end 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 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) 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), 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