From a7f269b7dfd3efe09da505ae6ab209f16395ac0c Mon Sep 17 00:00:00 2001 From: Robob27 Date: Wed, 8 Nov 2023 02:40:07 -0500 Subject: [PATCH 1/5] Add reorderstops overlay --- changelog.txt | 5 +- docs/trackstop.rst | 3 +- trackstop.lua | 191 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index f7e41acd3b..2b23a2c5b8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -28,7 +28,10 @@ Template for new versions: ## New Tools - `sync-windmills`: synchronize or randomize movement of active windmills -- `trackstop`: new overlay to allow changing track stop dump direction and friction and roller direction and speed after construction +- `trackstop`: provides 3 new overlays: + - trackstop: allow changing track stop dump direction and friction + - rollers: allow changing roller direction and speed + - reorderstops: reorder stops in hauling routes ## New Features - `gui/design`: show selected dimensions next to the mouse cursor when designating with vanilla tools, for example when painting a burrow or designating digging diff --git a/docs/trackstop.rst b/docs/trackstop.rst index 88579b783f..50a2e0b685 100644 --- a/docs/trackstop.rst +++ b/docs/trackstop.rst @@ -5,6 +5,7 @@ trackstop :summary: Add dynamic configuration options for track stops. :tags: fort buildings interface -This script provides 2 overlays that are managed by the `overlay` framework. The script does nothing when executed. +This script provides 3 overlays that are managed by the `overlay` framework. The script does nothing when executed. The trackstop overlay allows the player to change the friction and dump direction of a selected track stop after it has been constructed. The rollers overlay allows the player to change the roller direction and speed of a selected roller after it has been constructed. +The reorderstops overlay allows the player to change the order of stops in a hauling route. diff --git a/trackstop.lua b/trackstop.lua index 6657209b08..4f8d25ee9a 100644 --- a/trackstop.lua +++ b/trackstop.lua @@ -52,6 +52,23 @@ local DIRECTION_MAP = { local DIRECTION_MAP_REVERSE = utils.invert(DIRECTION_MAP) +local function swapElements(tbl, index1, index2) + tbl[index1], tbl[index2] = tbl[index2], tbl[index1] + return tbl +end + +local function reset_guide_paths(conditions) + for _i, condition in ipairs(conditions) do + local gpath = condition.guide_path + + if gpath then + gpath.x:resize(0) + gpath.y:resize(0) + gpath.z:resize(0) + end + end +end + TrackStopOverlay = defclass(TrackStopOverlay, overlay.OverlayWidget) TrackStopOverlay.ATTRS{ default_pos={x=-73, y=29}, @@ -120,7 +137,11 @@ function TrackStopOverlay:setDumpDirection(direction) end function TrackStopOverlay:render(dc) - local building = dfhack.gui.getSelectedBuilding() + local building = dfhack.gui.getSelectedBuilding(true) + if not building then + return + end + local friction = building.friction local friction_cycle = self.subviews.friction @@ -201,7 +222,10 @@ function RollerOverlay:setSpeed(speed) end function RollerOverlay:render(dc) - local building = dfhack.gui.getSelectedBuilding() + local building = dfhack.gui.getSelectedBuilding(true) + if not building then + return + end self.subviews.direction:setOption(DIRECTION_MAP_REVERSE[building.direction]) self.subviews.speed:setOption(SPEED_MAP_REVERSE[building.speed]) @@ -236,7 +260,170 @@ function RollerOverlay:init() } end +selected_stop = selected_stop or nil + +ReorderStopsWindow = defclass(ReorderStopsWindow, widgets.Window) +ReorderStopsWindow.ATTRS { + frame={t=4,l=60,w=45, h=28}, + frame_title='Reorder Stops', + view_id='main', +} + +local SELECT_STOP_HINT = 'Select a stop to move' +local SELECT_ANOTHER_STOP_HINT = 'Select another stop on the same route' + +function ReorderStopsWindow:init() + self:addviews{ + widgets.Label{ + frame={t=0,l=0}, + view_id='hint', + text=SELECT_STOP_HINT, + }, + widgets.List{ + view_id='routes', + frame={t=1,l=1}, + choices={}, + on_select=function(index, item) + if not item then return end + if item.type == 'stop' then + local item_pos = df.global.plotinfo.hauling.routes[item.route_index].stops[item.stop_index].pos + df.global.game.main_interface.recenter_indicator_m.x = item_pos.x + df.global.game.main_interface.recenter_indicator_m.y = item_pos.y + df.global.game.main_interface.recenter_indicator_m.z = item_pos.z + dfhack.gui.revealInDwarfmodeMap(item_pos, true) + end + end, + on_submit=function(index, item) + if selected_stop then + local hauling = df.global.plotinfo.hauling + local routes = hauling.routes + local view_stops = hauling.view_stops + local route = routes[item.route_index] + + -- rearrange stops + if item.type == 'stop' then + local stop_index = item.stop_index + + -- don't allow moving stops to a different route + if selected_stop.route_index ~= item.route_index then + return + end + + swapElements(route.stops, stop_index, selected_stop.stop_index) + swapElements(view_stops, selected_stop.list_position, index - 1) + + -- loop over each stop in the route, make the ids sequental and reset guide paths + -- TODO: what else does this break? + for i, stop in ipairs(route.stops) do + stop.id = i + 1 + reset_guide_paths(stop.conditions) + end + + selected_stop = nil + end + else + if item.stop_index then + selected_stop = item + end + end + + self:updateList() + end, + }, + } + + self:updateList() +end + +function ReorderStopsWindow:updateList() + local routes = df.global.plotinfo.hauling.routes + local choices = {} + local list_position = 0 + + if selected_stop then + self.subviews.hint:setText(SELECT_ANOTHER_STOP_HINT) + else + self.subviews.hint:setText(SELECT_STOP_HINT) + end + + for i, route in ipairs(routes) do + local stops = route.stops + local route_name = route.name + + if route_name == '' then + route_name = 'Route ' .. route.id + end + + table.insert(choices, {text=route_name, type='route', route_index=i, list_position=list_position}) + list_position = list_position + 1 + + for j, stop in ipairs(stops) do + local stop_name = stop.name + + if stop_name == '' then + stop_name = 'Stop ' .. stop.id + end + + if selected_stop and selected_stop.list_position == list_position then + stop_name = '=> ' .. stop_name + end + + stop_name = ' ' .. stop_name + + table.insert(choices, {text=stop_name, type='stop', stop_index=j, route_index=i, list_position=list_position}) + list_position = list_position + 1 + end + end + + self.subviews.routes:setChoices(choices) +end + +ReorderStopsModal = defclass(ReorderStopsModal, gui.ZScreenModal) + +ReorderStopsModal.ATTRS = { + focus_path = 'ReorderStops', +} + +function ReorderStopsModal:init() + self:addviews{ReorderStopsWindow{}} +end + +function ReorderStopsModal:onDismiss() + reorder_stops_modal = nil + selected_stop = nil + df.global.game.main_interface.recenter_indicator_m.x = -30000 + df.global.game.main_interface.recenter_indicator_m.y = -30000 + df.global.game.main_interface.recenter_indicator_m.z = -30000 +end + +ReorderStopsOverlay = defclass(ReorderStopsOverlay, overlay.OverlayWidget) +ReorderStopsOverlay.ATTRS{ + default_pos={x=6, y=6}, + default_enabled=true, + viewscreens='dwarfmode/Hauling', + frame={w=30, h=1}, + frame_background=gui.CLEAR_PEN, +} + +function ReorderStopsOverlay:init() + self:addviews{ + widgets.BannerPanel{ + subviews = { + widgets.HotkeyLabel{ + frame={t=0, l=1, r=1}, + label='DFHack reorder stops', + key='CUSTOM_CTRL_E', + on_activate=function() + reorder_stops_modal = reorder_stops_modal and reorder_stops_modal:raise() or ReorderStopsModal{}:show() + end, + }, + }, + }, + } +end + OVERLAY_WIDGETS = { trackstop=TrackStopOverlay, rollers=RollerOverlay, + reorderstops=ReorderStopsOverlay, } From 882f8e2a208a25e8505e008d91913dd168bc4f41 Mon Sep 17 00:00:00 2001 From: Robob27 Date: Wed, 8 Nov 2023 04:32:13 -0500 Subject: [PATCH 2/5] Feedback/improvements --- trackstop.lua | 52 ++++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/trackstop.lua b/trackstop.lua index 4f8d25ee9a..33771c1750 100644 --- a/trackstop.lua +++ b/trackstop.lua @@ -58,7 +58,7 @@ local function swapElements(tbl, index1, index2) end local function reset_guide_paths(conditions) - for _i, condition in ipairs(conditions) do + for _, condition in ipairs(conditions) do local gpath = condition.guide_path if gpath then @@ -260,19 +260,18 @@ function RollerOverlay:init() } end -selected_stop = selected_stop or nil - ReorderStopsWindow = defclass(ReorderStopsWindow, widgets.Window) ReorderStopsWindow.ATTRS { frame={t=4,l=60,w=45, h=28}, frame_title='Reorder Stops', - view_id='main', + resizable=true, } local SELECT_STOP_HINT = 'Select a stop to move' local SELECT_ANOTHER_STOP_HINT = 'Select another stop on the same route' function ReorderStopsWindow:init() + self.selected_stop = nil self:addviews{ widgets.Label{ frame={t=0,l=0}, @@ -283,18 +282,15 @@ function ReorderStopsWindow:init() view_id='routes', frame={t=1,l=1}, choices={}, - on_select=function(index, item) + on_select=function(_, item) if not item then return end if item.type == 'stop' then local item_pos = df.global.plotinfo.hauling.routes[item.route_index].stops[item.stop_index].pos - df.global.game.main_interface.recenter_indicator_m.x = item_pos.x - df.global.game.main_interface.recenter_indicator_m.y = item_pos.y - df.global.game.main_interface.recenter_indicator_m.z = item_pos.z - dfhack.gui.revealInDwarfmodeMap(item_pos, true) + dfhack.gui.revealInDwarfmodeMap(item_pos, true, true) end end, on_submit=function(index, item) - if selected_stop then + if self.selected_stop then local hauling = df.global.plotinfo.hauling local routes = hauling.routes local view_stops = hauling.view_stops @@ -304,26 +300,26 @@ function ReorderStopsWindow:init() if item.type == 'stop' then local stop_index = item.stop_index - -- don't allow moving stops to a different route - if selected_stop.route_index ~= item.route_index then + -- don't allow moving stops to a different route for now. TODO: investigate this + if self.selected_stop.route_index ~= item.route_index then return end - swapElements(route.stops, stop_index, selected_stop.stop_index) - swapElements(view_stops, selected_stop.list_position, index - 1) + swapElements(route.stops, stop_index, self.selected_stop.stop_index) + swapElements(view_stops, self.selected_stop.list_position, index - 1) -- loop over each stop in the route, make the ids sequental and reset guide paths - -- TODO: what else does this break? + -- TODO: figure out if changing the ids here breaks anything else for i, stop in ipairs(route.stops) do stop.id = i + 1 reset_guide_paths(stop.conditions) end - selected_stop = nil + self.selected_stop = nil end else if item.stop_index then - selected_stop = item + self.selected_stop = item end end @@ -340,7 +336,7 @@ function ReorderStopsWindow:updateList() local choices = {} local list_position = 0 - if selected_stop then + if self.selected_stop then self.subviews.hint:setText(SELECT_ANOTHER_STOP_HINT) else self.subviews.hint:setText(SELECT_STOP_HINT) @@ -364,7 +360,7 @@ function ReorderStopsWindow:updateList() stop_name = 'Stop ' .. stop.id end - if selected_stop and selected_stop.list_position == list_position then + if self.selected_stop and self.selected_stop.list_position == list_position then stop_name = '=> ' .. stop_name end @@ -389,8 +385,6 @@ function ReorderStopsModal:init() end function ReorderStopsModal:onDismiss() - reorder_stops_modal = nil - selected_stop = nil df.global.game.main_interface.recenter_indicator_m.x = -30000 df.global.game.main_interface.recenter_indicator_m.y = -30000 df.global.game.main_interface.recenter_indicator_m.z = -30000 @@ -407,17 +401,11 @@ ReorderStopsOverlay.ATTRS{ function ReorderStopsOverlay:init() self:addviews{ - widgets.BannerPanel{ - subviews = { - widgets.HotkeyLabel{ - frame={t=0, l=1, r=1}, - label='DFHack reorder stops', - key='CUSTOM_CTRL_E', - on_activate=function() - reorder_stops_modal = reorder_stops_modal and reorder_stops_modal:raise() or ReorderStopsModal{}:show() - end, - }, - }, + widgets.TextButton{ + frame={t=0, l=0}, + label='DFHack reorder stops', + key='CUSTOM_CTRL_E', + on_activate=function() ReorderStopsModal{}:show() end, }, } end From 2a5bac302a99fa0d076aea1b4e5c7d107d41e328 Mon Sep 17 00:00:00 2001 From: Robob27 Date: Wed, 8 Nov 2023 18:14:23 -0500 Subject: [PATCH 3/5] Allow swapping between different routes, cleanup --- trackstop.lua | 121 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 37 deletions(-) diff --git a/trackstop.lua b/trackstop.lua index 33771c1750..0852cc3d6d 100644 --- a/trackstop.lua +++ b/trackstop.lua @@ -52,9 +52,17 @@ local DIRECTION_MAP = { local DIRECTION_MAP_REVERSE = utils.invert(DIRECTION_MAP) -local function swapElements(tbl, index1, index2) - tbl[index1], tbl[index2] = tbl[index2], tbl[index1] - return tbl +--[[ + - swap 2 elements between different indexes in the same table like: + swap_elements({1, 2, 3}, 1, nil, 3) => {3, 2, 1} + - swap 2 elements at the specified indexes between 2 tables like: + swap_elements({1, 2, 3}, 1, {4, 5, 6}, 3) => {6, 2, 3} {4, 5, 1} +]]-- +local function swap_elements(tbl1, index1, tbl2, index2) + tbl2 = tbl2 or tbl1 + index2 = index2 or index1 + tbl1[index1], tbl2[index2] = tbl2[index2], tbl1[index1] + return tbl1, tbl2 end local function reset_guide_paths(conditions) @@ -270,6 +278,78 @@ ReorderStopsWindow.ATTRS { local SELECT_STOP_HINT = 'Select a stop to move' local SELECT_ANOTHER_STOP_HINT = 'Select another stop on the same route' + +function ReorderStopsWindow:handleStopSelection(index, item) + -- Skip routes + if item.type == 'route' then return end + + -- Select stop if none selected + if not self.selected_stop then + self:toggleStopSelection(item) + return + end + + -- Swap stops + self:swapStops(index, item) + + -- Reset stop properties + self:resetStopProperties(item) + + self.selected_stop = nil + self:updateList() +end + +function ReorderStopsWindow:toggleStopSelection(item) + if not self.selected_stop then + self.selected_stop = item + else + self.selected_stop = nil + end + + self:updateList() +end + +function ReorderStopsWindow:swapStops(index, item) + local hauling = df.global.plotinfo.hauling + local routes = hauling.routes + local view_stops = hauling.view_stops + local item_route = routes[item.route_index] + local item_stop_index = item.stop_index + local same_route = self.selected_stop.route_index == item.route_index + + if same_route then + swap_elements(item_route.stops, item_stop_index, nil, self.selected_stop.stop_index) + else + swap_elements( + routes[self.selected_stop.route_index].stops, + self.selected_stop.stop_index, + item_route.stops, + item_stop_index + ) + end + + swap_elements(view_stops, self.selected_stop.list_position, nil, index - 1) +end + +function ReorderStopsWindow:resetStopProperties(item) + local hauling = df.global.plotinfo.hauling + local routes = hauling.routes + local item_route = routes[item.route_index] + local same_route = self.selected_stop.route_index == item.route_index + + for i, stop in ipairs(item_route.stops) do + stop.id = i + 1 + reset_guide_paths(stop.conditions) + end + + if not same_route and self.selected_stop then + for i, stop in ipairs(routes[self.selected_stop.route_index].stops) do + stop.id = i + 1 + reset_guide_paths(stop.conditions) + end + end +end + function ReorderStopsWindow:init() self.selected_stop = nil self:addviews{ @@ -290,40 +370,7 @@ function ReorderStopsWindow:init() end end, on_submit=function(index, item) - if self.selected_stop then - local hauling = df.global.plotinfo.hauling - local routes = hauling.routes - local view_stops = hauling.view_stops - local route = routes[item.route_index] - - -- rearrange stops - if item.type == 'stop' then - local stop_index = item.stop_index - - -- don't allow moving stops to a different route for now. TODO: investigate this - if self.selected_stop.route_index ~= item.route_index then - return - end - - swapElements(route.stops, stop_index, self.selected_stop.stop_index) - swapElements(view_stops, self.selected_stop.list_position, index - 1) - - -- loop over each stop in the route, make the ids sequental and reset guide paths - -- TODO: figure out if changing the ids here breaks anything else - for i, stop in ipairs(route.stops) do - stop.id = i + 1 - reset_guide_paths(stop.conditions) - end - - self.selected_stop = nil - end - else - if item.stop_index then - self.selected_stop = item - end - end - - self:updateList() + self:handleStopSelection(index, item) end, }, } From 068b6a3a465db29f8e1e392b5e17fee32f7d2635 Mon Sep 17 00:00:00 2001 From: Robob27 Date: Wed, 8 Nov 2023 18:20:44 -0500 Subject: [PATCH 4/5] Fix hint, slight layout adjustments --- trackstop.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trackstop.lua b/trackstop.lua index 0852cc3d6d..874820c4d8 100644 --- a/trackstop.lua +++ b/trackstop.lua @@ -270,13 +270,13 @@ end ReorderStopsWindow = defclass(ReorderStopsWindow, widgets.Window) ReorderStopsWindow.ATTRS { - frame={t=4,l=60,w=45, h=28}, + frame={t=4,l=60,w=49, h=26}, frame_title='Reorder Stops', resizable=true, } local SELECT_STOP_HINT = 'Select a stop to move' -local SELECT_ANOTHER_STOP_HINT = 'Select another stop on the same route' +local SELECT_ANOTHER_STOP_HINT = 'Select another stop to swap or same to cancel' function ReorderStopsWindow:handleStopSelection(index, item) @@ -360,7 +360,7 @@ function ReorderStopsWindow:init() }, widgets.List{ view_id='routes', - frame={t=1,l=1}, + frame={t=2,l=1}, choices={}, on_select=function(_, item) if not item then return end From 44595f5a64f1d43a5895719f8b6b207e494c2c95 Mon Sep 17 00:00:00 2001 From: Robob27 Date: Fri, 10 Nov 2023 19:38:21 -0500 Subject: [PATCH 5/5] Feedback, improve variable names --- trackstop.lua | 70 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/trackstop.lua b/trackstop.lua index 874820c4d8..25f538d3f0 100644 --- a/trackstop.lua +++ b/trackstop.lua @@ -284,7 +284,7 @@ function ReorderStopsWindow:handleStopSelection(index, item) if item.type == 'route' then return end -- Select stop if none selected - if not self.selected_stop then + if not self.first_selected_stop then self:toggleStopSelection(item) return end @@ -295,55 +295,71 @@ function ReorderStopsWindow:handleStopSelection(index, item) -- Reset stop properties self:resetStopProperties(item) - self.selected_stop = nil + self.first_selected_stop = nil self:updateList() end function ReorderStopsWindow:toggleStopSelection(item) - if not self.selected_stop then - self.selected_stop = item + if not self.first_selected_stop then + self.first_selected_stop = item else - self.selected_stop = nil + self.first_selected_stop = nil end self:updateList() end -function ReorderStopsWindow:swapStops(index, item) +function ReorderStopsWindow:swapStops(index, second_selected_stop) local hauling = df.global.plotinfo.hauling local routes = hauling.routes local view_stops = hauling.view_stops - local item_route = routes[item.route_index] - local item_stop_index = item.stop_index - local same_route = self.selected_stop.route_index == item.route_index + local second_selected_stop_route = routes[second_selected_stop.route_index] + local second_selected_stop_index = second_selected_stop.stop_index + local same_route = self.first_selected_stop.route_index == second_selected_stop.route_index if same_route then - swap_elements(item_route.stops, item_stop_index, nil, self.selected_stop.stop_index) + swap_elements(second_selected_stop_route.stops, second_selected_stop_index, nil, self.first_selected_stop.stop_index) + + -- find out what index the vehicle is currently at for this route, if there is one + local vehicle_index = nil + local hauling_route = df.hauling_route.get_vector()[second_selected_stop.route_index] + + -- this vector will have 0 elements if there is no vehicle or 1 element if there is a vehicle + -- the element will be the index of the vehicle stop + for _, v in ipairs(hauling_route.vehicle_stops) do + vehicle_index = v + end + + if vehicle_index == self.first_selected_stop.stop_index then + hauling_route.vehicle_stops[0] = second_selected_stop_index + elseif vehicle_index == second_selected_stop_index then + hauling_route.vehicle_stops[0] = self.first_selected_stop.stop_index + end else swap_elements( - routes[self.selected_stop.route_index].stops, - self.selected_stop.stop_index, - item_route.stops, - item_stop_index + routes[self.first_selected_stop.route_index].stops, + self.first_selected_stop.stop_index, + second_selected_stop_route.stops, + second_selected_stop_index ) end - swap_elements(view_stops, self.selected_stop.list_position, nil, index - 1) + swap_elements(view_stops, self.first_selected_stop.list_position, nil, index - 1) end function ReorderStopsWindow:resetStopProperties(item) local hauling = df.global.plotinfo.hauling local routes = hauling.routes local item_route = routes[item.route_index] - local same_route = self.selected_stop.route_index == item.route_index + local same_route = self.first_selected_stop.route_index == item.route_index for i, stop in ipairs(item_route.stops) do stop.id = i + 1 reset_guide_paths(stop.conditions) end - if not same_route and self.selected_stop then - for i, stop in ipairs(routes[self.selected_stop.route_index].stops) do + if not same_route and self.first_selected_stop then + for i, stop in ipairs(routes[self.first_selected_stop.route_index].stops) do stop.id = i + 1 reset_guide_paths(stop.conditions) end @@ -351,7 +367,7 @@ function ReorderStopsWindow:resetStopProperties(item) end function ReorderStopsWindow:init() - self.selected_stop = nil + self.first_selected_stop = nil self:addviews{ widgets.Label{ frame={t=0,l=0}, @@ -383,7 +399,7 @@ function ReorderStopsWindow:updateList() local choices = {} local list_position = 0 - if self.selected_stop then + if self.first_selected_stop then self.subviews.hint:setText(SELECT_ANOTHER_STOP_HINT) else self.subviews.hint:setText(SELECT_STOP_HINT) @@ -407,7 +423,7 @@ function ReorderStopsWindow:updateList() stop_name = 'Stop ' .. stop.id end - if self.selected_stop and self.selected_stop.list_position == list_position then + if self.first_selected_stop and self.first_selected_stop.list_position == list_position then stop_name = '=> ' .. stop_name end @@ -421,6 +437,18 @@ function ReorderStopsWindow:updateList() self.subviews.routes:setChoices(choices) end +function ReorderStopsWindow:onInput(keys) + if keys.LEAVESCREEN or keys._MOUSE_R then + if self.first_selected_stop then + self.first_selected_stop = nil + self:updateList() + return true + end + end + + return ReorderStopsWindow.super.onInput(self, keys) +end + ReorderStopsModal = defclass(ReorderStopsModal, gui.ZScreenModal) ReorderStopsModal.ATTRS = {