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
- `buildingplan`: upright spike traps are now placed extended rather than retracted
- `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
## Misc Improvements

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