About the
modtools/extra-gamelog.lua... I love the soundsense utility, and am relying on this script to make better use of soundsense. I commented about issues with this script some moths ago, but then modified my own version and used it since. Recently I tested the version in current release (0.44.12-r1), and noticed that it still doesn't work mostly, unfortunately.
The main issues are:
- it tries to use a timer before it is initialises, so it doesn't run more than once and only partially (the event_loop() doesn't trigger, so log_nobles(), log_siege() and log_buildings() never run)
- it tries to uses some variables which are out of scope (should be either global, or local in another localisation)
- the log_buildings() function uses an impossible condition, so it could never run even if the timer was initialised
There are also some minor issues, like logging everything at once (once the other issues are resolved), which is particularly troublesome with nobles and with buildings built, and logging a specific line when besieged, regardless if this line is appropriate (undead sieges have different line).
For these reasons I suggest to change the script to something like this:
-- Regularly writes extra info to gamelog.txt
local help = [====[
modtools/extra-gamelog
======================
This script writes extra information to the gamelog.
This is useful for tools like :forums:`Soundsense <106497>`.
]====]
local msg = dfhack.gui.writeToGamelog
sk_starttime=os.time() -- to delay log_nobles()
sk_logit=false -- to delay log_nobles()
siege = false
buildStatesLoaded = false --so log_buildings() won't list building already built on world load
--luacheck: in=number
function log_on_load(op)
if op ~= SC_WORLD_LOADED then return end
-- Seasons fix for Soundsense
local seasons = {
[-1] = 'Nothing', -- worldgen
[0] = 'Spring',
[1] = 'Summer',
[2] = 'Autumn',
[3] = 'Winter'}
msg(seasons[df.global.cur_season]..' has arrived on the calendar.')
-- Weather fix for Soundsense
local raining = false
local snowing = false
for _, row in ipairs(df.global.current_weather) do
for _, weather in ipairs(row) do
raining = raining or weather == 1
snowing = snowing or weather == 2
end
end
if (not snowing and not raining) then msg("The weather has cleared.")
elseif raining then msg("It has started raining.")
elseif snowing then msg("A snow storm has come.")
end
-- Log site information for forts
if df.world_site.find(df.global.ui.site_id) == nil then return end
local site = df.world_site.find(df.global.ui.site_id)
local fort_ent = df.global.ui.main.fortress_entity
local civ_ent = df.historical_entity.find(df.global.ui.civ_id)
local function fullname(item)
return dfhack.TranslateName(item.name)..' ('..dfhack.TranslateName(item.name ,true)..')'
end
msg('Loaded '..df.global.world.cur_savegame.save_dir..', '..fullname(df.global.world.world_data)..
' at coordinates ('..site.pos.x..','..site.pos.y..')')
msg('Loaded the fortress '..fullname(site)..
(fort_ent and ', colonized by the group '..fullname(fort_ent) or '')..
(civ_ent and ' of the civilization '..fullname(civ_ent)..'.' or '.'))
sk_starttime=os.time()
sk_logit=false
siege = false
buildStatesLoaded = false
event_loop()
end
local old_expedition_leader = nil
local old_mayor = nil
function log_nobles()
local expedition_leader = nil
local mayor = nil
local function check(unit)
if not dfhack.units.isCitizen(unit) then return end
for _, pos in ipairs(dfhack.units.getNoblePositions(unit) or {}) do
if pos.position.name[0] == "expedition leader" then
expedition_leader = unit
elseif pos.position.name[0] == "mayor" then
mayor = unit
end
end
end
for _, unit in ipairs(df.global.world.units.active) do
check(unit)
end
if old_mayor == nil and expedition_leader == nil and mayor ~= nil and old_expedition_leader ~= nil then
msg("Expedition leader was replaced by mayor.")
end
if expedition_leader ~= old_expedition_leader then
if expedition_leader == nil then
msg("Expedition leader position is now vacant.")
else
msg(dfhack.TranslateName(dfhack.units.getVisibleName(expedition_leader)).." became expedition leader.")
end
end
if mayor ~= old_mayor then
if mayor == nil then
msg("Mayor position is now vacant.")
else
msg(dfhack.TranslateName(dfhack.units.getVisibleName(mayor)).." became mayor.")
end
end
old_mayor = mayor
old_expedition_leader = expedition_leader
end
function log_siege()
local function cur_siege()
for _, unit in ipairs(df.global.world.units.active) do
if unit.flags1.active_invader then return true end
end
return false
end
old_siege = siege
siege = cur_siege()
if siege ~= old_siege and siege then
msg("A vile force of darkness has arrived!")
msg("SIEGE: invaders present!")
elseif siege ~= old_siege and not siege then
msg("Siege was broken.")
end
end
local workshopTypes = {
[0]="Carpenters Workshop",
"Farmers Workshop",
"Masons Workshop",
"Craftsdwarfs Workshop",
"Jewelers Workshop",
"Metalsmiths Forge",
"Magma Forge",
"Bowyers Workshop",
"Mechanics Workshop",
"Siege Workshop",
"Butchers Workshop",
"Leatherworks Workshop",
"Tanners Workshop",
"Clothiers Workshop",
"Fishery",
"Still",
"Loom",
"Quern",
"Kennels",
"Kitchen",
"Ashery",
"Dyers Workshop",
"Millstone",
"Custom",
"Tool",
}
local furnaceTypes = {
[0]="Wood Furnace",
"Smelter",
"Glass Furnace",
"Kiln",
"Magma Smelter",
"Magma Glass Furnace",
"Magma Kiln",
"Custom Furnace",
}
buildStates = {} --as:bool[]
function log_buildings()
for _, building in ipairs(df.global.world.buildings.all) do
if df.building_workshopst:is_instance(building) or df.building_furnacest:is_instance(building) then
if buildStatesLoaded and buildStates[building.id] ~= building.flags.exists then
if building.flags.exists then buildStates[building.id] = true else buildStates[building.id] = false end
if building.flags.exists then
if df.building_workshopst:is_instance(building) then
msg(workshopTypes[building.type].." was built.") --hint:df.building_workshopst
elseif df.building_furnacest:is_instance(building) then
msg(furnaceTypes[building.type].." was built.") --hint:df.building_furnacest
end
end
end
if building.flags.exists then buildStates[building.id] = true else buildStates[building.id] = false end
end
end
if #df.global.world.buildings.all>0 then buildStatesLoaded=true end
end
function event_loop()
if sk_logit then --delays log_nobles() by 5 seconds
log_nobles()
else
local sk_actualtime=os.time()
if sk_actualtime-sk_starttime>5 then
sk_logit=true
end
end
log_siege()
log_buildings()
if extra_gamelog_enabled then dfhack.timeout(50, 'ticks', event_loop) end
end
extra_gamelog_enabled = false
local args = {...}
if args[1] == 'disable' then
dfhack.onStateChange[_ENV] = nil
extra_gamelog_enabled = false
elseif args[1] == 'enable' then
dfhack.onStateChange[_ENV] = log_on_load
extra_gamelog_enabled = true
event_loop()
else
print(help)
end
(The line
170 171 can be integrated into the next if statement, it's excluded here to show better.)
This causes every part to trigger when necessary, it seems, with some caveats:
- log_siege() writes two lines if siege is present: one is for compatibility with current setup of many soundsense users, the other gives them ability to trigger separate "battle music" when the save with ongoing siege is loaded
- log_nobles() is delayed by 5 seconds, to allow this battle music to be noticed before it's muted temporarily by logs from log_nobles(); it could be also redone in a way I redid log_buildings() (to prevent logging fake mayor election on load), but this is a bit simpler solution
- log_buildings() doesn't log already built buildings at all on game load. Instead it silently creates a list of buildings present, and then logs only newly built buildings when needed afterwards. Also, it works now.
EDIT: replaced the script, in previous version I managed to include the older version (specifically the log_siege() was old, it wasn't working correctly with load and reload of save without exiting the game).