From 013c6fe02a376fa1a2b4d6823879a8171a2f5ee1 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 23 Mar 2023 11:07:10 -0700 Subject: [PATCH 1/2] don't render footprint if we're minmized --- plugins/lua/buildingplan/planneroverlay.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index a60b2ebcd..ec05e90f6 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -694,6 +694,8 @@ function PlannerOverlay:onRenderFrame(dc, rect) uibs.building_type, uibs.building_subtype, uibs.custom_type)) end + if self.minimized then return end + local selection_pos = self.saved_selection_pos or uibs.selection_pos if not selection_pos or selection_pos.x < 0 then return end From 99d050d0a85f6120f8bed2fa1775dc479b7f852c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 24 Mar 2023 14:06:39 -0700 Subject: [PATCH 2/2] respect building size limits --- docs/changelog.txt | 1 + plugins/lua/buildingplan/planneroverlay.lua | 135 +++++++++++++------- 2 files changed, 92 insertions(+), 44 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2a5b2cf15..88c6eac3b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -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 diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index ec05e90f6..e92824f5f 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -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() @@ -696,16 +751,8 @@ function PlannerOverlay:onRenderFrame(dc, rect) if self.minimized then return end - local selection_pos = self.saved_selection_pos or uibs.selection_pos - if not selection_pos or selection_pos.x < 0 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 @@ -728,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) @@ -741,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) @@ -757,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 @@ -767,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