Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 51363c8

Browse files
authored
Merge pull request #1440 from myk002/myk_starvingdead
[starvingdead] properly handle state loading
2 parents b5178d6 + 404f492 commit 51363c8

File tree

4 files changed

+139
-109
lines changed

4 files changed

+139
-109
lines changed

changelog.txt

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Template for new versions:
3131
## New Features
3232

3333
## Fixes
34+
- `starvingdead`: properly restore to correct enabled state when loading a new game that is different from the first game loaded in this session
35+
- `starvingdead`: ensure undead decay does not happen faster than the declared decay rate when saving and loading the game
3436

3537
## Misc Improvements
3638

docs/starvingdead.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ gradually decay, losing strength, speed, and toughness. After six months,
1010
they collapse upon themselves, never to be reanimated.
1111

1212
Strength lost is proportional to the time until death, all units will have
13-
roughly 10% of each of their attributes' values when close to being removed.
13+
roughly 10% of each of their attributes' values when they succumb to decay.
1414

1515
In any game, this can be a welcome gameplay feature, but it is especially
16-
useful in preventing undead cascades in the caverns in reanimating biomes,
17-
where constant combat can lead to hundreds of undead roaming the caverns and
16+
useful in preventing undead cascades in the caverns in reanimating biomes.
17+
Constant combat can lead to hundreds of undead roaming the caverns and
1818
destroying your FPS.
1919

2020
Usage

emigration.lua

+13-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ local unit_link_utils = reqscript('internal/emigration/unit-link-utils')
99
local GLOBAL_KEY = 'emigration' -- used for state change hooks and persistence
1010

1111
local function get_default_state()
12-
return {enabled=false, last_cycle_tick=0}
12+
return {
13+
enabled=false,
14+
last_cycle_tick=0
15+
}
1316
end
1417

1518
state = state or get_default_state()
@@ -127,15 +130,15 @@ function checkmigrationnow()
127130
end
128131

129132
local function event_loop()
130-
if state.enabled then
131-
local current_tick = dfhack.world.ReadCurrentTick() + TICKS_PER_YEAR * dfhack.world.ReadCurrentYear()
132-
if current_tick - state.last_cycle_tick < TICKS_PER_MONTH then
133-
local timeout_ticks = state.last_cycle_tick - current_tick + TICKS_PER_MONTH
134-
dfhack.timeout(timeout_ticks, 'ticks', event_loop)
135-
else
136-
checkmigrationnow()
137-
dfhack.timeout(1, 'months', event_loop)
138-
end
133+
if not state.enabled then return end
134+
135+
local current_tick = dfhack.world.ReadCurrentTick() + TICKS_PER_YEAR * dfhack.world.ReadCurrentYear()
136+
if current_tick - state.last_cycle_tick < TICKS_PER_MONTH then
137+
local timeout_ticks = state.last_cycle_tick - current_tick + TICKS_PER_MONTH
138+
dfhack.timeout(timeout_ticks, 'ticks', event_loop)
139+
else
140+
checkmigrationnow()
141+
dfhack.timeout(1, 'months', event_loop)
139142
end
140143
end
141144

starvingdead.lua

+121-96
Original file line numberDiff line numberDiff line change
@@ -3,128 +3,153 @@
33
--@module = true
44

55
local argparse = require('argparse')
6+
local utils = require('utils')
67

78
local GLOBAL_KEY = 'starvingdead'
89

9-
starvingDeadInstance = starvingDeadInstance or nil
10+
local function get_default_state()
11+
return {
12+
enabled=false,
13+
decay_rate=1,
14+
death_threshold=6,
15+
last_cycle_tick=0,
16+
}
17+
end
18+
19+
state = state or get_default_state()
1020

1121
function isEnabled()
12-
return starvingDeadInstance ~= nil
22+
return state.enabled
1323
end
1424

1525
local function persist_state()
16-
dfhack.persistent.saveSiteData(GLOBAL_KEY, {
17-
enabled = isEnabled(),
18-
decay_rate = starvingDeadInstance and starvingDeadInstance.decay_rate or 1,
19-
death_threshold = starvingDeadInstance and starvingDeadInstance.death_threshold or 6
20-
})
26+
dfhack.persistent.saveSiteData(GLOBAL_KEY, state)
2127
end
2228

