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..25f538d3f0 100644 --- a/trackstop.lua +++ b/trackstop.lua @@ -52,6 +52,31 @@ local DIRECTION_MAP = { local DIRECTION_MAP_REVERSE = utils.invert(DIRECTION_MAP) +--[[ + - 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) + for _, 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 +145,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 +230,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 +268,225 @@ function RollerOverlay:init() } end +ReorderStopsWindow = defclass(ReorderStopsWindow, widgets.Window) +ReorderStopsWindow.ATTRS { + 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 to swap or same to cancel' + + +function ReorderStopsWindow:handleStopSelection(index, item) + -- Skip routes + if item.type == 'route' then return end + + -- Select stop if none selected + if not self.first_selected_stop then + self:toggleStopSelection(item) + return + end + + -- Swap stops + self:swapStops(index, item) + + -- Reset stop properties + self:resetStopProperties(item) + + self.first_selected_stop = nil + self:updateList() +end + +function ReorderStopsWindow:toggleStopSelection(item) + if not self.first_selected_stop then + self.first_selected_stop = item + else + self.first_selected_stop = nil + end + + self:updateList() +end + +function ReorderStopsWindow:swapStops(index, second_selected_stop) + local hauling = df.global.plotinfo.hauling + local routes = hauling.routes + local view_stops = hauling.view_stops + 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(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.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.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.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.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 + end +end + +function ReorderStopsWindow:init() + self.first_selected_stop = nil + self:addviews{ + widgets.Label{ + frame={t=0,l=0}, + view_id='hint', + text=SELECT_STOP_HINT, + }, + widgets.List{ + view_id='routes', + frame={t=2,l=1}, + choices={}, + 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 + dfhack.gui.revealInDwarfmodeMap(item_pos, true, true) + end + end, + on_submit=function(index, item) + self:handleStopSelection(index, item) + end, + }, + } + + self:updateList() +end + +function ReorderStopsWindow:updateList() + local routes = df.global.plotinfo.hauling.routes + local choices = {} + local list_position = 0 + + if self.first_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 self.first_selected_stop and self.first_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 + +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 = { + focus_path = 'ReorderStops', +} + +function ReorderStopsModal:init() + self:addviews{ReorderStopsWindow{}} +end + +function ReorderStopsModal:onDismiss() + 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.TextButton{ + frame={t=0, l=0}, + label='DFHack reorder stops', + key='CUSTOM_CTRL_E', + on_activate=function() ReorderStopsModal{}:show() end, + }, + } +end + OVERLAY_WIDGETS = { trackstop=TrackStopOverlay, rollers=RollerOverlay, + reorderstops=ReorderStopsOverlay, }