-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmessaging.lua
382 lines (301 loc) · 14.7 KB
/
messaging.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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
--[[
Omnified messaging library.
Written By: Matt Weber (https://badecho.com) (https://twitch.tv/omni)
Copyright 2024 Bad Echo LLC
Bad Echo Technologies are licensed under the
GNU Affero General Public License v3.0.
See accompanying file LICENSE.md or a copy at:
https://www.gnu.org/licenses/agpl-3.0.html
--]]
require("omnified")
require("statisticMessages")
require("apocalypseMessages")
-- Reads the current death counter from a local death counter file.
local function readDeathCounter()
local deathCounterFile = io.open("deathCounter.txt", "r")
if deathCounterFile == nil then
return
end
local deathCounterFromFile = deathCounterFile:read("*n")
if deathCounterFromFile == nil then
deathCounterFromFile = 0
end
deathCounterFile:close()
return deathCounterFromFile
end
local function writeDeathCounter(deathCounter)
local deathCounterFile = assert(io.open("deathCounter.txt", "w"))
deathCounterFile:write(deathCounter)
deathCounterFile:close()
end
-- Creates a JSON-encoded dump of hacked game statistics.
local function dumpStatistics()
local playerHealth = not healthIsInteger
and toInt(readFloat(playerHealthAddress))
or toInt(readInteger(playerHealthAddress))
local playerMaxHealth = not healthIsInteger
and toInt(readFloat(playerMaxHealthAddress))
or toInt(readInteger(playerMaxHealthAddress))
local playerStamina = not staminaIsInteger
and toInt(readFloat(playerStaminaAddress))
or toInt(readInteger(playerStaminaAddress))
local playerMaxStamina = not staminaIsInteger
and toInt(readFloat(playerMaxStaminaAddress))
or toInt(readInteger(playerMaxStaminaAddress))
local hidePlayerHealth = readInteger("hidePlayerHealth") == 1
and true
or false
local hidePlayerStamina = readInteger("hidePlayerStamina") == 1
and true
or false
-- Last damaged enemy health is always floating point, as it is maintained by the Apocalypse system.
local lastEnemyHealthValue = toInt(readFloat("lastEnemyHealthValue"))
local lastDamageToPlayer = toInt(readFloat("lastDamageToPlayer"))
local maxDamageToPlayer = toInt(readFloat("maxDamageToPlayer"))
local totalDamageToPlayer = toInt(readFloat("totalDamageToPlayer"))
local lastEnemyDamageEvent = toInt(readFloat("lastEnemyDamageEvent"))
local maxEnemyDamageEvent = toInt(readFloat("maxEnemyDamageEvent"))
local totalEnemyDamage = toInt(readFloat("totalEnemyDamage"))
local enemyDamagePulses = readInteger("enemyDamagePulses")
local playerCoordinates = readPlayerCoordinates()
local hidePlayerCoordinates = readInteger("hidePlayerCoordinates") == 1
and true
or false
local playerDamageX = readFloat("playerDamageX")
if playerDamageX ~= nil then
playerDamageX = toInt(playerDamageX * 100)
end
local playerSpeedX = readFloat("playerSpeedX")
if playerSpeedX ~= nil then
playerSpeedX = toInt(playerSpeedX * 100)
end
local deathCounterFromFile = readDeathCounter()
local deathCounter = toInt(readInteger("deathCounter"))
if deathCounterFromFile ~= 0 and deathCounter == 0 then
deathCounter = deathCounterFromFile
writeInteger("deathCounter", deathCounter)
end
if deathCounter ~= nil then
writeDeathCounter(deathCounter)
end
local statistics = {
FractionalStatistic("Health", playerHealth, playerMaxHealth, "#AA43BC50", "#AA27D88D", hidePlayerHealth),
FractionalStatistic("Stamina", playerStamina, playerMaxStamina, "#AA7515D9", "#AAB22DE5", hidePlayerStamina),
WholeStatistic("Enemy Health", lastEnemyHealthValue),
StatisticGroup("Damage Taken", {
WholeStatistic("Last", lastDamageToPlayer),
WholeStatistic("Max", maxDamageToPlayer, true),
WholeStatistic("Total", totalDamageToPlayer)
}),
StatisticGroup("Damage Inflicted", {
WholeStatistic("Hits", enemyDamagePulses),
WholeStatistic("Last", lastEnemyDamageEvent),
WholeStatistic("Max", maxEnemyDamageEvent, true),
WholeStatistic("Total", totalEnemyDamage)
}),
CoordinateStatistic("Coordinates", playerCoordinates.X, playerCoordinates.Y, playerCoordinates.Z, hidePlayerCoordinates),
WholeStatistic("Player Damage", playerDamageX, false, "{0}%"),
WholeStatistic("Player Speed", playerSpeedX, false, "{0}%"),
WholeStatistic("Deaths", deathCounter)
}
local additionalIndex = 2
for _, v in pairs(additionalStatistics ~= nil and additionalStatistics() or {}) do
additionalIndex = additionalIndex + 1
table.insert(statistics, additionalIndex, v)
end
return jsonEncode(statistics)
end
-- Creates a JSON-Lines-encoded dump of recent Player Apocalypse events.
local function dumpPlayerApocalypseEvent()
local apocalypseEvent = ApocalypseEvent()
-- Fatalis checks.
local fatalisState = readInteger("fatalisState")
-- A 'fatalisState' of 3 means we've been cured of Fatalis!
if fatalisState == 3 then
local fatalisDeaths = readInteger("fatalisDeaths")
local fatalisMinutesAfflicted = readInteger("fatalisMinutesAfflicted")
writeInteger("fatalisState", 0)
writeInteger("fatalisDeaths", 0)
writeInteger("fatalisMinutesAfflicted", 0)
return jsonEncode(FatalisCuredEvent(apocalypseEvent, fatalisDeaths, fatalisMinutesAfflicted))
end
-- A 'fatalisState' of 2 means our exposure to Fatalis has developed into an affliction and, because we've just been damaged,
-- we're dead.
if fatalisState == 2 then
local fatalisHealthLost = toInt(readFloat("fatalisHealthLost"))
return jsonEncode(FatalisDeathEvent(apocalypseEvent, fatalisHealthLost))
end
-- Main Player Apocalypse die roll checks.
local apocalypseDieRoll = readInteger("apocalypseDieRoll")
local lastDamageToPlayer = toInt(readFloat("lastDamageToPlayer"))
local healthAfter = not healthIsInteger
and toInt(readFloat(playerHealthAddress))
or readInteger(playerHealthAddress)
apocalypseEvent
= PlayerApocalypseEvent(apocalypseEvent, apocalypseDieRoll, lastDamageToPlayer, healthAfter)
if apocalypseDieRoll <= 4 then
-- Extra damage.
local extraDamageX = readFloat("extraDamageX")
apocalypseEvent = ExtraDamageEvent(apocalypseEvent, extraDamageX)
elseif apocalypseDieRoll <= 6 then
-- Teleportitis.
local lastXDisplacement = readFloat("lastXDisplacement")
local lastYDisplacement = readFloat("lastYDisplacement")
local lastZDisplacement = readFloat("lastZDisplacement")
local lastVerticalDisplacement = readFloat("lastVerticalDisplacement")
local isFreeFalling = lastVerticalDisplacement > freeFallThreshold
apocalypseEvent = TeleportitisEvent(apocalypseEvent,
lastXDisplacement,
lastYDisplacement,
lastZDisplacement,
isFreeFalling)
elseif apocalypseDieRoll <= 9 then
-- Risk of Murder.
local murderRoll = readInteger("murderRoll")
apocalypseEvent = RiskOfMurderEvent(apocalypseEvent, murderRoll)
if murderRoll <= 3 then
local fatalisAfflicted = fatalisState == 1
if fatalisAfflicted then
-- The Fatalis will now be treated as an affliction upon the next hit to the player.
writeInteger("fatalisState", 2)
end
-- Normal damage. If we've been exposeed to Fatalis, then we must be sure to announce it!
apocalypseEvent = NormalDamageEvent(apocalypseEvent, fatalisAfflicted)
else
-- Murder!
local murderDamageX = readFloat("murderDamageX")
apocalypseEvent = MurderEvent(apocalypseEvent, murderDamageX)
end
-- Orgasm!
else
local orgasmHealthHealed = toInt(readFloat("orgasmHealthHealed"))
apocalypseEvent = OrgasmEvent(apocalypseEvent, orgasmHealthHealed)
end
return jsonEncode(apocalypseEvent)
end
-- Creates a JSON-Lines-encoded dump of recent Enemy Apocalypse events.
local function dumpEnemyApocalypseEvent()
local apocalypseEvent = ApocalypseEvent()
local logEnemyApocalypseCrit = readInteger("logEnemyApocalypseCrit")
local lastEnemyDamageEventBonusX = readFloat("lastEnemyDamageEventBonusX")
local lastEnemyDamageEventBonusAmount = toInt(readFloat("lastEnemyDamageEventBonusAmount"))
if logEnemyApocalypseCrit == 1 then
local playerCritDamageResult = readInteger("playerCritDamageResult")
local playerCritDamageResultUpper = readInteger("playerCritDamageResultUpper")
local playerCritDamageResultLower = readInteger("playerCritDamageResultLower")
local playerCritDamageRange
= playerCritDamageResultUpper - playerCritDamageResultLower
local playerCritExtremeMinimum
= playerCritDamageResultUpper - (playerCritDamageRange * (1/3))
local isExtreme = playerCritDamageResult >= playerCritExtremeMinimum
apocalypseEvent = EnemyApocalypseEvent(apocalypseEvent,
lastEnemyDamageEventBonusAmount,
lastEnemyDamageEventBonusX,
BonusDamageType.CriticalHit,
isExtreme)
else
apocalypseEvent = EnemyApocalypseEvent(apocalypseEvent,
lastEnemyDamageEventBonusAmount,
lastEnemyDamageEventBonusX,
BonusDamageType.Kamehameha,
false)
end
return jsonEncode(apocalypseEvent)
end
-- Saves any newly accumulated damage pulses as the most recent damage event.
local function commitNewDamageEvents()
local newEnemyDamageEvent = readFloat("newEnemyDamageEvent")
local newEnemyDamageEventBonusAmount = readFloat("newEnemyDamageEventBonusAmount")
local newEnemyDamageEventBonusX = readFloat("newEnemyDamageEventBonusX")
if newEnemyDamageEvent > 0 then
writeFloat("lastEnemyDamageEvent", newEnemyDamageEvent)
end
if newEnemyDamageEventBonusAmount > 0 then
writeFloat("lastEnemyDamageEventBonusAmount", newEnemyDamageEventBonusAmount)
end
if newEnemyDamageEventBonusX > 0 then
writeFloat("lastEnemyDamageEventBonusX", newEnemyDamageEventBonusX)
end
end
-- Processes recent Apocalypse events.
local function processApocalypseEvents()
local newEnemyDamageEventNotProcessed = readInteger("newEnemyDamageEventNotProcessed")
local logPlayerApocalypse = readInteger("logPlayerApocalypse")
local logEnemyApocalypseGoku = readInteger("logEnemyApocalypseGoku")
local logEnemyApocalypseCrit = readInteger("logEnemyApocalypseCrit")
if newEnemyDamageEventNotProcessed == 1 then
commitNewDamageEvents()
writeInteger("newEnemyDamageEventNotProcessed", 0)
end
if logPlayerApocalypse == 1 then
local playerApocalypseEvent = dumpPlayerApocalypseEvent()
writeInteger("logPlayerApocalypse", 0)
local cooldownMilliseconds = readInteger("apocalypseCooldownMilliseconds")
if cooldownMilliseconds ~= 0 then
-- If a cooldown period between Apocalypse rolls is requested, we flag the system as being in cooldown
-- and then set up a timer to reenable it.
writeInteger("apocalypseInCooldown", 1)
if apocalypseCooldownTimer == nil then
apocalypseCooldownTimer = createTimer(getMainForm())
end
apocalypseCooldownTimer.Interval = cooldownMilliseconds
apocalypseCooldownTimer.OnTimer = function()
writeInteger("apocalypseInCooldown", 0)
apocalypseCooldownTimer.Enabled = false
apocalypseCooldownTimer.Destroy()
apocalypseCooldownTimer = nil
end
end
return playerApocalypseEvent
end
if any(1, logEnemyApocalypseGoku, logEnemyApocalypseCrit) then
local enemyApocalypseEvent = dumpEnemyApocalypseEvent()
writeInteger("logEnemyApocalypseCrit", 0)
writeInteger("logEnemyApocalypseGoku", 0)
return enemyApocalypseEvent
end
end
-- Enables the publishing of Apocalypse event messages.
function startApocalypseEventsPublisher()
if apocalypseTimer == nil then
apocalypseTimer = createTimer(getMainForm())
end
apocalypseTimer.Interval = 50
apocalypseTimer.OnTimer = function()
local apocalypseEvent = processApocalypseEvents()
if apocalypseEvent ~= nil then
-- New files don't get a newline, otherwise we'll have a blank first line. Duh!
if fileExists("apocalypse.jsonl") then apocalypseEvent = "\n" .. apocalypseEvent end
local apocalypseEventsFile = assert(io.open("apocalypse.jsonl", "a"))
apocalypseEventsFile:write(apocalypseEvent)
apocalypseEventsFile:close()
end
end
end
-- Disables the publishing of Apocalypse event messages.
function stopApocalypseEventsPublisher()
if apocalypseTimer == nil then return end
apocalypseTimer.Enabled = false
apocalypseTimer.Destroy()
apocalypseTimer = nil
end
-- Enables the publishing of Omnified game statistic messages.
function startStatisticsPublisher()
if statisticsTimer == nil then
statisticsTimer = createTimer(getMainForm())
end
statisticsTimer.Interval = 50
statisticsTimer.OnTimer = function()
local statistics = dumpStatistics()
local statisticsFile = assert(io.open("statistics.json","w"))
statisticsFile:write(statistics)
statisticsFile:close()
end
end
-- Disable the publishing of Omnified game statistic messages.
function stopStatisticsPublisher()
if statisticsTimer == nil then return end
statisticsTimer.Enabled = false
statisticsTimer.Destroy()
statisticsTimer = nil
end