Merge pull request #3085 from myk002/myk_buildingplan_max_size

[buildingplan] respect max building size
develop
Myk 2023-03-24 14:35:36 -07:00 committed by GitHub
commit cc5329b935
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 93 additions and 43 deletions

@ -39,6 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Fixes ## Fixes
- `buildingplan`: upright spike traps are now placed extended rather than retracted - `buildingplan`: upright spike traps are now placed extended rather than retracted
- `buildingplan`: fixed material filter getting lost for planning buildings on save/reload - `buildingplan`: fixed material filter getting lost for planning buildings on save/reload
- `buildingplan`: respect building size limits (e.g. roads and bridges cannot be more than 31 tiles in any dimension)
- `tailor`: now properly discriminates between dyed and undyed cloth, no longer defaults to using adamantine, and properly tracks material requirements for already queued orders - `tailor`: now properly discriminates between dyed and undyed cloth, no longer defaults to using adamantine, and properly tracks material requirements for already queued orders
## Misc Improvements ## Misc Improvements

@ -23,13 +23,68 @@ local function is_choosing_area()
return uibs.selection_pos.x >= 0 return uibs.selection_pos.x >= 0
end end
local function get_cur_area_dims(placement_data) -- TODO: reuse data in quickfort database
if not placement_data and not is_choosing_area() then return 1, 1, 1 end local function get_selection_size_limits()
local selection_pos = placement_data and placement_data.p1 or uibs.selection_pos local btype = uibs.building_type
local pos = placement_data and placement_data.p2 or uibs.pos if btype == df.building_type.Bridge
return math.abs(selection_pos.x - pos.x) + 1, or btype == df.building_type.FarmPlot
math.abs(selection_pos.y - pos.y) + 1, or btype == df.building_type.RoadPaved
math.abs(selection_pos.z - pos.z) + 1 or btype == df.building_type.RoadDirt then
return {w=31, h=31}
elseif btype == df.building_type.AxleHorizontal
or btype == df.building_type.Rollers then
return uibs.direction == 1 and {w=1, h=31} or {w=31, h=1}
end
end
local function get_selected_bounds(selection_pos, pos)
selection_pos = selection_pos or uibs.selection_pos
if not is_choosing_area() then return end
pos = pos or uibs.pos
local bounds = {
x1=math.min(selection_pos.x, pos.x),
x2=math.max(selection_pos.x, pos.x),
y1=math.min(selection_pos.y, pos.y),
y2=math.max(selection_pos.y, pos.y),
z1=math.min(selection_pos.z, pos.z),
z2=math.max(selection_pos.z, pos.z),
}
-- clamp to map edges
bounds = {
x1=math.max(0, bounds.x1),
x2=math.min(df.global.world.map.x_count-1, bounds.x2),
y1=math.max(0, bounds.y1),
y2=math.min(df.global.world.map.y_count-1, bounds.y2),
z1=math.max(0, bounds.z1),
z2=math.min(df.global.world.map.z_count-1, bounds.z2),
}
local limits = get_selection_size_limits()
if limits then
-- clamp to building type area limit
bounds = {
x1=math.max(selection_pos.x - (limits.w-1), bounds.x1),
x2=math.min(selection_pos.x + (limits.w-1), bounds.x2),
y1=math.max(selection_pos.y - (limits.h-1), bounds.y1),
y2=math.min(selection_pos.y + (limits.h-1), bounds.y2),
z1=bounds.z1,
z2=bounds.z2,
}
end
return bounds
end
local function get_cur_area_dims(bounds)
if not bounds and not is_choosing_area() then return 1, 1, 1 end
bounds = bounds or get_selected_bounds()
if not bounds then return 1, 1, 1 end
return bounds.x2 - bounds.x1 + 1,
bounds.y2 - bounds.y1 + 1,
bounds.z2 - bounds.z1 + 1
end end
local function is_pressure_plate() local function is_pressure_plate()
@ -53,7 +108,7 @@ end
-- adjusted from CycleHotkeyLabel on the planner panel -- adjusted from CycleHotkeyLabel on the planner panel
local weapon_quantity = 1 local weapon_quantity = 1
local function get_quantity(filter, hollow, placement_data) local function get_quantity(filter, hollow, bounds)
if is_pressure_plate() then if is_pressure_plate() then
local flags = uibs.plate_info.flags local flags = uibs.plate_info.flags
return (flags.units and 1 or 0) + (flags.water and 1 or 0) + return (flags.units and 1 or 0) + (flags.water and 1 or 0) +
@ -62,7 +117,7 @@ local function get_quantity(filter, hollow, placement_data)
return weapon_quantity return weapon_quantity
end end
local quantity = filter.quantity or 1 local quantity = filter.quantity or 1
local dimx, dimy, dimz = get_cur_area_dims(placement_data) local dimx, dimy, dimz = get_cur_area_dims(bounds)
if quantity < 1 then if quantity < 1 then
return (((dimx * dimy) // 4) + 1) * dimz return (((dimx * dimy) // 4) + 1) * dimz
end end
@ -519,19 +574,18 @@ function PlannerOverlay:clear_filter(idx)
end end
local function get_placement_data() local function get_placement_data()
local pos = uibs.pos
local direction = uibs.direction local direction = uibs.direction
local width, height, depth = get_cur_area_dims() local bounds = get_selected_bounds()
local width, height, depth = get_cur_area_dims(bounds)
local _, adjusted_width, adjusted_height = dfhack.buildings.getCorrectSize( local _, adjusted_width, adjusted_height = dfhack.buildings.getCorrectSize(
width, height, uibs.building_type, uibs.building_subtype, width, height, uibs.building_type, uibs.building_subtype,
uibs.custom_type, direction) uibs.custom_type, direction)
-- get the upper-left corner of the building/area at min z-level -- get the upper-left corner of the building/area at min z-level
local has_selection = is_choosing_area() local start_pos = bounds and xyz2pos(bounds.x1, bounds.y1, bounds.z1) or
local start_pos = xyz2pos( xyz2pos(
has_selection and math.min(uibs.selection_pos.x, pos.x) or pos.x - adjusted_width//2, uibs.pos.x - adjusted_width//2,
has_selection and math.min(uibs.selection_pos.y, pos.y) or pos.y - adjusted_height//2, uibs.pos.y - adjusted_height//2,
has_selection and math.min(uibs.selection_pos.z, pos.z) or pos.z uibs.pos.z)
)
if uibs.building_type == df.building_type.ScrewPump then if uibs.building_type == df.building_type.ScrewPump then
if direction == df.screw_pump_direction.FromSouth then if direction == df.screw_pump_direction.FromSouth then
start_pos.y = start_pos.y + 1 start_pos.y = start_pos.y + 1
@ -546,11 +600,11 @@ local function get_placement_data()
and (width > 1 or height > 1 or depth > 1) then and (width > 1 or height > 1 or depth > 1) then
max_x = min_x + width - 1 max_x = min_x + width - 1
max_y = min_y + height - 1 max_y = min_y + height - 1
max_z = math.max(uibs.selection_pos.z, pos.z) max_z = math.max(uibs.selection_pos.z, uibs.pos.z)
end end
return { return {
p1=xyz2pos(min_x, min_y, min_z), x1=min_x, y1=min_y, z1=min_z,
p2=xyz2pos(max_x, max_y, max_z), x2=max_x, y2=max_y, z2=max_z,
width=adjusted_width, width=adjusted_width,
height=adjusted_height height=adjusted_height
} }
@ -564,10 +618,11 @@ function PlannerOverlay:save_placement()
self.saved_pos = copyall(uibs.pos) self.saved_pos = copyall(uibs.pos)
uibs.selection_pos:clear() uibs.selection_pos:clear()
else else
self.saved_selection_pos = copyall(self.saved_placement.p1) local sp = self.saved_placement
self.saved_pos = copyall(self.saved_placement.p2) self.saved_selection_pos = xyz2pos(sp.x1, sp.y1, sp.z1)
self.saved_pos.x = self.saved_pos.x + self.saved_placement.width - 1 self.saved_pos = xyz2pos(sp.x2, sp.y2, sp.z2)
self.saved_pos.y = self.saved_pos.y + self.saved_placement.height - 1 self.saved_pos.x = self.saved_pos.x + sp.width - 1
self.saved_pos.y = self.saved_pos.y + sp.height - 1
end end
end end
@ -624,6 +679,7 @@ function PlannerOverlay:onInput(keys)
local num_filters = #filters local num_filters = #filters
local choose = self.subviews.choose local choose = self.subviews.choose
if choose.enabled() and choose:getOptionValue() then if choose.enabled() and choose:getOptionValue() then
local bounds = get_selected_bounds()
self:save_placement() self:save_placement()
local is_hollow = self.subviews.hollow:getOptionValue() local is_hollow = self.subviews.hollow:getOptionValue()
local chosen_items, active_screens = {}, {} local chosen_items, active_screens = {}, {}
@ -636,8 +692,7 @@ function PlannerOverlay:onInput(keys)
active_screens[idx] = itemselection.ItemSelectionScreen{ active_screens[idx] = itemselection.ItemSelectionScreen{
index=idx, index=idx,
desc=require('plugins.buildingplan').get_desc(filter), desc=require('plugins.buildingplan').get_desc(filter),
quantity=get_quantity(filter, is_hollow, quantity=get_quantity(filter, is_hollow, bounds),
self.saved_placement),
on_submit=function(items) on_submit=function(items)
chosen_items[idx] = items chosen_items[idx] = items
active_screens[idx]:dismiss() active_screens[idx]:dismiss()
@ -694,16 +749,10 @@ function PlannerOverlay:onRenderFrame(dc, rect)
uibs.building_type, uibs.building_subtype, uibs.custom_type)) uibs.building_type, uibs.building_subtype, uibs.custom_type))
end end
local selection_pos = self.saved_selection_pos or uibs.selection_pos if self.minimized then return end
if not selection_pos or selection_pos.x < 0 then return end
local pos = self.saved_pos or uibs.pos local bounds = get_selected_bounds(self.saved_selection_pos, self.saved_pos)
local bounds = { if not bounds then return end
x1 = math.max(0, math.min(selection_pos.x, pos.x)),
x2 = math.min(df.global.world.map.x_count-1, math.max(selection_pos.x, pos.x)),
y1 = math.max(0, math.min(selection_pos.y, pos.y)),
y2 = math.min(df.global.world.map.y_count-1, math.max(selection_pos.y, pos.y)),
}
local hollow = self.subviews.hollow:getOptionValue() local hollow = self.subviews.hollow:getOptionValue()
local default_pen = (self.saved_selection_pos or #uibs.errors == 0) and pens.GOOD_TILE_PEN or pens.BAD_TILE_PEN local default_pen = (self.saved_selection_pos or #uibs.errors == 0) and pens.GOOD_TILE_PEN or pens.BAD_TILE_PEN
@ -726,9 +775,9 @@ function PlannerOverlay:onRenderFrame(dc, rect)
guidm.renderMapOverlay(get_overlay_pen, bounds) guidm.renderMapOverlay(get_overlay_pen, bounds)
end end
function PlannerOverlay:get_stairs_subtype(pos, corner1, corner2) function PlannerOverlay:get_stairs_subtype(pos, bounds)
local subtype = uibs.building_subtype local subtype = uibs.building_subtype
if pos.z == corner1.z then if pos.z == bounds.z1 then
local opt = self.subviews.stairs_bottom_subtype:getOptionValue() local opt = self.subviews.stairs_bottom_subtype:getOptionValue()
if opt == 'auto' then if opt == 'auto' then
local tt = dfhack.maps.getTileType(pos) local tt = dfhack.maps.getTileType(pos)
@ -739,7 +788,7 @@ function PlannerOverlay:get_stairs_subtype(pos, corner1, corner2)
else else
subtype = opt subtype = opt
end end
elseif pos.z == corner2.z then elseif pos.z == bounds.z2 then
local opt = self.subviews.stairs_top_subtype:getOptionValue() local opt = self.subviews.stairs_top_subtype:getOptionValue()
if opt == 'auto' then if opt == 'auto' then
local tt = dfhack.maps.getTileType(pos) local tt = dfhack.maps.getTileType(pos)
@ -755,7 +804,7 @@ function PlannerOverlay:get_stairs_subtype(pos, corner1, corner2)
end end
function PlannerOverlay:place_building(placement_data, chosen_items) function PlannerOverlay:place_building(placement_data, chosen_items)
local p1, p2 = placement_data.p1, placement_data.p2 local pd = placement_data
local blds = {} local blds = {}
local hollow = self.subviews.hollow:getOptionValue() local hollow = self.subviews.hollow:getOptionValue()
local subtype = uibs.building_subtype local subtype = uibs.building_subtype
@ -765,17 +814,17 @@ function PlannerOverlay:place_building(placement_data, chosen_items)
elseif is_weapon_trap() then elseif is_weapon_trap() then
filters[2].quantity = get_quantity(filters[2]) filters[2].quantity = get_quantity(filters[2])
end end
for z=p1.z,p2.z do for y=p1.y,p2.y do for x=p1.x,p2.x do for z=pd.z1,pd.z2 do for y=pd.y1,pd.y2 do for x=pd.x1,pd.x2 do
if hollow and x ~= p1.x and x ~= p2.x and y ~= p1.y and y ~= p2.y then if hollow and x ~= pd.x1 and x ~= pd.x2 and y ~= pd.y1 and y ~= pd.y2 then
goto continue goto continue
end end
local pos = xyz2pos(x, y, z) local pos = xyz2pos(x, y, z)
if is_stairs() then if is_stairs() then
subtype = self:get_stairs_subtype(pos, p1, p2) subtype = self:get_stairs_subtype(pos, pd)
end end
local bld, err = dfhack.buildings.constructBuilding{pos=pos, local bld, err = dfhack.buildings.constructBuilding{pos=pos,
type=uibs.building_type, subtype=subtype, custom=uibs.custom_type, type=uibs.building_type, subtype=subtype, custom=uibs.custom_type,
width=placement_data.width, height=placement_data.height, width=pd.width, height=pd.height,
direction=uibs.direction, filters=filters} direction=uibs.direction, filters=filters}
if err then if err then
-- it's ok if some buildings fail to build -- it's ok if some buildings fail to build