第 1 卷第 1 章:1–9 年

This commit is contained in:
Chen Yi
2026-10-05 17:30:11 +08:00
parent c56394afdd
commit 3468433c9a
34 changed files with 2564 additions and 4 deletions
+27
View File
@@ -0,0 +1,27 @@
--@ module = true
-- 向当前界面发送按键。用法:dfhack-run "dfannals/act SELECT"
-- 多个按键按顺序发送:dfhack-run "dfannals/act STANDARDSCROLL_DOWN SELECT"
local gui = require('gui')
local args = {...}
local scr = dfhack.gui.getCurViewscreen()
local sent = {}
for _, name in ipairs(args) do
local key = df.interface_key[name]
if key then
scr:feed_key(key)
sent[#sent + 1] = name
else
sent[#sent + 1] = '?未知按键:' .. name
end
end
local f = io.open('dfannals-act.log', 'a')
if f then
f:write(string.format('%s\t%s\tfocus=%s\n', os.date('%H:%M:%S'),
table.concat(sent, ' '), table.concat(dfhack.gui.getCurFocus(), '/')))
f:close()
end
print('sent: ' .. table.concat(sent, ' '))
+28
View File
@@ -0,0 +1,28 @@
--@ module = true
-- 在窗口坐标 (x, y) 模拟一次左键点击。用法:dfhack-run "dfannals/click 160 40"
-- 方式与 DFHack 自带测试工具一致:设置鼠标位置后 simulateInput('_MOUSE_L')。
local gui = require('gui')
local args = {...}
local x, y = tonumber(args[1]), tonumber(args[2])
if not x or not y then
print('用法: dfannals/click <x> <y>')
return
end
df.global.gps.mouse_x = math.floor(x)
df.global.gps.precise_mouse_x = math.floor(x) * df.global.gps.tile_pixel_x
df.global.gps.mouse_y = math.floor(y)
df.global.gps.precise_mouse_y = math.floor(y) * df.global.gps.tile_pixel_y
local scr = dfhack.gui.getCurViewscreen()
gui.simulateInput(scr, '_MOUSE_L')
local f = io.open('dfannals-act.log', 'a')
if f then
f:write(string.format('%s\tclick %d,%d\tfocus=%s\n', os.date('%H:%M:%S'), x, y,
table.concat(dfhack.gui.getCurFocus(), '/')))
f:close()
end
print(string.format('clicked %d,%d', x, y))
+57
View File
@@ -0,0 +1,57 @@
--@ module = true
-- 把当前界面的字段结构导出到 dfannals-dump.txt,用于摸清 UI 元素。
-- 用法:dfhack-run "dfannals/dump"
local MAX_ITEMS = 400
local function is_scalar(v)
local t = type(v)
return t == 'number' or t == 'string' or t == 'boolean'
end
local function describe(obj, max_depth)
local out = {}
local function rec(o, prefix, depth)
if depth > max_depth or #out > MAX_ITEMS then return end
local ok, iter, state, ctrl = pcall(pairs, o)
if not ok then return end
for k, v in iter, state, ctrl do
if #out > MAX_ITEMS then return end
local key = tostring(k)
if is_scalar(v) then
out[#out + 1] = string.format('%s%s = %s', prefix, key, tostring(v))
else
local t = type(v)
out[#out + 1] = string.format('%s%s <%s>', prefix, key, t)
if t == 'table' or t == 'userdata' then
rec(v, prefix .. ' ', depth + 1)
end
end
end
end
rec(obj, '', 1)
return out
end
local f = io.open('dfannals-dump.txt', 'w')
if not f then return end
local function write(line) f:write(line .. '\n') end
write('# focus=' .. table.concat(dfhack.gui.getCurFocus(), '/'))
write('# date=' .. os.date('%Y-%m-%d %H:%M:%S'))
local scr = dfhack.gui.getCurViewscreen()
local depth = 0
while scr and depth < 3 do
write('==== 屏幕层 ' .. depth .. ' ====')
local ok, lines = pcall(describe, scr, 2)
if ok then
for _, l in ipairs(lines) do write(l) end
else
write('(失败: ' .. tostring(lines) .. ')')
end
scr = scr.parent
depth = depth + 1
end
f:close()
+39
View File
@@ -0,0 +1,39 @@
--@ module = true
-- 观测模式:把界面焦点变化持续追加到 dfannals-observe.log。
-- 用途:发现"新建世界 → 进入 legends → 导出"之间的真实界面序列。
local repeatUtil = require('repeat-util')
local LOG = 'dfannals-observe.log'
local last = nil
local ticks = 0
local function focus_string()
local ok, focus = pcall(dfhack.gui.getCurFocus)
if not ok or not focus then return '?' end
return table.concat(focus, '/')
end
function tick()
ticks = ticks + 1
local fs = focus_string()
if fs ~= last then
last = fs
local f = io.open(LOG, 'a')
if f then
f:write(string.format('%s\t%s\n', os.date('%H:%M:%S'), fs))
f:close()
end
end
end
function start()
repeatUtil.scheduleEvery('dfannals-observe', 10, tick)
local f = io.open(LOG, 'a')
if f then
f:write('--- observe start ' .. os.date('%Y-%m-%d %H:%M:%S') .. '\n')
f:close()
end
end
start()
+56
View File
@@ -0,0 +1,56 @@
--@ module = true
-- 把当前界面状态写入 dfannals-probe.txt,供 WSL 侧读取。
-- 用法(在 WSL 里):"Dwarf Fortress.exe" 所在目录的 dfhack-run.exe dfannals/probe
local function safe_type(obj)
if not obj then return '?' end
local ok, t = pcall(function() return obj._type end)
if ok and t then return tostring(t) end
return '?'
end
local function viewstack()
local out = {}
local scr = dfhack.gui.getCurViewscreen()
local depth = 0
while scr and depth < 15 do
out[#out + 1] = string.format('[%d] %s', depth, safe_type(scr))
local ok, parent = pcall(function() return scr.parent end)
if not ok then break end
scr = parent
depth = depth + 1
end
return out
end
local function world_info()
local lines = {}
local ok, folder = pcall(dfhack.world.ReadWorldFolder)
lines[#lines + 1] = 'world_folder=' .. (ok and tostring(folder) or '?')
local ok2, year = pcall(dfhack.world.ReadCurrentYear)
lines[#lines + 1] = 'current_year=' .. (ok2 and tostring(year) or '?')
local ok3, legends = pcall(dfhack.world.isLegends)
lines[#lines + 1] = 'is_legends=' .. (ok3 and tostring(legends) or '?')
local ok4, fort = pcall(dfhack.world.isFortressMode)
lines[#lines + 1] = 'is_fortress=' .. (ok4 and tostring(fort) or '?')
local ok5, name = pcall(function() return dfhack.translation.translateName(df.global.world.world_data.name) end)
lines[#lines + 1] = 'world_name=' .. (ok5 and tostring(name) or '?')
return lines
end
function dump()
local lines = {}
local ok, focus = pcall(dfhack.gui.getCurFocus)
lines[#lines + 1] = 'focus=' .. ((ok and focus) and table.concat(focus, '/') or '?')
for _, l in ipairs(world_info()) do lines[#lines + 1] = l end
lines[#lines + 1] = 'viewstack:'
for _, l in ipairs(viewstack()) do lines[#lines + 1] = ' ' .. l end
local f = io.open('dfannals-probe.txt', 'w')
if f then
f:write(table.concat(lines, '\n') .. '\n')
f:close()
end
end
dump()
+49
View File
@@ -0,0 +1,49 @@
--@ module = true
-- 遍历当前界面的控件树,导出控件名与矩形坐标,用于定位可点击目标。
-- 用法:dfhack-run "dfannals/widgets"
local MAX_DEPTH = 8
local MAX_LINES = 600
local function rect_str(w)
local ok, r = pcall(function() return w.rect end)
if not ok or not r then return '' end
local function g(k)
local ok2, v = pcall(function() return r[k] end)
return ok2 and tostring(v) or '?'
end
return string.format('rect=(%s,%s)-(%s,%s)', g('x1'), g('y1'), g('x2'), g('y2'))
end
local out = {}
local function walk(w, depth)
if not w or depth > MAX_DEPTH or #out > MAX_LINES then return end
local ok, name = pcall(function() return w.name end)
local nm = ok and tostring(name) or ''
local txt = ''
local ok3, t = pcall(function() return w.text end)
if ok3 and t then txt = tostring(t) end
out[#out + 1] = string.format('%s%s %s %s', string.rep(' ', depth), nm, rect_str(w),
txt ~= '' and ('text=' .. txt) or '')
local ok2, children = pcall(function() return w.children end)
if ok2 and children then
for _, c in ipairs(children) do walk(c, depth + 1) end
end
end
local f = io.open('dfannals-widgets.txt', 'w')
if not f then return end
f:write('# focus=' .. table.concat(dfhack.gui.getCurFocus(), '/') .. '\n')
local sw, sh = dfhack.screen.getWindowSize()
f:write(string.format('# window=%dx%d\n', sw, sh))
local scr = dfhack.gui.getCurViewscreen()
local ok, widgets = pcall(function() return scr.widgets end)
if ok and widgets then
walk(widgets, 0)
else
out[#out + 1] = '(该界面没有 widgets 字段)'
end
for _, l in ipairs(out) do f:write(l .. '\n') end
f:close()