summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorjj2012-09-14 14:30:25 +0200
committerjj2012-09-14 14:30:25 +0200
commit5474ccacb62bf848e3d823649497c27f1f35a31e (patch)
tree91f480d375dc2be4c69f8918ce5193b4f475ad3e /scripts
parentb05905a0bacb211ca86af70a3d1c8683328562da (diff)
parent68bfc63b7d0804ac5229623480e47f297783f502 (diff)
downloaddfhack-5474ccacb62bf848e3d823649497c27f1f35a31e.tar.gz
dfhack-5474ccacb62bf848e3d823649497c27f1f35a31e.tar.bz2
dfhack-5474ccacb62bf848e3d823649497c27f1f35a31e.tar.xz
Merge branch 'master' of git://github.com/angavrilov/dfhack
Diffstat (limited to 'scripts')
-rw-r--r--scripts/devel/lsmem.lua14
-rw-r--r--scripts/devel/pop-screen.lua3
-rw-r--r--scripts/fix/stable-temp.lua33
-rw-r--r--scripts/gui/liquids.lua41
-rw-r--r--scripts/gui/mechanisms.lua2
-rw-r--r--scripts/gui/power-meter.lua116
-rw-r--r--scripts/gui/rename.lua63
-rw-r--r--scripts/gui/siege-engine.lua490
-rw-r--r--scripts/setfps.lua10
9 files changed, 762 insertions, 10 deletions
diff --git a/scripts/devel/lsmem.lua b/scripts/devel/lsmem.lua
new file mode 100644
index 00000000..75586324
--- /dev/null
+++ b/scripts/devel/lsmem.lua
@@ -0,0 +1,14 @@
+-- Prints memory ranges of the process.
+
+for _,v in ipairs(dfhack.internal.getMemRanges()) do
+ local access = { '-', '-', '-', 'p' }
+ if v.read then access[1] = 'r' end
+ if v.write then access[2] = 'w' end
+ if v.execute then access[3] = 'x' end
+ if not v.valid then
+ access[4] = '?'
+ elseif v.shared then
+ access[4] = 's'
+ end
+ print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name))
+end
diff --git a/scripts/devel/pop-screen.lua b/scripts/devel/pop-screen.lua
new file mode 100644
index 00000000..f1ee072c
--- /dev/null
+++ b/scripts/devel/pop-screen.lua
@@ -0,0 +1,3 @@
+-- For killing bugged out gui script screens.
+
+dfhack.screen.dismiss(dfhack.gui.getCurViewscreen())
diff --git a/scripts/fix/stable-temp.lua b/scripts/fix/stable-temp.lua
index d06d0fcc..27a88ef7 100644
--- a/scripts/fix/stable-temp.lua
+++ b/scripts/fix/stable-temp.lua
@@ -1,5 +1,9 @@
-- Reset item temperature to the value of their tile.
+local args = {...}
+
+local apply = (args[1] == 'apply')
+
local count = 0
local types = {}
@@ -9,13 +13,16 @@ local function update_temp(item,btemp)
local tid = item:getType()
types[tid] = (types[tid] or 0) + 1
end
- item.temperature = btemp
- item.temperature_fraction = 0
- if item.contaminants then
- for _,c in ipairs(item.contaminants) do
- c.temperature = btemp
- c.temperature_fraction = 0
+ if apply then
+ item.temperature = btemp
+ item.temperature_fraction = 0
+
+ if item.contaminants then
+ for _,c in ipairs(item.contaminants) do
+ c.temperature = btemp
+ c.temperature_fraction = 0
+ end
end
end
@@ -23,7 +30,9 @@ local function update_temp(item,btemp)
update_temp(sub,btemp)
end
- item:checkTemperatureDamage()
+ if apply then
+ item:checkTemperatureDamage()
+ end
end
local last_frame = df.global.world.frame_counter-1
@@ -39,7 +48,11 @@ for _,item in ipairs(df.global.world.items.all) do
end
end
-print('Items updated: '..count)
+if apply then
+ print('Items updated: '..count)
+else
+ print('Items not in equilibrium: '..count)
+end
local tlist = {}
for k,_ in pairs(types) do tlist[#tlist+1] = k end
@@ -47,3 +60,7 @@ table.sort(tlist, function(a,b) return types[a] > types[b] end)
for _,k in ipairs(tlist) do
print(' '..df.item_type[k]..':', types[k])
end
+
+if not apply then
+ print("Use 'fix/stable-temp apply' to force-change temperature.")
+end
diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua
index 869cac90..89f08b7c 100644
--- a/scripts/gui/liquids.lua
+++ b/scripts/gui/liquids.lua
@@ -3,6 +3,7 @@
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
+local dlg = require 'gui.dialogs'
local liquids = require('plugins.liquids')
@@ -199,6 +200,42 @@ function LiquidsUI:onRenderBody(dc)
dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint")
end
+function ensure_blocks(cursor, size, cb)
+ local cx,cy,cz = pos2xyz(cursor)
+ local all = true
+ for x=1,size.x or 1,16 do
+ for y=1,size.y or 1,16 do
+ for z=1,size.z do
+ if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then
+ all = false
+ end
+ end
+ end
+ end
+ if all then
+ cb()
+ return
+ end
+ dlg.showYesNoPrompt(
+ 'Instantiate Blocks',
+ 'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.',
+ COLOR_YELLOW,
+ function()
+ for x=1,size.x or 1,16 do
+ for y=1,size.y or 1,16 do
+ for z=1,size.z do
+ dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1)
+ end
+ end
+ end
+ cb()
+ end,
+ function()
+ cb()
+ end
+ )
+end
+
function LiquidsUI:onInput(keys)
local paint = self.paint:get()
local liquid = paint.liquid
@@ -239,13 +276,15 @@ function LiquidsUI:onInput(keys)
else
guidm.clearSelection()
end
- liquids.paint(
+ local cb = curry(
+ liquids.paint,
cursor,
self.brush:get().tag, self.paint:get().tag,
self.amount, size,
self.set:get().tag, self.flow:get().tag,
self.permaflow:get().tag
)
+ ensure_blocks(cursor, size, cb)
elseif self:propagateMoveKeys(keys) then
return
elseif keys.D_LOOK_ARENA_WATER then
diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua
index 6b4b4042..c14bfcbe 100644
--- a/scripts/gui/mechanisms.lua
+++ b/scripts/gui/mechanisms.lua
@@ -122,7 +122,7 @@ function MechanismList:onInput(keys)
end
end
-if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then
+if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then
qerror("This script requires the main dwarfmode view in 'q' mode")
end
diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua
new file mode 100644
index 00000000..8baf43e7
--- /dev/null
+++ b/scripts/gui/power-meter.lua
@@ -0,0 +1,116 @@
+-- Interface front-end for power-meter plugin.
+
+local utils = require 'utils'
+local gui = require 'gui'
+local guidm = require 'gui.dwarfmode'
+local dlg = require 'gui.dialogs'
+
+local plugin = require('plugins.power-meter')
+local bselector = df.global.ui_build_selector
+
+PowerMeter = defclass(PowerMeter, guidm.MenuOverlay)
+
+PowerMeter.focus_path = 'power-meter'
+
+function PowerMeter:init()
+ self:init_fields{
+ min_power = 0, max_power = -1, invert = false,
+ }
+ guidm.MenuOverlay.init(self)
+ return self
+end
+
+function PowerMeter:onShow()
+ guidm.MenuOverlay.onShow(self)
+
+ -- Send an event to update the errors
+ bselector.plate_info.flags.whole = 0
+ self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER')
+end
+
+function PowerMeter:onRenderBody(dc)
+ dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN)
+ dc:seek(1,1):pen(COLOR_WHITE)
+ dc:string("Power Meter"):newline():newline(1)
+ dc:string("Placement"):newline():newline(1)
+
+ dc:string("Excess power range:")
+
+ dc:newline(3):string("as", COLOR_LIGHTGREEN)
+ dc:string(": Min ")
+ if self.min_power <= 0 then
+ dc:string("(any)")
+ else
+ dc:string(''..self.min_power)
+ end
+
+ dc:newline(3):string("zx", COLOR_LIGHTGREEN)
+ dc:string(": Max ")
+ if self.max_power < 0 then
+ dc:string("(any)")
+ else
+ dc:string(''..self.max_power)
+ end
+ dc:newline():newline(1)
+
+ dc:string("i",COLOR_LIGHTGREEN):string(": ")
+ if self.invert then
+ dc:string("Inverted")
+ else
+ dc:string("Not inverted")
+ end
+end
+
+function PowerMeter:onInput(keys)
+ if keys.CUSTOM_I then
+ self.invert = not self.invert
+ elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then
+ self.min_power = self.min_power + 10
+ elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then
+ self.min_power = math.max(0, self.min_power - 10)
+ elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then
+ if self.max_power < 0 then
+ self.max_power = 0
+ else
+ self.max_power = self.max_power + 10
+ end
+ elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then
+ self.max_power = math.max(-1, self.max_power - 10)
+ elseif keys.LEAVESCREEN then
+ self:dismiss()
+ self:sendInputToParent('LEAVESCREEN')
+ elseif keys.SELECT then
+ if #bselector.errors == 0 then
+ if not plugin.makePowerMeter(
+ bselector.plate_info,
+ self.min_power, self.max_power, self.invert
+ )
+ then
+ dlg.showMessage(
+ 'Power Meter',
+ 'Could not initialize.', COLOR_LIGHTRED
+ )
+
+ self:dismiss()
+ self:sendInputToParent('LEAVESCREEN')
+ return
+ end
+
+ self:sendInputToParent('SELECT')
+ if bselector.stage ~= 1 then
+ self:dismiss()
+ end
+ end
+ elseif self:propagateMoveKeys(keys) then
+ return
+ end
+end
+
+if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap'
+or bselector.building_subtype ~= df.trap_type.PressurePlate
+then
+ qerror("This script requires the main dwarfmode view in build pressure plate mode")
+end
+
+local list = mkinstance(PowerMeter):init()
+list:show()
diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua
new file mode 100644
index 00000000..a457a0bf
--- /dev/null
+++ b/scripts/gui/rename.lua
@@ -0,0 +1,63 @@
+-- Rename various objects via gui.
+
+local gui = require 'gui'
+local dlg = require 'gui.dialogs'
+local plugin = require 'plugins.rename'
+
+local mode = ...
+local focus = dfhack.gui.getCurFocus()
+
+local function verify_mode(expected)
+ if mode ~= nil and mode ~= expected then
+ qerror('Invalid UI state for mode '..mode)
+ end
+end
+
+if string.match(focus, '^dwarfmode/QueryBuilding/Some') then
+ verify_mode('building')
+
+ local building = df.global.world.selected_building
+ if plugin.canRenameBuilding(building) then
+ dlg.showInputPrompt(
+ 'Rename Building',
+ 'Enter a new name for the building:', COLOR_GREEN,
+ building.name,
+ curry(plugin.renameBuilding, building)
+ )
+ else
+ dlg.showMessage(
+ 'Rename Building',
+ 'Cannot rename this type of building.', COLOR_LIGHTRED
+ )
+ end
+elseif dfhack.gui.getSelectedUnit(true) then
+ local unit = dfhack.gui.getSelectedUnit(true)
+
+ if mode == 'unit-profession' then
+ dlg.showInputPrompt(
+ 'Rename Unit',
+ 'Enter a new profession for the unit:', COLOR_GREEN,
+ unit.custom_profession,
+ function(newval)
+ unit.custom_profession = newval
+ end
+ )
+ else
+ verify_mode('unit')
+
+ local vname = dfhack.units.getVisibleName(unit)
+ local vnick = ''
+ if vname and vname.has_name then
+ vnick = vname.nickname
+ end
+
+ dlg.showInputPrompt(
+ 'Rename Unit',
+ 'Enter a new nickname for the unit:', COLOR_GREEN,
+ vnick,
+ curry(dfhack.units.setNickname, unit)
+ )
+ end
+elseif mode then
+ verify_mode(nil)
+end
diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua
new file mode 100644
index 00000000..47043cbb
--- /dev/null
+++ b/scripts/gui/siege-engine.lua
@@ -0,0 +1,490 @@
+-- Front-end for the siege engine plugin.
+
+local utils = require 'utils'
+local gui = require 'gui'
+local guidm = require 'gui.dwarfmode'
+local dlg = require 'gui.dialogs'
+
+local plugin = require 'plugins.siege-engine'
+local wmap = df.global.world.map
+
+local LEGENDARY = df.skill_rating.Legendary
+
+-- Globals kept between script calls
+last_target_min = last_target_min or nil
+last_target_max = last_target_max or nil
+
+local item_choices = {
+ { caption = 'boulders (default)', item_type = df.item_type.BOULDER },
+ { caption = 'blocks', item_type = df.item_type.BLOCKS },
+ { caption = 'weapons', item_type = df.item_type.WEAPON },
+ { caption = 'trap components', item_type = df.item_type.TRAPCOMP },
+ { caption = 'bins', item_type = df.item_type.BIN },
+ { caption = 'barrels', item_type = df.item_type.BARREL },
+ { caption = 'anything', item_type = -1 },
+}
+
+local item_choice_idx = {}
+for i,v in ipairs(item_choices) do
+ item_choice_idx[v.item_type] = i
+end
+
+SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay)
+
+SiegeEngine.focus_path = 'siege-engine'
+
+function SiegeEngine:init(building)
+ self:init_fields{
+ building = building,
+ center = utils.getBuildingCenter(building),
+ selected_pile = 1,
+ }
+ guidm.MenuOverlay.init(self)
+ self.mode_main = {
+ render = self:callback 'onRenderBody_main',
+ input = self:callback 'onInput_main',
+ }
+ self.mode_aim = {
+ render = self:callback 'onRenderBody_aim',
+ input = self:callback 'onInput_aim',
+ }
+ self.mode_pile = {
+ render = self:callback 'onRenderBody_pile',
+ input = self:callback 'onInput_pile',
+ }
+ return self
+end
+
+function SiegeEngine:onShow()
+ guidm.MenuOverlay.onShow(self)
+
+ self.old_cursor = guidm.getCursorPos()
+ self.old_viewport = self:getViewport()
+
+ self.mode = self.mode_main
+ self:showCursor(false)
+end
+
+function SiegeEngine:onDestroy()
+ if self.save_profile then
+ plugin.saveWorkshopProfile(self.building)
+ end
+ if not self.no_select_building then
+ self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10)
+ end
+end
+
+function SiegeEngine:showCursor(enable)
+ local cursor = guidm.getCursorPos()
+ if cursor and not enable then
+ self.cursor = cursor
+ self.target_select_first = nil
+ guidm.clearCursorPos()
+ elseif not cursor and enable then
+ local view = self:getViewport()
+ cursor = self.cursor
+ if not cursor or not view:isVisible(cursor) then
+ cursor = view:getCenter()
+ end
+ self.cursor = nil
+ guidm.setCursorPos(cursor)
+ end
+end
+
+function SiegeEngine:centerViewOn(pos)
+ local cursor = guidm.getCursorPos()
+ if cursor then
+ guidm.setCursorPos(pos)
+ else
+ self.cursor = pos
+ end
+ self:getViewport():centerOn(pos):set()
+end
+
+function SiegeEngine:zoomToTarget()
+ local target_min, target_max = plugin.getTargetArea(self.building)
+ if target_min then
+ local cx = math.floor((target_min.x + target_max.x)/2)
+ local cy = math.floor((target_min.y + target_max.y)/2)
+ local cz = math.floor((target_min.z + target_max.z)/2)
+ local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz))
+ self:centerViewOn(pos)
+ end
+end
+
+function paint_target_grid(dc, view, origin, p1, p2)
+ local r1, sz, r2 = guidm.getSelectionRange(p1, p2)
+
+ if view.z < r1.z or view.z > r2.z then
+ return
+ end
+
+ local p1 = view:tileToScreen(r1)
+ local p2 = view:tileToScreen(r2)
+ local org = view:tileToScreen(origin)
+ dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true }
+
+ -- Frame
+ dc:fill(p1.x,p1.y,p1.x,p2.y)
+ dc:fill(p1.x,p1.y,p2.x,p1.y)
+ dc:fill(p2.x,p1.y,p2.x,p2.y)
+ dc:fill(p1.x,p2.y,p2.x,p2.y)
+
+ -- Grid
+ local gxmin = org.x+10*math.ceil((p1.x-org.x)/10)
+ local gxmax = org.x+10*math.floor((p2.x-org.x)/10)
+ local gymin = org.y+10*math.ceil((p1.y-org.y)/10)
+ local gymax = org.y+10*math.floor((p2.y-org.y)/10)
+ for x = gxmin,gxmax,10 do
+ for y = gymin,gymax,10 do
+ dc:fill(p1.x,y,p2.x,y)
+ dc:fill(x,p1.y,x,p2.y)
+ end
+ end
+end
+
+function SiegeEngine:renderTargetView(target_min, target_max)
+ local view = self:getViewport()
+ local map = self.df_layout.map
+ local map_dc = gui.Painter.new(map)
+
+ plugin.paintAimScreen(
+ self.building, view:getPos(),
+ xy2pos(map.x1, map.y1), view:getSize()
+ )
+
+ if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then
+ paint_target_grid(map_dc, view, self.center, target_min, target_max)
+ end
+
+ local cursor = guidm.getCursorPos()
+ if cursor then
+ local cx, cy, cz = pos2xyz(view:tileToScreen(cursor))
+ if cz == 0 then
+ map_dc:seek(cx,cy):char('X', COLOR_YELLOW)
+ end
+ end
+end
+
+function SiegeEngine:scrollPiles(delta)
+ local links = plugin.getStockpileLinks(self.building)
+ if links then
+ self.selected_pile = 1+(self.selected_pile+delta-1) % #links
+ return links[self.selected_pile]
+ end
+end
+
+function SiegeEngine:renderStockpiles(dc, links, nlines)
+ local idx = (self.selected_pile-1) % #links
+ local page = math.floor(idx/nlines)
+ for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do
+ local color = COLOR_BROWN
+ if i == idx then
+ color = COLOR_YELLOW
+ end
+ dc:newline(2):string(utils.getBuildingName(links[i+1]), color)
+ end
+end
+
+function SiegeEngine:onRenderBody_main(dc)
+ dc:newline(1):pen(COLOR_WHITE):string("Target: ")
+
+ local target_min, target_max = plugin.getTargetArea(self.building)
+ if target_min then
+ dc:string(
+ (target_max.x-target_min.x+1).."x"..
+ (target_max.y-target_min.y+1).."x"..
+ (target_max.z-target_min.z+1).." Rect"
+ )
+ else
+ dc:string("None (default)")
+ end
+
+ dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle")
+ if last_target_min then
+ dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste")
+ end
+ dc:newline(3)
+ if target_min then
+ dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ")
+ dc:string("z",COLOR_LIGHTGREEN):string(": Zoom")
+ end
+
+ dc:newline():newline(1)
+ if self.building.type == df.siegeengine_type.Ballista then
+ dc:string("Uses ballista arrows")
+ else
+ local item = plugin.getAmmoItem(self.building)
+ dc:string("u",COLOR_LIGHTGREEN):string(": Use ")
+ if item_choice_idx[item] then
+ dc:string(item_choices[item_choice_idx[item]].caption)
+ else
+ dc:string(df.item_type[item])
+ end
+ end
+
+ dc:newline():newline(1)
+ dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3)
+ local links = plugin.getStockpileLinks(self.building)
+ local bottom = dc.height - 5
+ if links then
+ dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ")
+ dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline()
+ self:renderStockpiles(dc, links, bottom-2-dc:localY())
+ dc:newline():newline()
+ end
+
+ local prof = self.building:getWorkshopProfile() or {}
+ dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ')
+ dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-')
+ dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption)
+ dc:newline():newline()
+
+ if self.target_select_first then
+ self:renderTargetView(self.target_select_first, guidm.getCursorPos())
+ else
+ self:renderTargetView(target_min, target_max)
+ end
+end
+
+function SiegeEngine:setTargetArea(p1, p2)
+ self.target_select_first = nil
+
+ if not plugin.setTargetArea(self.building, p1, p2) then
+ dlg.showMessage(
+ 'Set Target Area',
+ 'Could not set the target area', COLOR_LIGHTRED
+ )
+ else
+ last_target_min = p1
+ last_target_max = p2
+ end
+end
+
+function SiegeEngine:setAmmoItem(choice)
+ if self.building.type == df.siegeengine_type.Ballista then
+ return
+ end
+
+ if not plugin.setAmmoItem(self.building, choice.item_type) then
+ dlg.showMessage(
+ 'Set Ammo Item',
+ 'Could not set the ammo item', COLOR_LIGHTRED
+ )
+ end
+end
+
+function SiegeEngine:onInput_main(keys)
+ if keys.CUSTOM_R then
+ self:showCursor(true)
+ self.target_select_first = nil
+ self.mode = self.mode_aim
+ elseif keys.CUSTOM_P and last_target_min then
+ self:setTargetArea(last_target_min, last_target_max)
+ elseif keys.CUSTOM_U then
+ local item = plugin.getAmmoItem(self.building)
+ local idx = 1 + (item_choice_idx[item] or 0) % #item_choices
+ self:setAmmoItem(item_choices[idx])
+ elseif keys.CUSTOM_Z then
+ self:zoomToTarget()
+ elseif keys.CUSTOM_X then
+ plugin.clearTargetArea(self.building)
+ elseif keys.SECONDSCROLL_UP then
+ self:scrollPiles(-1)
+ elseif keys.SECONDSCROLL_DOWN then
+ self:scrollPiles(1)
+ elseif keys.CUSTOM_D then
+ local pile = self:scrollPiles(0)
+ if pile then
+ plugin.removeStockpileLink(self.building, pile)
+ end
+ elseif keys.CUSTOM_O then
+ local pile = self:scrollPiles(0)
+ if pile then
+ self:centerViewOn(utils.getBuildingCenter(pile))
+ end
+ elseif keys.CUSTOM_T then
+ self:showCursor(true)
+ self.mode = self.mode_pile
+ self:sendInputToParent('CURSOR_DOWN_Z')
+ self:sendInputToParent('CURSOR_UP_Z')
+ elseif keys.CUSTOM_G then
+ local prof = plugin.saveWorkshopProfile(self.building)
+ prof.min_level = math.max(0, prof.min_level-1)
+ plugin.saveWorkshopProfile(self.building)
+ elseif keys.CUSTOM_H then
+ local prof = plugin.saveWorkshopProfile(self.building)
+ prof.min_level = math.min(LEGENDARY, prof.min_level+1)
+ plugin.saveWorkshopProfile(self.building)
+ elseif keys.CUSTOM_J then
+ local prof = plugin.saveWorkshopProfile(self.building)
+ prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1)
+ plugin.saveWorkshopProfile(self.building)
+ elseif keys.CUSTOM_K then
+ local prof = plugin.saveWorkshopProfile(self.building)
+ prof.max_level = math.min(LEGENDARY, prof.max_level+1)
+ if prof.max_level >= LEGENDARY then prof.max_level = 3000 end
+ plugin.saveWorkshopProfile(self.building)
+ elseif self:simulateViewScroll(keys) then
+ self.cursor = nil
+ else
+ return false
+ end
+ return true
+end
+
+local status_table = {
+ ok = { pen = COLOR_GREEN, msg = "Target accessible" },
+ out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" },
+ blocked = { pen = COLOR_RED, msg = "Target obstructed" },
+ semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" },
+}
+
+function SiegeEngine:onRenderBody_aim(dc)
+ local cursor = guidm.getCursorPos()
+ local first = self.target_select_first
+
+ dc:newline(1):string('Select target rectangle'):newline()
+
+ local info = status_table[plugin.getTileStatus(self.building, cursor)]
+ if info then
+ dc:newline(2):string(info.msg, info.pen)
+ else
+ dc:newline(2):string('ERROR', COLOR_RED)
+ end
+
+ dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN)
+ if first then
+ dc:string(": Finish rectangle")
+ else
+ dc:string(": Start rectangle")
+ end
+ dc:newline()
+
+ local target_min, target_max = plugin.getTargetArea(self.building)
+ if target_min then
+ dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target")
+ end
+
+ if first then
+ self:renderTargetView(first, cursor)
+ else
+ local target_min, target_max = plugin.getTargetArea(self.building)
+ self:renderTargetView(target_min, target_max)
+ end
+end
+
+function SiegeEngine:onInput_aim(keys)
+ if keys.SELECT then
+ local cursor = guidm.getCursorPos()
+ if self.target_select_first then
+ self:setTargetArea(self.target_select_first, cursor)
+
+ self.mode = self.mode_main
+ self:showCursor(false)
+ else
+ self.target_select_first = cursor
+ end
+ elseif keys.CUSTOM_Z then
+ self:zoomToTarget()
+ elseif keys.LEAVESCREEN then
+ self.mode = self.mode_main
+ self:showCursor(false)
+ elseif self:simulateCursorMovement(keys) then
+ self.cursor = nil
+ else
+ return false
+ end
+ return true
+end
+
+function SiegeEngine:onRenderBody_pile(dc)
+ dc:newline(1):string('Select pile to take from'):newline():newline(2)
+
+ local sel = df.global.world.selected_building
+
+ if df.building_stockpilest:is_instance(sel) then
+ dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1)
+
+ if plugin.isLinkedToPile(self.building, sel) then
+ dc:string("Already taking from here"):newline():newline(2)
+ dc:string("d", COLOR_LIGHTGREEN):string(": Delete link")
+ else
+ dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile")
+ end
+ elseif sel then
+ dc:string(utils.getBuildingName(sel), COLOR_DARKGREY)
+ dc:newline():newline(1)
+ dc:string("Not a stockpile",COLOR_LIGHTRED)
+ else
+ dc:string("No building selected", COLOR_DARKGREY)
+ end
+end
+
+function SiegeEngine:onInput_pile(keys)
+ if keys.SELECT then
+ local sel = df.global.world.selected_building
+ if df.building_stockpilest:is_instance(sel)
+ and not plugin.isLinkedToPile(self.building, sel) then
+ plugin.addStockpileLink(self.building, sel)
+
+ df.global.world.selected_building = self.building
+ self.mode = self.mode_main
+ self:showCursor(false)
+ end
+ elseif keys.CUSTOM_D then
+ local sel = df.global.world.selected_building
+ if df.building_stockpilest:is_instance(sel) then
+ plugin.removeStockpileLink(self.building, sel)
+ end
+ elseif keys.LEAVESCREEN then
+ df.global.world.selected_building = self.building
+ self.mode = self.mode_main
+ self:showCursor(false)
+ elseif self:propagateMoveKeys(keys) then
+ --
+ else
+ return false
+ end
+ return true
+end
+
+function SiegeEngine:onRenderBody(dc)
+ dc:clear()
+ dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline()
+
+ self.mode.render(dc)
+
+ dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE)
+ dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ")
+ dc:string("c", COLOR_LIGHTGREEN):string(": Recenter")
+end
+
+function SiegeEngine:onInput(keys)
+ if self.mode.input(keys) then
+ --
+ elseif keys.CUSTOM_C then
+ self:centerViewOn(self.center)
+ elseif keys.LEAVESCREEN then
+ self:dismiss()
+ elseif keys.LEAVESCREEN_ALL then
+ self:dismiss()
+ self.no_select_building = true
+ guidm.clearCursorPos()
+ df.global.ui.main.mode = df.ui_sidebar_mode.Default
+ df.global.world.selected_building = nil
+ end
+end
+
+if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then
+ qerror("This script requires a siege engine selected in 'q' mode")
+end
+
+local building = df.global.world.selected_building
+
+if not df.building_siegeenginest:is_instance(building) then
+ qerror("A siege engine must be selected")
+end
+
+local list = mkinstance(SiegeEngine):init(df.global.world.selected_building)
+list:show()
diff --git a/scripts/setfps.lua b/scripts/setfps.lua
new file mode 100644
index 00000000..690f8270
--- /dev/null
+++ b/scripts/setfps.lua
@@ -0,0 +1,10 @@
+-- Set the FPS cap at runtime.
+
+local cap = ...
+local capnum = tonumber(cap)
+
+if not capnum or capnum < 1 then
+ qerror('Invalid FPS cap value: '..cap)
+end
+
+df.global.enabler.fps = capnum