diff --git a/dfhack.init-example b/dfhack.init-example index 8505f5acc..3ecbf4342 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -72,6 +72,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine # military weapon auto-select keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons +# minecart Guide path +keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path + ############################ # UI and game logic tweaks # ############################ diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua new file mode 100644 index 000000000..f3e27d917 --- /dev/null +++ b/scripts/gui/guide-path.lua @@ -0,0 +1,201 @@ +-- Show and manipulate the path used by Guide Cart orders. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local tile_attrs = df.tiletype.attrs + +function same_xyz(a,b) + return a and b and a.x == b.x and a.y == b.y and a.z == b.z +end + +GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) + +GuidePathUI.focus_path = 'guide-path' + +GuidePathUI.ATTRS { + route = DEFAULT_NIL, + stop = DEFAULT_NIL, + order = DEFAULT_NIL, +} + +function GuidePathUI:init() + self.saved_mode = df.global.ui.main.mode + + for i=0,#self.route.stops-1 do + if self.route.stops[i] == self.stop then + self.stop_idx = i + break + end + end + + if not self.stop_idx then + error('Could not find stop index') + end + + self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops] +end + +function GuidePathUI:onShow() + -- with cursor, but without those ugly lines from native hauling mode + df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles +end + +function GuidePathUI:onDestroy() + df.global.ui.main.mode = self.saved_mode +end + +local function getTileType(cursor) + local block = dfhack.maps.getTileBlock(cursor) + if block then + return block.tiletype[cursor.x%16][cursor.y%16] + else + return 0 + end +end + +local function isTrackTile(tile) + return tile_attrs[tile].special == df.tiletype_special.TRACK +end + +local function paintMapTile(dc, vp, cursor, pos, ...) + if not same_xyz(cursor, pos) then + local stile = vp:tileToScreen(pos) + if stile.z == 0 then + dc:seek(stile.x,stile.y):char(...) + end + end +end + +local function get_path_point(gpath,i) + return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) +end + +local function blink_visible(delay) + return math.floor(dfhack.getTickCount()/delay) % 2 == 0 +end + +function GuidePathUI:renderPath(cursor) + local gpath = self.order.guide_path + local startp = self.stop.pos + local endp = self.next_stop.pos + local vp = self:getViewport() + local dc = gui.Painter.new(self.df_layout.map) + local visible = blink_visible(500) + + if visible then + paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) + end + + local ok = nil + local pcnt = #gpath.x + if pcnt > 0 then + ok = true + + for i = 0,pcnt-1 do + local pt = get_path_point(gpath, i) + if i == 0 and not same_xyz(startp,pt) then + ok = false + end + if i == pcnt-1 and not same_xyz(endp,pt) then + ok = false + end + local tile = getTileType(pt) + if not isTrackTile(tile) then + ok = false + end + if visible then + local char = '+' + if i < pcnt-1 then + local npt = get_path_point(gpath, i+1) + if npt.x == pt.x+1 then + char = 26 + elseif npt.x == pt.x-1 then + char = 27 + elseif npt.y == pt.y+1 then + char = 25 + elseif npt.y == pt.y-1 then + char = 24 + end + end + local color = COLOR_LIGHTGREEN + if not ok then color = COLOR_LIGHTRED end + paintMapTile(dc, vp, cursor, pt, char, color) + end + end + end + + if blink_visible(120) then + paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) + end + + return ok +end + +function GuidePathUI:onRenderBody(dc) + dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path") + + dc:seek(2,3) + + local cursor = guidm.getCursorPos() + local path_ok = self:renderPath(cursor) + + if path_ok == nil then + dc:string('No saved path', COLOR_DARKGREY) + elseif path_ok then + dc:string('Valid path', COLOR_GREEN) + else + dc:string('Invalid path', COLOR_RED) + end + + dc:newline():newline(1) + dc:key('CUSTOM_Z'):string(": Reset path, ",COLOR_GREY,nil,path_ok~=nil) + dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) + dc:newline(1) + dc:key('CUSTOM_C'):string(": Zoom cur, ") + dc:key('CUSTOM_N'):string(": Zoom next") + + dc:newline():newline(1):string('At cursor:') + dc:newline():newline(2) + + local tile = getTileType(cursor) + if isTrackTile(tile) then + dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN) + else + dc:string('No track', COLOR_DARKGREY) + end + + dc:newline():newline(1) + dc:key('LEAVESCREEN'):string(": Back") +end + +function GuidePathUI:onInput(keys) + if keys.CUSTOM_C then + self:moveCursorTo(copyall(self.stop.pos)) + elseif keys.CUSTOM_N then + self:moveCursorTo(copyall(self.next_stop.pos)) + elseif keys.CUSTOM_Z then + local gpath = self.order.guide_path + gpath.x:resize(0) + gpath.y:resize(0) + gpath.z:resize(0) + elseif keys.LEAVESCREEN then + self:dismiss() + elseif self:propagateMoveKeys(keys) then + return + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then + qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order") +end + +local hauling = df.global.ui.hauling +local route = hauling.view_routes[hauling.cursor_top] +local stop = hauling.view_stops[hauling.cursor_top] +local order = hauling.stop_conditions[hauling.cursor_stop] + +local list = GuidePathUI{ route = route, stop = stop, order = order } +list:show()