23-
dfhack.onStateChange[GLOBAL_KEY] = function(sc)
24-
if sc == SC_MAP_UNLOADED then
25-
enabled = false
26-
return
27-
end
28-
29-
if sc ~= SC_MAP_LOADED or df.global.gamemode ~= df.game_mode.DWARF then
30-
return
31-
end
32-
33-
local persisted_data = dfhack.persistent.getSiteData(GLOBAL_KEY, {})
34-
35-
if persisted_data.enabled then
36-
starvingDeadInstance = StarvingDead{
37-
decay_rate = persisted_data.decay_rate,
38-
death_threshold = persisted_data.death_threshold
39-
}
40-
end
41-
end
42-
43-
StarvingDead = defclass(StarvingDead)
44-
StarvingDead.ATTRS{
45-
decay_rate = 1,
46-
death_threshold = 6,
47-
}
48-
49-
function StarvingDead:init()
50-
self.timeout_id = nil
51-
-- Percentage goal each attribute should reach before death.
52-
local attribute_goal = 10
53-
self.attribute_decay = (attribute_goal ^ (1 / ((self.death_threshold * 28 / self.decay_rate)))) / 100
54-
55-
self:checkDecay()
56-
print(([[StarvingDead started, checking every %s days and killing off at %s months]]):format(self.decay_rate, self.death_threshold))
57-
end
58-
59-
function StarvingDead:checkDecay()
60-
for _, unit in pairs(df.global.world.units.active) do
61-
if (unit.enemy.undead and not unit.flags1.inactive) then
62-
-- time_on_site is measured in ticks, a month is 33600 ticks.
63-
-- @see https://dwarffortresswiki.org/index.php/Time
64-
for _, attribute in pairs(unit.body.physical_attrs) do
65-
attribute.value = math.floor(attribute.value - (attribute.value * self.attribute_decay))
66-
end
67-
68-
if unit.curse.interaction.time_on_site > (self.death_threshold * 33600) then
69-
unit.animal.vanish_countdown = 1
70-
end
29+
-- threshold each attribute should reach before death.
30+
local ATTRIBUTE_THRESHOLD_PERCENT = 10
31+
32+
local TICKS_PER_DAY = 1200
33+
local TICKS_PER_MONTH = 28 * TICKS_PER_DAY
34+
local TICKS_PER_YEAR = 12 * TICKS_PER_MONTH
35+
36+
local function do_decay()
37+
local decay_exponent = state.decay_rate / (state.death_threshold * 28)
38+
local attribute_decay = (ATTRIBUTE_THRESHOLD_PERCENT ^ decay_exponent) / 100
39+
40+
for _, unit in pairs(df.global.world.units.active) do
41+
if (unit.enemy.undead and not unit.flags1.inactive) then
42+
for _,attribute in pairs(unit.body.physical_attrs) do
43+
attribute.value = math.floor(attribute.value - (attribute.value * attribute_decay))
44+
end
45+
46+
if unit.curse.interaction.time_on_site > (state.death_threshold * TICKS_PER_MONTH) then
47+
unit.animal.vanish_countdown = 1
48+
end
49+
end
7150
end
72-
end
51+
end
7352

74-
self.timeout_id = dfhack.timeout(self.decay_rate, 'days', self:callback('checkDecay'))
53+
local function get_normalized_tick()
54+
return dfhack.world.ReadCurrentTick() + TICKS_PER_YEAR * dfhack.world.ReadCurrentYear()
7555
end
7656

77-
if dfhack_flags.module then
78-
return
57+
timeout_id = timeout_id or nil
58+
59+
local function event_loop()
60+
if not state.enabled then return end
61+
62+
local current_tick = get_normalized_tick()
63+
local ticks_per_cycle = TICKS_PER_DAY * state.decay_rate
64+
local timeout_ticks = ticks_per_cycle
65+
66+
if current_tick - state.last_cycle_tick < ticks_per_cycle then
67+
timeout_ticks = state.last_cycle_tick - current_tick + ticks_per_cycle
68+
else
69+
do_decay()
70+
state.last_cycle_tick = current_tick
71+
persist_state()
72+
end
73+
timeout_id = dfhack.timeout(timeout_ticks, 'ticks', event_loop)
7974
end
8075

81-
local options, args = {
82-
decay_rate = nil,
83-
death_threshold = nil
84-
}, {...}
76+
local function do_enable()
77+
if state.enabled then return end
8578

