Prior to the creation of the Apocalypse system, Omnified games, for the most part, exercised a simple maxim: if you get hit, you die. Indeed, my very first Omnified hack was to kill the player dead whenever an enemy managed to inflict any damage at all. If I didn’t simply have some kill code go off when the player was struck, I’d achieve the same goal by multiplying all damage done by 69x, or by some other catchy number.
Then, one fateful eve…the Apocalypse system was born!
It was the result of me setting goals for myself to learn more and more for each game that I hacked. For every new game, I needed to do something a bit fancier; changes that relied on more complicated rules getting set up, as well as deeper and more complicated injections into code found through intensive sessions of reverse engineering.
Now, ever since the Apocalypse was born, Omnified gameplay consists of random effects going off in response to the player receiving damage. Many of these effects can easily kill the character, but, more or less, there is technically a smaller chance we end up dying immediately from Apocalypse rules than when there was a simple “one hit == kill” hack implemented.
Now, don’t get me wrong, I love the Apocalypse system, the kind of gameplay it offers is amazing and always keeps me on my toes. Still, I sometimes pine for the incredibly frantic days of old where I had to fear damage from anything killing me very dead in one hit. So, I started to think to myself: is there any way to enjoy the best of both worlds?
And this line of thought inspired the latest addition to the Apocalypse system: the dreaded temporary status effect and malady known as Fatalis.
Mechanics of Fatalis
Fatalis is a status effect that, when active on the player, will result in all damage done to the player resulting in immediate and irrevocable death. Through magical and mysterious means, this status effect persists across deaths, and only dissipates after a certain period of time has elapsed. In a sense, the Fatalis status effect is a temporary “one-hit kill” mode.
By adding Fatalis to Apocalypse, it basically allows for the gameplay to be able to shift to the terrifying old school Omnified one-hit-and-you-dead mechanics at the drop of a dime. So, I get to enjoy the structured randomness of the Apocalypse system, and whenever that randomness happens to fall on the tiny chance of afflicting us with Fatalis, I get to enjoy the original whirlpool of murderous one-shot mayhem.
And although it’s going to be lots of fun, Fatalis afflictions should be a rare occurrence.
All of Apocalypse’s random effects go off in response to the results of various “dice” rolls. It is very much on purpose that the whole theme of Apocalypse reeks a bit of the rolls of dice that happen during a D&D game. Currently, the majority of the effects that go off are based on the rolls of a ten sided die and a five sided die. Simply adding another side to the dice and sticking Fatalis on that side would not do, as the probability of it coming up would be far too common.
Instead, we’ll be adding a chance for Fatalis to go off in conjunction with another effect occurring; ideally, a benign effect that doesn’t do much on its own. When this benign effect goes off, we then do a many-sided die roll that will result in Fatalis if the roll results in one specific number coming up. Let’s now look at a listing of all the mechanics related to Fatalis, including how it is applied and when it dissipates.
Fatalis: How It’s Applied, Its Effects, and Lifetime
- If Apocalypse’s primary dice roll results in values 7, 8, or 9, a Risk of Murder roll occurs.
- If the Risk of Murder roll results in a 4 or 5, the player is sixty nined, and is pretty much screwed right there. However, if the player lucks out and it lands on a 1, 2, or 3, normal damage is applied instead.
- This “normal damage” effect is quite benign! Therefore, it is a great place to do the check for whether or not Fatalis is getting applied. At this point in time then, we will roll a many-sided die, and if it results in the highest possible number, the player now has Fatalis.
- An appropriate sound effect will go off heralding the player being afflicted with Fatalis, and a notification will be logged in the Apocalypse event log informing all viewers of this fact as well.
- For the next ten minutes (or whatever we set the duration to in the end), every time the player is hit, the player is immediately killed instead of normal Apocalypse rolls going off. These instant deaths should be accompanied with their own sound effect as well, along with a notification about this instant death also appearing in the event log.
- After the Fatalis effect has expired, a final, very cheery and positive sound effect will go off announcing the end of the suffering, along with a notification in the event log regarding the Fatalis debuff being cured. Normal Apocalypse operation resumes.
The above list basically describes everything we need the Fatalis debuff to do. When we get afflicted with it, we die in one hit. After some time, it goes away and things go back to normal.
This is the first time I’ve ever added a timed status effect to Omnified code.
And hopefully it’ll be the first of many. Very excited. Let’s get to work on implementing it now then.
Changes to the Omnified Framework Library
Game-neutral code for my Omnified systems can be found in the Omnified framework library file: Omnified.lua. At the time of writing, I don’t have my Omnified hacking code uploaded to the Bad Echo source repository, but I will make sure I do so not long after publishing this.
This file contains all the game-neutral assembly code as well as LUA functions needed in order to bring it to life. I’ll be writing a nice little article about why I have it all packaged in a LUA file as well, so stay tuned for that.
We’re going to need to do a few things in order to get Fatalis implemented:
- Add the new Fatalis dice roll mechanics in the assembly function
- Add a means to monitor for status effects and remove them upon their expiration in the LUA initialization routine of the Omnified framework.
- Add new display logic regarding Fatalis to the Omnified display routines found in OmnifiedDisplay.lua.
Let’s start with the assembly changes.
Apocalypse Assembly Function Changes
All of our changes will be occurring to the Player Apocalypse Function. First, we’ll need to add a few new symbols.
Player Apocalypse Code – New Symbols
alloc(fatalisResult,8) alloc(fatalisResultUpper,8) alloc(fatalisResultLower,8) // fatalisState: 0 = not active; 1 = active; 2 = cured (used for announcement, then set to 0) alloc(fatalisState,8) // The rest of the symbol allocations follows... // ...and then we register some of our new symbols. registersymbol(fatalisResult) registersymbol(fatalisResultUpper) registersymbol(fatalisState)
We create a number of new symbols here. The first three deal with the random number generation involved when checking for Fatalis affliction, and the last new symbol deals with the current state of Fatalis affliction. The dice roll result and state are both made public here as it we need to inspect their values from outside of the assembly code.
The upper limit on the Fatalis dice roll is also public because that is the value that determines whether or not it actually ends up getting applied, so it will be useful to the display-related functions in determining whether we need to make an announcement or not.
Next up we’ll modify the part of the Apocalypse function found right before the primary dice roll. If the player has the Fatalis debuff, we will kill them right then and there and forego the rest of the normal dice rollings.
Player Apocalypse Code – Fatalis Death Dealin’
applyApocalypse: // If the player has the Fatalis debuff, all damage is fatal. cmp [fatalisState],1 jne applyApocalypseRoll // To make good on the Fatalis debuff, we set the damage equal to the health. movss xmm0,xmm3 jmp updateEnemyDamageStats applyApocalypseRoll: // Load the parameters for generating the dice roll random number. push [apocalypseResultLower] push [apocalypseResultUpper] mov rax,playerApocalypseRandomState push rax call generateRandomNumber
fatalisState symbol is 1, then it is game over man.
We now need to write the code that will actually apply the affliction. We stated earlier that we were going to attach the roll to check for Fatalis in the event that a “normal damage” Risk of Murder effect occurs, so let’s insert some new instructions there.
Player Apocalypse Code – Fatalis Affliction
riskOfMurder: // Load the parameters for generating the Risk of Murder dice roll random // number. push [riskOfMurderResultLower] push [riskOfMurderResultUpper] mov rax,playerApocalypseRandomState push rax call generateRandomNumber // Our Risk of Murder roll is in eax -- we back it up to the "riskOfMurderResult" // symbol so that the value can be displayed by the event logging display code. mov [riskOfMurderResult],eax cmp eax,3 // If the resulting roll is 4 or 5, then the player is getting sixty nined. jg sixtyNine // Otherwise, normal damage applies, however there is also now a very slight chance // of Fatalis being applied, but only if it is not already active. cmp [fatalisState],1 je updateEnemyDamageStats push [fatalisResultLower] push [fatalisResultUpper] mov rax,playerApocalypseRandomState push rax call generateRandomNumber // The Fatalis roll is in eax -- we throw it into the "fatalisResult" symbol // so we can report on it. mov [fatalisResult],eax // Fatalis will only be applied if the roll landed on the maximum possible value. cmp eax,[fatalisResultUpper] jne updateEnemyDamageStats mov [fatalisState],1 jmp updateEnemyDamageStats
The above is the existing Risk of Murder code coupled with some new random number generation instructions purposed to check for whether or not the player is now afflicted with Fatalis.
And that’s pretty much it for the assembly side of things. We’ll just need to define some defaults for our new symbols…
Player Apocalypse Code – Fatalis Symbol Defaults
fatalisResult: dd 0 fatalisResultUpper: dd 5 fatalisResultLower: dd 1 fatalisState: dd 0
So, as it stands now, the probability that Fatalis is applied is a 1 in 5 chance upon a “normal effect” Risk of Murder roll occurring. I settled on using this particular probability by thinking through all the various rolls in play in order for a Fatalis affliction to occur:
- Every time we’re hit, there’s a 30% chance a Risk of Murder roll happens (10 sided die, value landing on 7, 8, or 9).
- With a Risk of Murder roll, there’s a 60% chance of normal damage occurring (5 sided die, value landing on 1, 2, or 3).
- Taking that into account, we can then say that every time we’re hit, there is an 18% chance that Fatalis might get applied to the player.
- Given that there’s a 1 in 5 chance that Fatalis might be applied when we’re checking for it, coupled with an 18% chance for the check, we can say that there’s a 3.6% chance that an attack done to the player will result in Fatalis.
I’d like the effect to be frequent enough that I get it perhaps a few times per stream, but not so frequent that gameplay becomes wall-to-wall Fatalis sessions. I think 3.6% of all attacks is a good probability to satisfy this desire, but in the event I’m wrong, I’ll be tweaking the value as we go!
Now that we have the “meat” of the matter inserted into our core assembly code, we’re going to need to now build up a monitoring system that will check for the Fatalis affliction and “cure” the player at the appropriate time.
Omnified LUA Changes
Up until now, we’ve never engaged in any kind of “timed effects” with Omnified hackery. We need some way to establish a system of monitoring for and making good the expiration of these timed buffs and debuffs. I have many plans for the future where we’ll be adding a whole slew of additional timed status effects, with a bunch of visual indications of their state to boot, so it is good that we’re starting to create some kind of infrastructure for them now.
In order to monitor and manage status effects, we’re going to need to make use of one or more timers that will be constantly checking the state of the player, changing the relevant bits at the appointed time of said effects’ expiration. The best place to initialize and use these higher level constructs is outside of the scary realm of assembly and in the less mind-numbingly complex land of LUA.
In Omnified.lua, we have several registration and unregistration methods responsible for injecting and uninjecting the assembly code from the target process in memory. After we successfully inject our code into the process, we’ll want to then create the status effect timers. Conversely, when we uninject our assembly code from the process, we’ll want to clean up these timers.
Omnified Registration Code – Fatalis Timers
function registerOmnification() if registered == false then registered, disableInfo = autoAssemble(omnifiedAssembly) if registered == false then print("Failed to register Omnification.") do return end end if statusTimer == nil then statusTimer = createTimer(getMainForm()) end statusTimer.Interval = 400 statusTimer.OnTimer = function() local fatalisState = readInteger("fatalisState") if fatalisState == 1 and fatalisTimer == nil then fatalisTimer = createTimer(getMainForm()) fatalisTimer.Interval = 600000 fatalisTimer.OnTimer = function() writeInteger("fatalisState",2) fatalisTimer.Enabled = false fatalisTimer.Destroy() fatalisTimer = nil end end end end end
fatalisState to 2 the player will no longer be instantly murdered by damage from an enemy, as it must equal 1 in order for that to happen. The display logic will then detect the state has been changed to 2 and will make an announcement saying that the player is cured before finally setting it to 0.
As it stands, the duration of the Fatalis debuff (essentially defined by the
fatalisTimer interval) is hardcoded at 10 minutes. I will probably make this a value that can be changed more easily from code in the future.
And then here is our updated cleanup code.
Omnified Cleanup Code – Fatalis Timers
function unregisterOmnification() if registered == true and disableInfo ~= nil then local disabled = autoAssemble(omnifiedDisableAssembly, disableInfo) if disabled == false then print("Failed to uninject.") else registered = false if statusTimer ~= nil then if fatalisTimer ~= nil then fatalisTimer.Enabled = false fatalisTimer.Destroy() end statusTimer.Enabled = false statusTimer.Destroy() statusTimer = nil end end else print("Nothing to unregister.") end end
There we have it. This is where all of our status timers will probably go, albeit I’ll probably add a level of organization to it that pleases me first. I’m still learning LUA, so bear with me until I have it mastered!
We’re almost done. We now need to update the display library found in OmnifiedDisplay.lua to inform the audience about the changes and effects of the Fatalis affliction.
Omnified Display LUA Changes
I haven’t published much about the internal workings of OmnifiedDisplay.lua before, mainly because it will probably get a very proper refactoring along the time I have completed work on my Vision product (an Omnified technology in the form of comprehensive screen overlay software that displays all the extracted data pertinent to the Omnified experience).
But, since we’re writing about adding the Fatalis debuff, I’ll just include the sections that need to be modified. First, we need to be able to detect and tell the audience when the player has just been one-shot due to Fatalis.
Apocalypse Log Display Code – Fatalis Death
if logApocalypse == 1 and apocalypseResult ~= nil and lastDamageToPlayer ~= nil and playerHealth ~= nil and timestamp ~= nil and extraDamageX ~= nil and fatalisResult ~= nil and fatalisResultUpper ~= nil and fatalisState ~= nil then if fatalisState == 1 and fatalisResult == 0 then log:write(timestamp, "The player, afflicted with Fatalis, dies immediately from the enemy attack!\n") playSound(findTableFile('fatalisDeath.wav')) else -- The normal display logic follows. local apocalypseEnemyRoll = string.format("%s%s %i: ", timestamp, logEntryEnemyRoll, apocalypseResult)
fatalisState is equal to 1 (indicating we have Fatalis) but
fatalisResult is not 0, that means we just became afflicted with the status malady as the result of an Apocalypse roll. In order for
fatalisState to become set to 1, a
fatalisResult must have been rolled equal to the maximum possible number. We actually don’t want to kill the player with the same blow that gave them the Fatalis, we want the next attack to do it.
fatalisState is 1, no more Apocalypse rolls happens, only instant death. That means the onus on resetting
fatalisResult to 0 is on us, outside of the assembly, and the perfect place to do it is when announcing the player has become afflicted.
Apocalypse Log Display Code – Fatalis Affliction
if riskOfMurderResult <= 3 then log:write(riskOfMurderEnemyRoll, "WHEW! Just normal damage causing ", apocalypseDamagedHealth) if fatalisResult == fatalisResultUpper then log:write(timestamp, "Unfortunately the player has been stricken with Fatalis for the next ten minutes...\n") playSound(findTableFile('fatalisAfflication.wav')) writeInteger("fatalisResult",0) end else -- The remaining possible effects are logged below.
Remember, Fatalis affliction occurs when we get normal damage from a Risk of Murder roll. So, that’s where we insert the logic to announce that the player has acquired it. As you’ll notice in this example and the one above, we not only log the event to the display log, but also play some sounds for the event as well. Sounds that I think will be thought as quite funny, I hope!
The remaining thing to do is to announce the player being cured from Fatalis. This actually occurs outside of this particular section of code (which only goes off when the player has been damaged), and is in fact right above it, and will execute independently from anything else.
Apocalypse Log Display Code – Fatalis Cured
if fatalisState == 2 then log:write(timestamp, "Miraculously, the player has been cured of Fatalis!") playSound(findTableFile('fatalisCured.wav')) writeInteger("fatalisState", 0) end
Very simple. We detect the cured state by check for a value of 2, and then reset the state to 0 once we announce the event via text and audio.
That’s It! Looking Forward to the Fun.
The Fatalis debuff has come alive.
To see the code in its entirety, know that I will soon relocate my “hackspace” to a source control folder, and all that work will start showing up on my Bad Echo source repo. I’ll also be updating the original Apocalypse article to include these changes, sometime in the future.
As for what sounds I’m using for the new effects, you’ll have to tune in on my Twitch stream to find out! I’ll be showcasing it during my very next stream, so check out my current schedule to know when that will be. And, as always, feel free to join the strange Omni community on my Discord.
Until next time, thank you for your interest and your time! Have a good one.