From 6bef167f8342176d58aafcda98c726759bb9a8c2 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 21 Apr 2014 09:24:05 +0400
Subject: [PATCH] Add a couple of useful scripts and fix two missing NULL
checks.
- A script to unstick jobs trying to build walls from the same tile.
- A devel script for viewing the path a unit is currently following.
---
Lua API.html | 3 +
Lua API.rst | 4 +
NEWS | 1 +
Readme.html | 5 +
Readme.rst | 7 ++
library/lua/utils.lua | 15 +++
library/modules/Units.cpp | 4 +
scripts/devel/unit-path.lua | 210 +++++++++++++++++++++++++++++++++
scripts/fix/build-location.lua | 37 ++++++
9 files changed, 286 insertions(+)
create mode 100644 scripts/devel/unit-path.lua
create mode 100644 scripts/fix/build-location.lua
diff --git a/Lua API.html b/Lua API.html
index eec1a0ea2..bd17cc644 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -2157,6 +2157,9 @@ for i = 1,#order do output[i] = data[order[i]] end
way enables applying the same permutation to multiple arrays.
This function is used by the sort plugin.
+for link,item in utils.listpairs(list)
+Iterates a df-list structure, for example df.global.world.job_list.
+
utils.assign(tgt, src)
Does a recursive assignment of src into tgt.
Uses df.assign if tgt is a native object ref; otherwise
diff --git a/Lua API.rst b/Lua API.rst
index 37ff003ed..072d9265f 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -2045,6 +2045,10 @@ utils
way enables applying the same permutation to multiple arrays.
This function is used by the sort plugin.
+* ``for link,item in utils.listpairs(list)``
+
+ Iterates a df-list structure, for example ``df.global.world.job_list``.
+
* ``utils.assign(tgt, src)``
Does a recursive assignment of src into tgt.
diff --git a/NEWS b/NEWS
index 6e66a6017..165938e2b 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ DFHack future
New scripts:
- gui/mod-manager: allows installing/uninstalling mods into df from df/mods directory.
- gui/clone-uniform: duplicates the currently selected uniform in the military screen.
+ - fix/build-location: partial work-around for bug 5991 (trying to build wall while standing on it)
New commands:
- move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands
diff --git a/Readme.html b/Readme.html
index 98139cb50..b93dc3bfe 100644
--- a/Readme.html
+++ b/Readme.html
@@ -2930,6 +2930,11 @@ in memory and patching up some invalid reference fields. Needs to be run
every time a save game is loaded; putting fix/cloth-stockpile enable
in dfhack.init makes it run automatically.
+fix/build-location
+Fixes construction jobs that are stuck trying to build a wall while standing
+on the same exact tile (bug 5991), designates the tile restricted traffic to
+hopefully avoid jamming it again, and unsuspends them.
+
diff --git a/Readme.rst b/Readme.rst
index 84c4f7c9d..9b8fd07cb 100644
--- a/Readme.rst
+++ b/Readme.rst
@@ -2049,6 +2049,13 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
every time a save game is loaded; putting ``fix/cloth-stockpile enable``
in ``dfhack.init`` makes it run automatically.
+* fix/build-location
+
+ Fixes construction jobs that are stuck trying to build a wall while standing
+ on the same exact tile (bug 5991), designates the tile restricted traffic to
+ hopefully avoid jamming it again, and unsuspends them.
+
+
gui/*
=====
diff --git a/library/lua/utils.lua b/library/lua/utils.lua
index 4a7a8d510..caa682c75 100644
--- a/library/lua/utils.lua
+++ b/library/lua/utils.lua
@@ -147,6 +147,21 @@ function make_sort_order(data,ordering)
return index
end
+--[[
+ Iterate a 'list' structure, e.g. df.global.world.job_list
+--]]
+local function next_df_list(s,link)
+ link = link.next
+ if link then
+ return link, link.item
+ end
+end
+
+function listpairs(list)
+ return next_df_list, nil, list
+end
+
+
--[[
Recursively assign data into a table.
--]]
diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp
index 13e23fb39..20c536cd1 100644
--- a/library/modules/Units.cpp
+++ b/library/modules/Units.cpp
@@ -1368,6 +1368,8 @@ bool DFHack::Units::getNoblePositions(std::vector *pvec, df::unit
std::string DFHack::Units::getProfessionName(df::unit *unit, bool ignore_noble, bool plural)
{
+ CHECK_NULL_POINTER(unit);
+
std::string prof = unit->custom_profession;
if (!prof.empty())
return prof;
@@ -1514,6 +1516,8 @@ std::string DFHack::Units::getCasteProfessionName(int race, int casteid, df::pro
int8_t DFHack::Units::getProfessionColor(df::unit *unit, bool ignore_noble)
{
+ CHECK_NULL_POINTER(unit);
+
std::vector np;
if (!ignore_noble && getNoblePositions(&np, unit))
diff --git a/scripts/devel/unit-path.lua b/scripts/devel/unit-path.lua
new file mode 100644
index 000000000..dc1066bb2
--- /dev/null
+++ b/scripts/devel/unit-path.lua
@@ -0,0 +1,210 @@
+-- Show the internal path a unit is currently following.
+
+local utils = require 'utils'
+local gui = require 'gui'
+local guidm = require 'gui.dwarfmode'
+local dlg = require 'gui.dialogs'
+
+local tile_attrs = df.tiletype.attrs
+
+UnitPathUI = defclass(UnitPathUI, guidm.MenuOverlay)
+
+UnitPathUI.focus_path = 'unit-path'
+
+UnitPathUI.ATTRS {
+ unit = DEFAULT_NIL,
+}
+
+function UnitPathUI:init()
+ self.saved_mode = df.global.ui.main.mode
+
+end
+
+function UnitPathUI:onShow()
+ -- with cursor, but without those ugly lines from native hauling mode
+ df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles
+end
+
+function UnitPathUI:onDestroy()
+ self:moveCursorTo(copyall(self.unit.pos))
+ 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 getTileWalkable(cursor)
+ local block = dfhack.maps.getTileBlock(cursor)
+ if block then
+ return block.walkable[cursor.x%16][cursor.y%16]
+ else
+ return 0
+ end
+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
+
+function UnitPathUI:renderPath(dc,vp,cursor)
+ local gpath = self.unit.path.path
+ local startp = self.unit.pos
+ local endp = self.unit.path.dest
+ local visible = gui.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.z == pt.z+1 then
+ char = 30
+ elseif npt.z == pt.z-1 then
+ char = 31
+ elseif 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 getTileWalkable(pt) == 0 then color = COLOR_LIGHTRED end
+ paintMapTile(dc, vp, cursor, pt, char, color)
+ end
+ end
+ end
+
+ if gui.blink_visible(120) then
+ paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN)
+ end
+
+ return ok
+end
+
+function UnitPathUI:onRenderBody(dc)
+ dc:clear():seek(1,1):pen(COLOR_WHITE):string("Unit Path")
+
+ local prof = dfhack.units.getProfessionName(self.unit)
+ local name = dfhack.units.getVisibleName(self.unit)
+
+ dc:seek(2,3):pen(COLOR_BLUE):string(prof)
+ if name and name.has_name then
+ dc:seek(2,4):pen(COLOR_BLUE):string(dfhack.TranslateName(name))
+ end
+
+ local cursor = guidm.getCursorPos()
+ local has_path = #self.unit.path.path.x>0
+
+ local vp = self:getViewport()
+ local mdc = gui.Painter.new(self.df_layout.map)
+
+ if not has_path then
+ if gui.blink_visible(120) then
+ paintMapTile(mdc, vp, cursor, self.unit.pos, 15, COLOR_LIGHTRED, COLOR_RED)
+ end
+
+ dc:seek(1,6):pen(COLOR_RED):string('Not following path')
+ else
+ self:renderPath(mdc,vp,cursor)
+
+ dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal])
+ end
+
+ dc:newline():pen(COLOR_GREY)
+ dc:newline(2):string('Speed: '..dfhack.units.computeMovementSpeed(self.unit))
+ dc:newline(2):string('Slowdown: '..dfhack.units.computeSlowdownFactor(self.unit))
+
+ dc:newline():newline(1)
+
+ local has_station = self.unit.idle_area_type >= 0
+
+ if has_station then
+ if gui.blink_visible(250) then
+ paintMapTile(mdc, vp, cursor, self.unit.idle_area, 21, COLOR_LIGHTCYAN)
+ end
+
+ dc:pen(COLOR_GREEN):string(df.unit_station_type[self.unit.idle_area_type])
+ dc:newline():newline(2):pen(COLOR_GREY):string('Threshold: '..self.unit.idle_area_threshold)
+ else
+ dc:pen(COLOR_RED):string('No station'):newline():newline(2)
+ end
+
+ dc:newline():newline(1):string('At cursor:')
+ dc:newline():newline(2)
+
+ local tile = getTileType(cursor)
+ dc:string(df.tiletype[tile],COLOR_CYAN)
+
+ dc:newline():newline(1):pen(COLOR_WHITE)
+ dc:key('CUSTOM_Z'):string(": Zoom unit, ")
+ dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,has_path)
+ dc:newline(1)
+ dc:key('CUSTOM_N'):string(": Zoom station",COLOR_GREY,nil,has_station)
+ dc:newline():newline(1)
+ dc:key('LEAVESCREEN'):string(": Back")
+end
+
+function UnitPathUI:onInput(keys)
+ if keys.CUSTOM_Z then
+ self:moveCursorTo(copyall(self.unit.pos))
+ elseif keys.CUSTOM_G then
+ if #self.unit.path.path.x > 0 then
+ self:moveCursorTo(copyall(self.unit.path.dest))
+ end
+ elseif keys.CUSTOM_N then
+ if self.unit.idle_area_type > 0 then
+ self:moveCursorTo(copyall(self.unit.idle_area))
+ end
+ elseif keys.LEAVESCREEN then
+ self:dismiss()
+ elseif self:propagateMoveKeys(keys) then
+ return
+ end
+end
+
+local unit = dfhack.gui.getSelectedUnit(true)
+
+if not unit or not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/ViewUnits/Some/') then
+ qerror("This script requires the main dwarfmode view in 'v' mode with a unit selected")
+end
+
+UnitPathUI{ unit = unit }:show()
diff --git a/scripts/fix/build-location.lua b/scripts/fix/build-location.lua
new file mode 100644
index 000000000..0eea2c780
--- /dev/null
+++ b/scripts/fix/build-location.lua
@@ -0,0 +1,37 @@
+-- Lets constructions reconsider the build location.
+
+-- Partial work-around for http://www.bay12games.com/dwarves/mantisbt/view.php?id=5991
+
+local utils = require('utils')
+
+local count = 0
+
+for link,job in utils.listpairs(df.global.world.job_list) do
+ local job = link.item
+ local place = dfhack.job.getHolder(job)
+
+ if job.job_type == df.job_type.ConstructBuilding
+ and place and place:isImpassableAtCreation()
+ and job.item_category[0]
+ then
+ local cpos = utils.getBuildingCenter(place)
+
+ if same_xyz(cpos, job.pos) then
+ -- Reset the flag
+ job.item_category[0] = false
+ job.flags.suspend = false
+
+ -- Mark the tile restricted traffic
+ local dsgn,occ = dfhack.maps.getTileFlags(cpos)
+ dsgn.traffic = df.tile_traffic.Restricted
+
+ count = count + 1
+ end
+ end
+end
+
+print('Found and unstuck '..count..' construct building jobs.')
+
+if count > 0 then
+ df.global.process_jobs = true
+end