86-
local positionals = argparse.processArgsGetopt(args, {
87-
{'h', 'help', handler = function() options.help = true end},
88-
{'r', 'decay-rate', hasArg = true, handler=function(arg) options.decay_rate = argparse.positiveInt(arg, 'decay-rate') end },
89-
{'t', 'death-threshold', hasArg = true, handler=function(arg) options.death_threshold = argparse.positiveInt(arg, 'death-threshold') end },
90-
})
79+
state.enabled = true
80+
state.last_cycle_tick = get_normalized_tick()
81+
event_loop()
82+
end
9183

92-
if dfhack_flags.enable then
93-
if dfhack_flags.enable_state then
94-
if starvingDeadInstance then
95-
return
84+
local function do_disable()
85+
if not state.enabled then return end
86+
87+
state.enabled = false
88+
if timeout_id then
89+
dfhack.timeout_active(timeout_id, nil)
90+
timeout_id = nil
9691
end
92+
end
9793

98-
starvingDeadInstance = StarvingDead{}
99-
persist_state()
100-
else
101-
if not starvingDeadInstance then
102-
return
94+
dfhack.onStateChange[GLOBAL_KEY] = function(sc)
95+
if sc == SC_MAP_UNLOADED then
96+
do_disable()
97+
return
10398
end
10499

105-
dfhack.timeout_active(starvingDeadInstance.timeout_id, nil)
106-
starvingDeadInstance = nil
107-
end
108-
else
109-
if not dfhack.isMapLoaded() then
110-
qerror('This script requires a fortress map to be loaded')
111-
end
100+
if sc ~= SC_MAP_LOADED or not dfhack.world.isFortressMode() then
101+
return
102+
end
112103

113-
if positionals[1] == "help" or options.help then
114-
print(dfhack.script_help())
104+
state = get_default_state()
105+
utils.assign(state, dfhack.persistent.getSiteData(GLOBAL_KEY, state))
106+
107+
event_loop()
108+
end
109+
110+
if dfhack_flags.module then
115111
return
116-
end
112+
end
117113

118-
if positionals[1] == nil then
119-
if starvingDeadInstance then
120-
starvingDeadInstance.decay_rate = options.decay_rate or starvingDeadInstance.decay_rate
121-
starvingDeadInstance.death_threshold = options.death_threshold or starvingDeadInstance.death_threshold
114+
if not dfhack.isMapLoaded() or not dfhack.world.isFortressMode() then
115+
qerror('This script requires a fortress map to be loaded')
116+
end
122117

123-
print(([[StarvingDead is running, checking every %s days and killing off at %s months]]):format(
124-
starvingDeadInstance.decay_rate, starvingDeadInstance.death_threshold
125-
))
118+
if dfhack_flags.enable then
119+
if dfhack_flags.enable_state then
120+
do_enable()
126121
else
127-
print("StarvingDead is not running!")
122+
do_disable()
128123
end
129-
end
124+
end
125+
126+
local opts = {}
127+
local positionals = argparse.processArgsGetopt({...}, {
128+
{'h', 'help', handler=function() opts.help = true end},
129+
{'r', 'decay-rate', hasArg=true,
130+
handler=function(arg) opts.decay_rate = argparse.positiveInt(arg, 'decay-rate') end },
131+
{'t', 'death-threshold', hasArg=true,
132+
handler=function(arg) opts.death_threshold = argparse.positiveInt(arg, 'death-threshold') end },
133+
})
134+
135+
136+
if positionals[1] == "help" or opts.help then
137+
print(dfhack.script_help())
138+
return
139+
end
140+
141+
if opts.decay_rate then
142+
state.decay_rate = opts.decay_rate
143+
end
144+
if opts.death_threshold then
145+
state.death_threshold = opts.death_threshold
146+
end
147+
persist_state()
148+
149+
if state.enabled then
150+
print(([[StarvingDead is running, decaying undead every %s day%s and killing off at %s month%s]]):format(
151+
state.decay_rate, state.decay_rate == 1 and '' or 's', state.death_threshold, state.death_threshold == 1 and '' or 's'))
152+
else
153+
print(([[StarvingDead is not running, but would decay undead every %s day%s and kill off at %s month%s]]):format(
154+
state.decay_rate, state.decay_rate == 1 and '' or 's', state.death_threshold, state.death_threshold == 1 and '' or 's'))
130155
end

0 commit comments

Comments
 (0)