@ -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 p 1, p2 = placement_data.p1 , placement_data.p2
local p d = 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 = p 1.z, p2.z do for y = p1.y , p2.y do for x = p1.x , p2.x do
for z = p d.z1, pd.z2 do for y = pd.y1 , pd.y2 do for x = pd.x1 , pd.x2 do
if hollow and x ~= p 1.x and x ~= p2.x and y ~= p1.y and y ~= p2.y then
if hollow and x ~= p d.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 , p 1, p2 )
subtype = self : get_stairs_subtype ( pos , p d )
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 = p lacement_ data .width, height = p lacement_ data .height,
width = p d.width, height = p d.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