-
Notifications
You must be signed in to change notification settings - Fork 0
/
CrowdControl.lua
291 lines (260 loc) · 7.11 KB
/
CrowdControl.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
-- Depends on ModUtil, StyxScribe, StyxScribeShared
ModUtil.Mod.Register( "CrowdControl" )
local requestTimes = { }
local idQueues = { }
local effectMaps = { }
local cancelled = { }
local timers = { }
local rigid = { }
local ignore = { }
local shared, notifyEffect
-- Helpers
local function handleEffects(effectMap, idQueue)
table.insert( effectMaps, effectMap )
if idQueue then idQueues[ effectMap ] = idQueue end
end
local function checkEffect( id )
return not cancelled[ id ]
end
local function invokeEffect( id, effect, ... )
if not checkEffect( id ) or not effect then return end
local args = table.pack( effect( id, ... ) )
if not checkEffect( id ) then return end
if args.n >= 1 then
local final = true
if not ignore[ id ] and args[ 1 ] == true then
if timers[ id ] then
notifyEffect( id, "Finished" )
timers[ id ] = nil
else
notifyEffect( id, "Success" )
end
elseif args[ 1 ] == false then
if rigid[ id ] then
notifyEffect( id, "Failure" )
rigid[ id ] = nil
else
notifyEffect( id, "Retry" )
end
else
final = false
end
if final then
cancelled[ id ] = true
requestTimes[ id ] = nil
ignore[ id ] = nil
end
end
return table.unpack( args )
end
--[[
effectMap - a map/dictionary from id to effect
idQueue - an array/sequence table of ids
--]]
local function invokeEffects( effectMap, idQueue, ... )
if idQueue then
-- if you provide an id queue then we will mutate that as well
-- this way effects are invoked in insertion order
local n = #idQueue
if n == 0 then return end
for i = 1, n do
local id = idQueue[ i ]
local effect = effectMap[ id ]
invokeEffect( id, effect, ... )
idQueue[ i ] = nil
effectMap[ id ] = nil
end
OverwriteAndCollapseTable( idQueue )
else
-- if you don't provide an id queue then we will just mutate the map
-- this means invocation order is implementation detail / undefined behaviour
for id, effect in pairs( effectMap ) do
if invokeEffect( id, effect, ... ) ~= false then
effectMap[ id ] = nil
end
end
end
end
local function pipeEffect( a, b )
return function( id, ... )
return invokeEffect( id, b, invokeEffect( id, a, ... ) )
end
end
local function bindEffect( effect, value )
return function( id, ... )
return invokeEffect( id, effect, value, ... )
end
end
local function keyedEffect( effect )
return function( id, key, ... )
return invokeEffect( id, effect[key], ... )
end
end
local function rigidEffect( effect )
return function( id, ... )
rigid[ id ] = true
return invokeEffect( id, effect, ... )
end
end
local function softEffect( effect )
return function( id, ... )
rigid[ id ] = false
return invokeEffect( id, effect, ... )
end
end
local function timedEffect( enable, disable )
return function( id, duration, ... )
local ig = ignore[ id ]
ignore[ id ] = true
local args = table.pack( invokeEffect( id, enable, duration, ... ) )
if not checkEffect( id ) then return end
ignore[ id ] = ig
requestTimes[ id ] = _worldTime
timers[ id ] = duration
notifyEffect( id, "Success", duration )
thread( function( )
wait( duration )
if timers[ id ] and disable then
invokeEffect( id, disable, table.unpack( args ) )
end
timers[ id ] = nil
end )
end
end
-- Implementation
local function checkHandledEffects( )
for id, duration in rawpairs( timers ) do
duration = duration - _worldTime + requestTimes[ id ]
timers[ id ] = duration
if duration >= 0 then
notifyEffect( id, "Resumed", duration )
else
print( "CrowdControl: Error: Effect with ID " .. id .. " has negative duration" )
end
end
for i, effectMap in rawipairs( effectMaps ) do
local idQueue = idQueues[ effectMap ]
if idQueue then
for i, id in rawipairs( idQueue ) do
if not checkEffect( id ) then
idQueue[ i ] = nil
effectMap[ id ] = nil
end
end
OverwriteAndCollapseTable( idQueue )
else
for id in rawpairs( effectMap ) do
if not checkEffect( id ) then
effectMap[ id ] = nil
end
end
end
end
end
local function routineCheckHandledEffects( )
while true do
waitScreenTime( CrowdControl.RoutineCheckPeriod )
checkHandledEffects( )
end
end
function notifyEffect( id, ... )
if not checkEffect( id ) then return end
return shared.NotifyEffect( id, ... )
end
local function requestEffect( id, effect, ... )
requestTimes[ id ] = _worldTime
if ModUtil.Callable( effect ) then
return effect( id )
end
local func = CrowdControl.Effects[ effect ]
if not ModUtil.Callable( func ) and type( effect ) == "string" then
func = ModUtil.Path.Get( effect, CrowdControl.Effects )
end
if not ModUtil.Callable( func ) then
return notifyEffect( id, "Unavailable" )
end
return invokeEffect( id, func, ... )
end
local function initShared( )
local root = StyxScribeShared.Root
shared = root.CrowdControl
if not shared then
root.CrowdControl = { }
shared = root.CrowdControl
end
CrowdControl.Shared = shared
shared.RequestEffect = requestEffect
end
local function cancelEffect( message )
local eid = tonumber( message )
cancelled[ eid ] = true
for i, effectMap in rawipairs( effectMaps ) do
effectMap[ eid ] = nil
local idQueue = idQueues[ effectMap ]
if idQueue then
for i, id in rawipairs( idQueue ) do
if id == eid then
idQueue[ i ] = nil
end
end
OverwriteAndCollapseTable( idQueue )
end
end
end
local function resetEffects( )
for k in pairs( cancelled ) do
cancelled[ k ] = nil
end
for k in pairs( rigid ) do
rigid[ k ] = nil
end
for k in pairs( ignore ) do
ignore[ k ] = nil
end
for k in pairs( timers ) do
timers[ k ] = nil
end
for k in pairs( requestTimes ) do
requestTimes[ k ] = nil
end
for i, effectMap in rawipairs( effectMaps ) do
local idQueue = idQueues[ effectMap ]
if idQueue then
for i, id in rawipairs( idQueue ) do
idQueue[ i ] = nil
effectMap[ id ] = nil
end
else
for id in rawpairs( effectMap ) do
effectMap[ id ] = nil
end
end
end
end
-- API
CrowdControl.EffectTimeout = 20
CrowdControl.RoutineCheckPeriod = 2
CrowdControl.Shared = nil
CrowdControl.Effects = { }
CrowdControl.RequestEffect = requestEffect
CrowdControl.NotifyEffect = notifyEffect
CrowdControl.InvokeEffect = invokeEffect
CrowdControl.InvokeEffects = invokeEffects
CrowdControl.HandleEffects = handleEffects
CrowdControl.CheckEffect = checkEffect
CrowdControl.BindEffect = bindEffect
CrowdControl.KeyedEffect = keyedEffect
CrowdControl.TimedEffect = timedEffect
CrowdControl.PipeEffect = pipeEffect
CrowdControl.RigidEffect = rigidEffect
CrowdControl.SoftEffect = softEffect
-- Internal
CrowdControl.Internal = ModUtil.UpValues( function( )
return initShared, requestEffect, notifyEffect, invokeEffect, invokeEffects, timedEffect, cancelEffect, pipeEffect, rigidEffect, softEffect,
bindEffect, keyedEffect, checkEffect, handleEffects, checkHandledEffects, routineCheckHandledEffects, cancelled, rigid, ignore, timers, resetEffects
end )
StyxScribe.AddHook( initShared, "StyxScribeShared: Reset", CrowdControl )
StyxScribe.AddEarlyHook( cancelEffect, "CrowdControl: Cancel: ", CrowdControl )
StyxScribe.AddEarlyHook( resetEffects, "CrowdControl: Reset", CrowdControl )
initShared( )
thread( routineCheckHandledEffects )