263 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Lua
		
	
			
		
		
	
	
			263 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Lua
		
	
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
 |