diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 0a05c4ed5..6c16b043e 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -171,6 +171,7 @@ static bool call_buildingplan_lua(color_ostream *out, const char *fn_name, static void clear_state(color_ostream &out) { call_buildingplan_lua(&out, "signal_reset"); + call_buildingplan_lua(&out, "reload_cursors"); planned_buildings.clear(); tasks.clear(); for (auto &entry : job_item_repo) { diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index cb5926723..b24f5893a 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -75,17 +75,18 @@ local function is_choosing_area() return uibs.selection_pos.x >= 0 end -local function get_cur_area_dims(pos) - if not is_choosing_area() then return 1, 1, 1 end - pos = pos or uibs.pos - return math.abs(uibs.selection_pos.x - pos.x) + 1, - math.abs(uibs.selection_pos.y - pos.y) + 1, - math.abs(uibs.selection_pos.z - pos.z) + 1 +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 end -local function get_quantity(filter) +local function get_quantity(filter, placement_data) local quantity = filter.quantity or 1 - local dimx, dimy, dimz = get_cur_area_dims() + local dimx, dimy, dimz = get_cur_area_dims(placement_data) if quantity < 1 then quantity = (((dimx * dimy) // 4) + 1) * dimz else @@ -151,16 +152,16 @@ ItemSelection = defclass(ItemSelection, widgets.Window) ItemSelection.ATTRS{ frame_title='Choose items', frame={w=56, h=20, l=4, t=8}, - draggable=false, resizable=true, index=DEFAULT_NIL, + placement_data=DEFAULT_NIL, on_submit=DEFAULT_NIL, on_cancel=DEFAULT_NIL, } function ItemSelection:init() local filter = get_cur_filters()[self.index] - self.quantity = get_quantity(filter) + self.quantity = get_quantity(filter, self.placement_data) self.num_selected = 0 self.selected_set = {} local plural = self.quantity == 1 and '' or 's' @@ -364,8 +365,9 @@ end ItemSelectionScreen = defclass(ItemSelectionScreen, BuildingplanScreen) ItemSelectionScreen.ATTRS { - focus_path='buildingplan/itemselection', + focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/itemselection', index=DEFAULT_NIL, + placement_data=DEFAULT_NIL, on_submit=DEFAULT_NIL, on_cancel=DEFAULT_NIL, } @@ -374,6 +376,7 @@ function ItemSelectionScreen:init() self:addviews{ ItemSelection{ index=self.index, + placement_data=self.placement_data, on_submit=self.on_submit, on_cancel=self.on_cancel, } @@ -401,7 +404,6 @@ FilterSelection = defclass(FilterSelection, widgets.Window) FilterSelection.ATTRS{ frame_title='Choose filters', frame={w=60, h=40, l=4, t=8}, - draggable=false, resizable=true, index=DEFAULT_NIL, } @@ -411,7 +413,7 @@ end FilterSelectionScreen = defclass(FilterSelectionScreen, BuildingplanScreen) FilterSelectionScreen.ATTRS { - focus_path='buildingplan/filterselection', + focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/filterselection', index=DEFAULT_NIL, } @@ -818,6 +820,73 @@ function PlannerOverlay:clear_filter(idx) setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, idx, "") end +local function get_placement_data() + local pos = uibs.pos + local direction = uibs.direction + local width, height, depth = get_cur_area_dims() + 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 + ) + if uibs.building_type == df.building_type.ScrewPump then + if direction == df.screw_pump_direction.FromSouth then + start_pos.y = start_pos.y + 1 + elseif direction == df.screw_pump_direction.FromEast then + start_pos.x = start_pos.x + 1 + end + end + local min_x, max_x = start_pos.x, start_pos.x + local min_y, max_y = start_pos.y, start_pos.y + local min_z, max_z = start_pos.z, start_pos.z + if adjusted_width == 1 and adjusted_height == 1 + 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) + end + return { + p1=xyz2pos(min_x, min_y, min_z), + p2=xyz2pos(max_x, max_y, max_z), + width=adjusted_width, + height=adjusted_height + } +end + +function PlannerOverlay:save_placement() + self.saved_placement = get_placement_data() + if (uibs.selection_pos:isValid()) then + self.saved_selection_pos_valid = true + self.saved_selection_pos = copyall(uibs.selection_pos) + 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 + end +end + +function PlannerOverlay:restore_placement() + if self.saved_selection_pos_valid then + uibs.selection_pos = self.saved_selection_pos + self.saved_selection_pos_valid = nil + else + uibs.selection_pos:clear() + end + self.saved_selection_pos = nil + self.saved_pos = nil + local placement_data = self.saved_placement + self.saved_placement = nil + return placement_data +end + function PlannerOverlay:onInput(keys) if not is_plannable() then return false end if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then @@ -845,8 +914,7 @@ function PlannerOverlay:onInput(keys) return true end if #uibs.errors > 0 then return true end - local pos = dfhack.gui.getMousePos() - if pos then + if dfhack.gui.getMousePos() then if is_choosing_area() or cur_building_has_no_area() then local num_filters = #get_cur_filters() if num_filters == 0 then @@ -854,6 +922,7 @@ function PlannerOverlay:onInput(keys) end local choose = self.subviews.choose if choose.enabled() and choose:getOptionValue() then + self:save_placement() local chosen_items, active_screens = {}, {} local pending = num_filters for idx = num_filters,1,-1 do @@ -861,19 +930,21 @@ function PlannerOverlay:onInput(keys) if (self.subviews['item'..idx].available or 0) > 0 then active_screens[idx] = ItemSelectionScreen{ index=idx, + placement_data=self.saved_placement, on_submit=function(items) chosen_items[idx] = items active_screens[idx]:dismiss() active_screens[idx] = nil pending = pending - 1 if pending == 0 then - self:place_building(pos, chosen_items) + self:place_building(self:restore_placement(), chosen_items) end end, on_cancel=function() for i,scr in pairs(active_screens) do scr:dismiss() end + self:restore_placement() end, }:show() else @@ -881,7 +952,7 @@ function PlannerOverlay:onInput(keys) end end else - self:place_building(pos) + self:place_building(get_placement_data()) end return true elseif not is_choosing_area() then @@ -898,10 +969,12 @@ function PlannerOverlay:render(dc) PlannerOverlay.super.render(self, dc) end -local GOOD_PEN = to_pen{ch='o', fg=COLOR_GREEN, - tile=dfhack.screen.findGraphicsTile('CURSORS', 1, 2)} -local BAD_PEN = to_pen{ch='X', fg=COLOR_RED, - tile=dfhack.screen.findGraphicsTile('CURSORS', 3, 0)} +local GOOD_PEN, BAD_PEN +function reload_cursors() + GOOD_PEN = to_pen{ch='o', fg=COLOR_GREEN, tile=dfhack.screen.findGraphicsTile('CURSORS', 1, 2)} + BAD_PEN = to_pen{ch='X', fg=COLOR_RED, tile=dfhack.screen.findGraphicsTile('CURSORS', 3, 0)} +end +reload_cursors() function PlannerOverlay:onRenderFrame(dc, rect) PlannerOverlay.super.onRenderFrame(self, dc, rect) @@ -912,17 +985,18 @@ function PlannerOverlay:onRenderFrame(dc, rect) uibs.building_type, uibs.building_subtype, uibs.custom_type)) end - if not is_choosing_area() 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 = uibs.pos + local pos = self.saved_pos or uibs.pos local bounds = { - x1 = math.min(uibs.selection_pos.x, pos.x), - x2 = math.max(uibs.selection_pos.x, pos.x), - y1 = math.min(uibs.selection_pos.y, pos.y), - y2 = math.max(uibs.selection_pos.y, pos.y), + 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), } - local pen = #uibs.errors > 0 and BAD_PEN or GOOD_PEN + local pen = (self.saved_selection_pos or #uibs.errors == 0) and GOOD_PEN or BAD_PEN local function get_overlay_pen(pos) return pen @@ -931,69 +1005,47 @@ function PlannerOverlay:onRenderFrame(dc, rect) guidm.renderMapOverlay(get_overlay_pen, bounds) end -function PlannerOverlay:place_building(pos, chosen_items) - local direction = uibs.direction - local width, height, depth = get_cur_area_dims(pos) - 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 - ) - if uibs.building_type == df.building_type.ScrewPump then - if direction == df.screw_pump_direction.FromSouth then - start_pos.y = start_pos.y + 1 - elseif direction == df.screw_pump_direction.FromEast then - start_pos.x = start_pos.x + 1 +function PlannerOverlay:get_stairs_subtype(pos, corner1, corner2) + local subtype = uibs.building_subtype + if pos.z == corner1.z then + local opt = self.subviews.stairs_bottom_subtype:getOptionValue() + if opt == 'auto' then + local tt = dfhack.maps.getTileType(pos) + local shape = df.tiletype.attrs[tt].shape + if shape ~= df.tiletype_shape.STAIR_DOWN then + subtype = df.construction_type.UpStair + end + else + subtype = opt + end + elseif pos.z == corner2.z then + local opt = self.subviews.stairs_top_subtype:getOptionValue() + if opt == 'auto' then + local tt = dfhack.maps.getTileType(pos) + local shape = df.tiletype.attrs[tt].shape + if shape ~= df.tiletype_shape.STAIR_UP then + subtype = df.construction_type.DownStair + end + else + subtype = opt end end - local min_x, max_x = start_pos.x, start_pos.x - local min_y, max_y = start_pos.y, start_pos.y - local min_z, max_z = start_pos.z, start_pos.z - if adjusted_width == 1 and adjusted_height == 1 - 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) - end + return subtype +end + +function PlannerOverlay:place_building(placement_data, chosen_items) + local p1, p2 = placement_data.p1, placement_data.p2 local blds = {} local subtype = uibs.building_subtype - for z=min_z,max_z do for y=min_y,max_y do for x=min_x,max_x do + for z=p1.z,p2.z do for y=p1.y,p2.y do for x=p1.x,p2.x do local pos = xyz2pos(x, y, z) if is_stairs() then - if z == min_z then - subtype = self.subviews.stairs_bottom_subtype:getOptionValue() - if subtype == 'auto' then - local tt = dfhack.maps.getTileType(pos) - local shape = df.tiletype.attrs[tt].shape - if shape == df.tiletype_shape.STAIR_DOWN then - subtype = uibs.building_subtype - else - subtype = df.construction_type.UpStair - end - end - elseif z == max_z then - subtype = self.subviews.stairs_top_subtype:getOptionValue() - if subtype == 'auto' then - local tt = dfhack.maps.getTileType(pos) - local shape = df.tiletype.attrs[tt].shape - if shape == df.tiletype_shape.STAIR_UP then - subtype = uibs.building_subtype - else - subtype = df.construction_type.DownStair - end - end - else - subtype = uibs.building_subtype - end + subtype = self:get_stairs_subtype(pos, p1, p2) end local bld, err = dfhack.buildings.constructBuilding{pos=pos, type=uibs.building_type, subtype=subtype, custom=uibs.custom_type, - width=adjusted_width, height=adjusted_height, direction=direction} + width=placement_data.width, height=placement_data.height, + direction=uibs.direction} if err then for _,b in ipairs(blds) do dfhack.buildings.deconstruct(b)