The Predator system is a game-neutral Omnified process that makes movement of enemies deadlier through intelligent, and sometimes quite large, boosts to their speed. When implemented into a game, the existing movement system for NPCs will become subject to Predator system rules, making it the authority on NPC movement in general. The goal of the Predator system is to greatly increase difficulty through the manipulation of how enemies move.
Simply put, the Predator system works by adjusting the speed at which creatures move based on both their proximity to the player as well as the discerned intent behind said movement. In the end, we get a game with an overhauled enemy movement system where multiple enemies swarm towards the player at breakneck speed as they close in for the kill.
Want to skip all the words for now and get a quick primer on what the Predator system is all about? Well, you’re in luck, as I made a general overview video covering the Predator system a bit ago for your viewing enjoyment (of course, some finer details in the video may be a bit out of date):
The Predator system was the second formalized Omnified system that I made, the first being the Apocalypse system, which itself is concerned mainly with a game’s damage system. All aspects of the Predator system, from its usage to its inner workings, will be covered here in this article.
Yup, after a nice long while of promising that I’d write it, we’re finally getting a detailed analysis article for the Predator system! As time goes on, I’ll try to keep this up to date with any modifications I make to the system; always refer to the official source code repository for the Omnified framework for the latest changes and updates, of course.
Origins of the Predator System
Similar to how the Apocalypse system evolved from the earliest of my hacks (which made any enemy damage always lethal), the Predator system derives from the second class of hacks I started to employ on stream: namely, the classic speed hack. It was during my playthrough of Omnified Mass Effect 2 when I first started to toy with the idea of making enemies move faster in order to make the unfortunate player (me) die quicker.
The workings of these early enemy speed hacks were, naturally, both straightforward and primitive. They were all a type of hack I refer to as a dirty speed hack. In order to implement a dirty speed hack, we typically would take the following steps:
- Hook into the place in code where new coordinate values (resulting from some kind of movement) are being committed to the source-of-truth coordinates (i.e., the places in memory that, when manipulated, cause the character to actually move on-screen).
- Determine if the coordinates being updated belong to a player or an NPC, exiting out of the routine if coordinates are player related (we only want to boost enemy speed, not our own!).
- Calculate the difference between the new and old coordinates, yielding the change in coordinate values.
- Multiply the calculated change by a configured speed multiplier value.
- Add the updated change amount to a copy of the original coordinates.
- Finally, replace the game-provided “new coordinates” with the values calculated in the previous step.
This more or less achieved the desired effect. When using a speed multiplier of two, the enemies in the game would move twice as fast. However, it should not surprise you to hear, that a primitive speed hack such as this introduces a whole host of problems. One would expect that a great many games have quite a bit of design based on assumptions made in regards to how fast creatures move, and that rough increases to movement speed may cause things to no longer work so perfectly.
Well, you would be correct. From enemies getting stuck trying to navigate through narrow corridors, to a sudden large inaccuracy in their charging attacks (missing the player entirely, or going way past them), it was quickly observed that simple boosts to speed brought about an undesirable level of instability. This is fine when it is funny, but if it inadvertently makes things easier (like when attacks start to miss the player), why that is unacceptable!
The Predator system came about as a means to address these problems, while still providing a creative and very effective boost in difficulty for games via the manipulation of creature locomotion. It is game-neutral, meaning it has been designed to work with any game, provided it is 3D (though I suppose I can make it support 2D easily enough, in fact it actually used to only support two dimensions).
Basic Mechanics of the Predator System
The Predator system’s primary function is to take the movement offsets of a creature currently in the middle of moving and then return updated offsets to be used instead. A movement offset represents the amount of movement occurring on a particular axis.
For example, given the offsets (0.5, 1.5, 0.3) and location coordinates (1921, 29, 109), applying said offsets would result in the coordinates for the creature shifting to (1921.5, 30.5, 109.3).
The Predator system changes those offsets in a manner that results in deadly and ever-increasing speed of enemies towards the player, without causing all of the negative and undesirable side-effects that are born of the chaos produced from a crudely applied constant speed increase. Speed increases are done only when appropriate and in magnitudes most appropriate for where movement is taking place.
The goal of the Predator system is to boost enemy speed as they move towards the player, ramping up the speed as they close in, in order to produce a terrifying and off-putting charge to the player to deliver death. Instead of simply boosting any and all movement speed by a set amount for enemies, the resulting speed multiplier is shaped through the consideration of multiple factors, such as:
- Proximity to player
- Direction of movement
- Intention of the enemy, as best as we can discern
All of these factors come into play and are evaluated through the use of location-based discriminators that are one of the cornerstones of the Predator system itself, and are key to understanding how this Omnified intelligent speed system works.
Areas of Influence
Specifically determining the intention of a moving creature violates the game-neutral approach of the Omnified methodology, so we instead make use of location-based discriminators devised by myself known as the areas of influence.
The areas of influence are a collection of different regions surrounding the player, each of which has different effects being applied to the movement speed of enemies as well as different conditions that must be met for those effects to be realized. Simply put, however, the closer an advancing enemy is to the player, the greater a boost they receive to their movement.
There are four different areas of influence, and they are, from the furthest away to the closest: indifference, sketchiness, aggro, and threat. Movement speed is treated very differently in each area, and we shall be going over each one in detail in this section. First, however, it’s important to understand how we go about determining the particular area that a creature is in.
Distance Calculation
The particular area that a creature is assigned to is wholly determined by the distance of the creature from the player. We determine this by applying the relatively elementary geometric equation shown above; elementary, perhaps, yet non-trivial, as we needed to implement it in assembly (!!).
The distance calculation is implemented as a discrete, independent function, which quite conveniently allows us to consume it outside of the Predator system. The nature of this function’s implementation as well as how it can be consumed will be covered later on in this article in the API section for the Predator system.
Area of Indifference
The area of indifference is the furthest area away from the player. It’s named such because enemies who are this far away from the player are most likely as indifferent to the player as we are indifferent to them; they’re too far away to be any kind of threat.
Here are its specifics:
The enemy is within the area of indifference if the distance between them and the player is greater than or equal to the value of the external parameter aggroDistance
multiplied by the value of the external parameter indifferenceDistanceX
. An external parameter is a variable with a default value that we can override in a game-targeting hack file. The parameters in question here, aggroDistance
and indifferenceDistanceX
, define the distance between the player and boundary of the area of influence known as the area of aggro and the distance between that area and the area of indifference respectively.
Given that the default value for indifferenceDistanceX
is 2.0, we can say that, according to default values at least, that the enemy is within the area of indifference if the distance between them and the player is greater than or equal to double the value of the parameter aggroDistance
.
In the area of indifference, enemies simply have a base constant speed boost applied to them, the value of which is found in the external parameter enemySpeedX
. The speed boost being applied here is primarily for cosmetic (or even humorous) effect, as I quite enjoy seeing groups of enemies in the distance meandering about in a slightly faster-than-normal fashion.
That being said, if we’re ever in a situation where you have incoming waves of enemies that start from very far away, the speed boost applied to enemies while in the area of indifference can easily result in the swarming of the player by these waves. This is very much a desired outcome and testament to how the Predator system effectively increases difficulty in a variety of ways; it is also always quite hilarious to see on stream.
Note that any mention of ‘enemySpeedX’ in this section isn’t quite enemySpeedX…
When you see enemySpeedX
in this general overview of the areas, please note that we’re talking about the scaled enemySpeedX
, which takes into account the size of the creature for games that have both true scaling and my Abomnification system implemented. For the majority of games, this will be equivalent to just whatever enemySpeedX
is.
More information can be found on this in a later section.
Area of Sketchiness
The area of sketchiness is one area closer to the player from the area of indifference. Enemies found within this area are close enough to be deemed sketchy; they may have noticed our player and are seeking to engage, or they may have not. Further indication of enemy intent is determined by the direction the enemy is moving.
Here are its specifics:
The enemy is within the area of sketchiness if their distance from the player is between the value of aggroDistance
and this same value, multiplied by indifferenceDistanceX
. This is logical, as the area of aggro ends at aggroDistance
and the area of indifference begins at that value multiplied by indifferenceDistanceX
.
The mechanic we then begin to see come into play with the area of sketchiness is the notion of directionality. If the enemy is not moving towards the player, then the same constant speed boost (enemySpeedX
) is applied to them as if they were in the area of indifference. If the enemy is moving towards the player, however, then an increased speed boost starts to be applied. This increased speed boost is effectively the base speed boost (enemySpeedX
), multiplied by the successive area boost multiplier of 2.0, multiplied by the aggression speed multiplier (aggressionSpeedX
).
These various external parameters will be documented at length in the API section, however, going by their default values at least, we can expect to see the speed of an enemy doubling if they’re moving towards us (from the speed exhibited when not moving towards us) while they lie within the area of sketchiness.
The change in behavior that we see with enemies within the area of sketchiness goes hand in hand with our intention for the speed of enemies to be ever increasing as they seem to be closing in to our player. Once creatures cross into the next area of influence, however, we’ll want to both increase the speed to even greater levels as well as start to enact some finer-grain control over their movements.
Area of Aggro
The area of aggro is the area of influence that is close enough to the player for us to be able to assume an attack is impending. It is here where the creature’s speed will become elevated high enough so that the creature will come at us at shocking speed, which usually does the job of catching us off guard and giving us a bit of scare each time it happens!
Here are the important details for this area:
This is the area where exciting stuff happens. There are some important differences with this area that distinguish it from other areas.
First, if the enemy is moving towards the player, then the boost becomes effectively doubled from the boost provided to the enemy while they were in the area of sketchiness. This is typically going to result in a very, very fast-moving creature that is really going to catch us off guard and usually cause me to scream on stream.
The other important difference is what happens if the enemy is not moving towards the player. When this occurs, all speed boosts are terminated, resulting in the enemy moving at its normal speed. This is to allow for the creature to be able to exert fine-grained control over its movements in the event that it needs to, such as needing to turn around a corner that is very close to the player.
If we force a ridiculous speed boost on the enemy, now close to the player, regardless of its direction of movement, then we will most likely observe the enemy’s inability to correct itself if it ever needs to get aligned with the character before closing in for the kill. It’s only when the enemy has a clear and direct path to the player that we want to inject a healthy dose of ludicrous speed to its movements.
Note that the “stripping of movement speed” is achieved by setting the effective speed boost to the defaultSpeedX
external parameter, which by default has a value of 1.0. So, if we wish to forego (or even make more severe) the movement speed reduction, this parameter can be easily overridden in a game-targeting hack file.
Area of Threat
This is the area closest to the player among the various areas of influence. It is also one of the simplest, yet most important of the areas. The area of threat can be visualized as the area surrounding the player where he or she is within range for a melee attack by an enemy. If we’re talking about a game with swordplay, then the area of threat should extend to the furthest distance an enemy can stand and still be able to reach the player with a deft sword stroke.
Here are the details for this area:
If the enemy is within the area of threat, all speed buffs are immediately stopped, regardless of circumstance. This is essential, as it corrects one of the primary issues brought about by primitive, constant speed buffage: a sharp increase in the inaccuracy of an enemy’s attacks; in particular, a loss of accuracy with attacks that exhibit some sort of movement, such as a lunge or a leap.
This “speed boost dead zone” is one of the strongest parts of the Predator system, as it allows ludicrous speed without sacrificing attack accuracy. The external parameter that controls the size of the area of threat, threatDistance
, is independent from the other area sizing parameters and will be covered in greater detail in the API section later on in the article.
True Scaling Speed Support
The Predator system supports scaling the base applied speed boost based on the size of the creature being boosted for games that support true scaling. True scaling is when the speed of creatures is directly affected by the size of their own bodies.
For example, if we take two creatures of the same body type, but one three times as large as the other, we can expect the larger creature to move roughly three times as fast given that their walking stride is that much larger. This difference in movement speed is very appropriate given the differences in their sizes.
Very few games actually support this, and indeed the Predator system does not implement this functionality on its own; rather, it provides support for it in the event that it is present. It becomes critical that we do provide support when we need to go about boosting the speed of an abnormally sized creature: a creature that is three times as large having its speed tripled will actually result in an effective speed boost of nine times — this will result in the creature covering ridiculous distances that we would be hard pressed to keep up with.
The need for scaling the base speed boost multiplier becomes further exacerbated when we have support for the Abomnification system implemented, which is another Omnified gaming system that causes enemies to morph and take on randomly determined shapes and sizes.
If a game does support true scaling, then we ensure that we take the creature’s size into account by having their dimensional scaling multipliers provided to the Predator system, where they will be inputted into the following function to produce the scaled base speed boost multiplier:
We’re essentially taking the base speed boost, enemySpeedX
, and dividing it by the average scale of the creature. This is going to result in enemies that are, let’s say, double in size, receiving half the speed boost. This should then even out to essentially the same distance covered that a normally-sized creature would exhibit with the full speed boost applied.
Again, all of this only comes into play for games that actually support true scaling, which are definitely in the minority. These will typically be games that support creatures of the same type with different size characteristics that are determined at the time of creation. Monster Hunter: World is an example of a game like this.
For the most part, we will not scale the boost enemy speed multiplier, and this is done by providing an identity matrix for the creature’s scaling multipliers as is described in the API section.
Speed Limiting
A final feature offered by the Predator system exists as a means to prevent things from getting out of hand if the speed of an enemy is getting out of control. Prior to the Predator system returning the newly calculated movement offsets, these offsets are compared against configured speed limits to see if the creature is moving too fast.
If the creature is indeed moving too fast, the particular offending offset is set to a speed limit corrective value, which will either be the same as the limit value, or even a value of zero, depending on the game.
This system of speed limiting is only required for games where the speed of a creature’s movement influences its movement in the next frame. For the majority of games, the speed at which a creature moves is, for the most part, independent of any previous change affected to its coordinates.
This is not the case for some games, however, like the most recent games released by FromSoftware, such as Sekiro: Shadows Die Twice and Dark Souls III. Boosting movement speed with these games can result in an unpredictable surge in speed causing enemies to essentially dematerialize as they’re thrown off into the abyss.
The Predator system’s speed limiting system helps prevent this without causing too much of an interruption with the normal flow of enhanced movement. The actual speed limits are defined with the values that the positiveLimit
and negativeLimit
external parameters are set to. Additionally, the corrective values can be found by looking at the positiveLimitCorrection
and negativeLimitCorrection
external parameters.
A more detailed explanation for these external parameters and others can be found in the upcoming section where we’ll be discussing the API of the Predator system.
Predator System API
Integration of the Predator system and its subsystems require proper construction of an initiation point that calls into the main Predator system function when the game is processing movement offsets that are about to be applied onto new coordinates for entities. This section serves to list all executable functions provided by the Predator system, the parameters they require, and other important bits of information certain to be useful if correct operation is desired.
We will also cover all the external parameters supported and used by the Predator system. These external parameters allow for extensive customization of how the Predator system behaves in a game-targeting hack file.
The physical makeup and organization of the functions about to be described in this section are covered in the next section that goes over the actual code involved with the Predator system.
Distance Calculation Function
The distance between the player and another entity can be calculated by calling the findCoordinateDistance
function. This is a useful function that can be applied to a wide variety of situations, but it is used most prominently by the main Predator system function in determining which area of influence a creature is currently in.
Parameters
- Player’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z coordinates as floats
- This is the complete set of coordinates representing the player’s current position.
- The coordinates are meant to be provided as if an SSE register containing the x, y, and z coordinates is being directly dumped onto the stack. Please see the code section for examples.
- Enemy’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z coordinates as floats
- This is the complete set of coordinates representing the enemy’s current position.
- The coordinates are meant to be provided as if an SSE register containing the x, y, and z coordinates is being directly dumped onto the stack. Please see the code section for examples.
Return Values
eax
: Set to the distance between the player and enemy coordinates.
Scaled Speed Calculation Function
If the game supports true scaling, the calculateScaledSpeed
function will return a modified base speed boost multiplier that is consistent with the provided size of the creature.
If true scaling is not implemented in the targeted game, then the the base speed boost multiplier returned will essentially be the same as it was prior to the function call, assuming an identity matrix of values is provided as the parameter.
Parameters
- Enemy’s Dimensional Scales
- Data Type: 128-bit wide dump of height, width, and depth scales as floats
- This is the complete set of dimensional scaling multipliers being applied to the the enemy’s height, width, and depth respectively.
- The scaling multipliers are meant to be provided as if an SSE register containing the height, width, and depth multipliers is being directly dumped onto the stack. Please see the code section for examples.
- If true scaling is not implemented, an identity matrix (consisting of all 1.0 values) should be provided instead.
Return Values
eax
: The calculated scaled speed multiplier.
The base speed boost multiplier used in this function’s calculations is the value of the enemySpeedX
external parameter.
Movement Directionality Function
The directionality of an enemy’s movement, and whether said enemy is moving towards the player, is determined by calling the isMovingTowards
function. This function can certainly be useful outside of the Predator system, however its use is certainly critical in the determination of the various movement speed bonuses that are applied in the areas of sketchiness and aggro.
Parameters
- Player’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z coordinates as floats
- This is the complete set of coordinates representing the player’s current position.
- The coordinates are meant to be provided as if an SSE register containing the x, y, and z coordinates is being directly dumped onto the stack. Please see the code section for examples.
- Enemy’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z coordinates as floats
- This is the complete set of coordinates representing the enemy’s current position.
- The coordinates are meant to be provided as if an SSE register containing the x, y, and z coordinates is being directly dumped onto the stack. Please see the code section for examples.
- Changes to Enemy’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z axes movement offsets as floats
- This is the complete set of movement offsets to be applied to the enemy’s current coordinates.
- The movement offsets are meant to be provided as if an SSE register containing the x, y, and z axes movement offsets is being directly dumped onto the stack. Please see the code section for examples.
Return Values
eax
: 1 if the enemy is moving towards the player; otherwise, 0.
Main Predator System Function
Predator system functionality is initiated by calling the executePredator
function. It must be called whenever movement offsets are about to be applied to a working set of coordinates that will eventually be propagated to the entity’s source-of-truth coordinate values.
Parameters
- Player’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z coordinates as floats
- This is the complete set of coordinates representing the player’s current position.
- The coordinates are meant to be provided as if an SSE register containing the x, y, and z coordinates is being directly dumped onto the stack. Please see the code section for examples.
- Enemy’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z coordinates as floats
- This is the complete set of coordinates representing the enemy’s current position.
- The coordinates are meant to be provided as if an SSE register containing the x, y, and z coordinates is being directly dumped onto the stack. Please see the code section for examples.
- Enemy’s Dimensional Scales
- Data Type: 128-bit wide dump of height, width, and depth scales as floats
- This is the complete set of dimensional scaling multipliers being applied to the the enemy’s height, width, and depth respectively.
- The scaling multipliers are meant to be provided as if an SSE register containing the height, width, and depth multipliers is being directly dumped onto the stack. Please see the code section for examples.
- If true scaling is not implemented, an identity matrix (consisting of all 1.0 values) should be provided instead.
- Changes to Enemy’s Coordinates
- Data Type: 128-bit wide dump of x, y, and z axes movement offsets as floats
- This is the complete set of movement offsets to be applied to the enemy’s current coordinates.
- The movement offsets are meant to be provided as if an SSE register containing the x, y, and z axes movement offsets is being directly dumped onto the stack. Please see the code section for examples.
Return Values
eax
: The updated movement offset for the enemy’s x-coordinate.ebx
: The updated movement offset for the enemy’s y-coordinate.ecx
: The updated movement offset for the enemy’s z-coordinate.
Initiation Point Implementation
The location of an Omnified process’s initiation point is critical in the successful operation of all Omnified game-neutral systems, and the Predator system is no exception. The initiation point for this system needs to be injected right into the game’s movement application code where movement offsets are being applied to working memory holding the creature’s coordinate values.
By using the term “working memory”, I’m referring to memory that is being used to temporarily hold a value that is going to be committed to a more permanent place in memory later on. In the context of the Predator system, this more permanent place in memory being spoken of is the place in memory holding the source-of-truth coordinate values. The particular piece of code that commits calculated values to the source-of-truth coordinate values is known as the location update code.
The location update code, where we see source-of-truth coordinate values being updated to new values, is always a very easy thing to find — indeed, we just need to reverse engineer source-of-truth coordinate values for the player, and then we can use Cheat Engine to see what code is writing to these places in memory. Finding the movement application code, however, is much more difficult, as it sometimes occurs tens of thousands instructions apart from the location update code.
It is extremely important (99% of the time) that the initiation point be injected into the movement application code and not the location update code. Failure to do so when implementing the Predator system can cause horrific results: creatures my phase out of existence, or end up just bouncing all over the place.
This is all because there is a lot of physics-related code running in games these days, and this auxiliary code needs to be aware of where a creature is moving to — a simple dirty speed hack, written in place of a location update, can result in supplementary code working off the old movement offsets instead of the new, actual offsets, leading to disaster.
How to find the movement application code?
Finding the movement application code requires the reverse engineer to start at the location update code (which, again, is easily found), and then working your way backwards. This is done by hooking into parent function calls found on the stack at the time the location update code is being executed, and then performing execution traces to see what exactly is going on between the parents and the location update code.
This is sometimes a medium-difficulty activity, and sometimes a very difficult activity, especially if the parent functions in question are common, generic pieces of code that are called for all kinds of things.
To learn about the skills and techniques that I’ve employed in the past in order to find the movement application code, I’d recommend checking out Predator implementation articles where I document exactly how I wired up the Predator system for a particular game.
I’m having difficulties finding the movement application code…
Well, you might need to try harder then. But, fret not, as it is not always required that you find the movement application code. You might be able to get by if you implement the Predator system at the place of the location update code — keep in mind, however, that I have found the movement application code to be, by far, the superior point of implementation as far as stability is concerned, and that for many games, required.
I’ve observed that the more modern a game is, the less likely you are to get away with implementing the Predator’s initiation point at the place of location updates. Still, nothing is ever set in stone, and I was surprised to see when hacking Predator into The Witcher 3 that I was able to get away with writing it into the location update code of NPCs.
Also be advised, that sometimes games will have entirely separate movement systems dedicated to NPCs versus the player. That adds another mini-mountain of difficulty in finding the appropriate place to hook in.
Alright! I found the movement application code…now what?
Calling the executePredator
function will replace the values stored in the eax
, ebx
, and ecx
registers with updated movement offsets. Hopefully you backed up those registers before calling into the Predator system!
Assuming you did, you’ll want to take these new movement offsets and replace the original movements offsets in memory (wherever they were being sourced from) with them. After that, you’re done my friend!
External Parameters
The Predator system, like the other Omnified systems, have a number of configurable parameters that we can modify in order to fine tune the system’s implementation into a particular game. To change the value of these parameters, we can simply redefine the value for the parameter somewhere inside of our game-targeting hack file, like so:
Example of Changing Parameter Value
nameOfExternalParameter: dd (float)15.0
As you can see, the above code is changing the value of this fictitious parameter from whatever its default value was to a new value of 15.0.
Let’s now go over all of the external parameters made available by the Predator system.
enemySpeedX
- Data Type: float
- Default: 1.25
- This is the base speed boost multiplier applied to NPCs that lie within the various areas of influence extending from the area of threat. Except for conditions in certain areas that result in no speed buffs being applied, this acts as the minimum speed increase applied to all enemies. For the areas where a compound speed buff is applied, this is one of the constituent parts that form the final calculated speed buff that is applied.
aggroDistance
- Data Type: float
- Default: 10.0
- This defines the distance between the player and boundary of the area of aggro. It uses the same units of measurement used in the coordinate values of the player and other NPCs.
- Given that these units vary in terms of actual observed distance between games, this value will very likely need to be overridden in a particular game-targeting hack file.
threatDistance
- Data Type: float
- Default: 2.5
- This defines the distance between the player and boundary of the area of threat. Like
aggroDistance
, it uses the same units of measurement used in the coordinate values of the player and other NPCs. It is an independent setting fromaggroDistance
, however it should always be smaller than the area of aggro if normal Predator functionality is desired. - Given that these units vary in terms of actual observed distance between games, and because the distance enemies can be from the player before engaging in a melee attack will also differ between games, this value will very likely need to be overridden in a particular game-targeting hack file.
yIsVertical
- Data Type: 4 bytes
- Default: 1
- This is actually a global external parameter, and changing it may affect other Omnified systems that are also implemented.
- Setting this option to 1 will result in no speed buffs being applied to anything associated with the y-axis.
- We often only want to apply speed buffs to creature locomotion that is horizontal in nature — boosting vertical speed often results in undesirable effects such as inaccurate leaps and falls that cause the creature to plunge deep under the ground.
- For many games, the y-axis acts as the vertical plane in coordinate space. If we are targeting such a game, then this parameter should be left set to 1. If the y-axis for the game we’re targeting does not represent the vertical location of a creature, then this should instead be set to 0.
- If this setting is set to 0, it will then be assumed that the z-axis acts as the vertical plane in coordinate space, and movement offset boosts will no longer be applied to that axis.
aggressionSpeedX
- Data Type: float
- Default: 1.0
- This is the aggression speed multiplier, and it controls how much of a boost is received as an enemy crosses over successive areas of influence towards the player.
- If left at its default value of 1.0, then the boosts received as an enemy nears the players shall be received in full. If there is a need to temper the amount of boosting that is occurring, then this parameter can be reduced so that only a fractional increase to the applied speed boost occurs.
- This can be useful for games where perhaps the effect of speed increases on actual physical movement is not exactly linear, or if the normal amount of boosting results in undesirable effects.
defaultSpeedX
- Data Type: float
- Default: 1.0
- This is the default speed multiplier that will be applied to movement offsets in the event that all Predator speed boosts are meant to be disabled.
- For example, this is the multiplier used when enemies are not moving towards the player inside the area of aggro, as well when an enemy is within the area of threat.
- Proper Predator system functionality relies on this being set to 1.0, however it is exposed as an external parameter if for some reason we wish to perhaps further retard the speed of movement when an enemy is operating within a no-speed-zone. Increasing this value from 1.0 is not recommended.
positiveLimit
- Data Type: float
- Default: 15.0
- This is the maximum movement offset in a positive direction allowed by the Predator system following applied speed boosts.
- If an offset value on a particular axis exceeds this value, then a corrective mode shall be engaged and the new offset value will be set to
positiveLimitCorrection
. - Many games do not require the system of speed limiting, and this setting can safely be left alone when targeting one of them, as the default value for this is set to a distance far greater than the amount of movement you’ll ever see a creature make in a single frame.
- If a game does require speed limiting however, this value may require tweaking in order to effectively distinguish between normal and abnormal movement.
negativeLimit
- Data Type: float
- Default: -15.0
- This is the maximum movement offset in a negative direction allowed by the Predator system following applied speed boosts.
- If an offset value on a particular axis exceeds this value, then a corrective mode shall be engaged and the new offset value will be set to
negativeLimitCorrection
. - Many games do not require the system of speed limiting, and this setting can safely be left alone when targeting one of them, as the default value for this is set to a distance far greater than the amount of movement you’ll ever see a creature make in a single frame.
- If a game does require speed limiting however, this value may require tweaking in order to effectively distinguish between normal and abnormal movement.
positiveLimitCorrection
- Data Type: float
- Default: 0.0
- This is the corrective value used for movement offsets that exceed the limit set in
positiveLimit
. - The default value of 0.0 is used in order to abruptly kill all abnormal movement in the hope that normal movement will resume thereafter. If a more staggered approach is desired in the limiting of abnormal movement, then this can be tweaked to a value (that should be substantially) lower than that of
positiveLimit
.
negativeLimitCorrection
- Data Type: float
- Default: 0.0
- This is the corrective value used for movement offsets that exceed the limit set in
negativeLimit
. - The default value of 0.0 is used in order to abruptly kill all abnormal movement in the hope that normal movement will resume thereafter. If a more staggered approach is desired in the limiting of abnormal movement, then this can be tweaked to a value (that should be substantially) greater than that of
negativeLimit
.
Predator System Code Analysis
As is tradition in these Omnified design articles, we will now be taking a look at the actual code involved with the Predator system. Please be advised that any bit of code should never be regarded as permanent, and that the most up-to-date version of Predator system code can be found in the official Omnified hacking framework source repository.
Distance Calculation Function Code
The distance calculation function takes the two sets of coordinates provided to it, finds the differences between each, squares the differences, adds them all together, and then takes the square root of it all. This yields the distance.
// Determines the distance between the player and another creature. // This function's parameters are most easily loaded onto the stack // from an SSE register: the player's coordinates first, and then the enemy's. // [rsp+28]: Enemy z-coordinate value // [rsp+30]: Enemy x-coordinate value // [rsp+34]: Enemy y-coordinate value // [rsp+38]: Player z-coordinate value // [rsp+40]: Player x-coordinate value // [rsp+44]: Player y-coordinate value // Distance is in EAX alloc(findCoordinateDistance,$1000) registersymbol(findCoordinateDistance) findCoordinateDistance: sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 // Load enemy's coordinates into xmm0, and player's into xmm1. movups xmm0,[rsp+28] movups xmm1,[rsp+38] // Take the difference between the two sets of coordinates. subps xmm0,xmm1 // Square the differences. mulps xmm0,xmm0 // Then we proceed to add the various squared differences to each other. movdqu xmm1,xmm0 // Shuffle the y-coordinate results to the lowest doubleword for adding to the // running total. shufps xmm1,xmm0,0xB addss xmm0,xmm1 // Shuffle the x-coordinate results to the lowest doubleword for adding to the // running total. shufps xmm1,xmm1,0x1 addss xmm0,xmm1 // Take the square root of everything, and we have our distance! sqrtss xmm0,xmm0 movd eax,xmm0 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 ret 20
Scaled Speed Calculation Function Code
This bit of code takes the average of the provided enemy dimensional scales and then divides the base speed boost multipler (enemySpeedX
) by it.
// Calculates the scaled base speed. // [rsp+28]: Enemy depth scale // [rsp+30]: Enemy width scale // [rsp+34]: Enemy height scale // Scaled speed is in EAX. alloc(calculateScaledSpeed,$1000) alloc(averageScaleDivisor,8) registersymbol(calculateScaledSpeed) calculateScaledSpeed: sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 // Find average of the three scales. movss xmm0,[rsp+28] movss xmm1,[rsp+30] addss xmm0,xmm1 movss xmm1,[rsp+34] addss xmm0,xmm1 divss xmm0,[averageScaleDivisor] // Load the base enemy speed boost and divide it by the average scale. movss xmm1,[enemySpeedX] divss xmm1,xmm0 movd eax,xmm1 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 ret 10 averageScaleDivisor: dd (float)3.0
Movement Directionality Function Code
This is one of the most complicated bits of assembly code presented here — indeed, determining whether or not something is moving towards something else was a bit of a new problem for me and one I hope I managed to solve well.
In fact, I do believe I solved it well, and here is the code for your benefit.
// Determines if enemy is moving towards the player. // [rsp+38]: Change to enemy's z-coordinate value // [rsp+40]: Change to enemy's x-coordinate value // [rsp+44]: Change to enemy's y-coordinate value // [rsp+48]: Enemy's z-coordinate value // [rsp+50]: Enemy's x-coordinate value // [rsp+54]: Enemy's y-coordinate value // [rsp+58]: Player's z-coordinate value // [rsp+60]: Player's x-coordinate value // [rsp+64]: Player's y-coordinate value // EAX is 1 if enemy is moving towards player, otherwise 0. alloc(isMovingTowards,$1000) registersymbol(isMovingTowards) isMovingTowards: sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm2 // Load player's coordinates into xmm0, and enemy's into xmm1. movups xmm0,[rsp+58] movups xmm1,[rsp+48] // We take the difference between each set to find distances on each axis. subps xmm0,xmm1 // We square the results so that we aren't bothered by any notion of signs. mulps xmm0,xmm0 // Contents of xmm0 register: (zp-ze)^2 | 0 | (xp-xe)^2 | (yp-ye)^2 movups xmm1,xmm0 shufps xmm1,xmm0,0x87 // Contents of xmm1 register: (yp-ye)^2 | 0 | (zp-ze)^2 | (xp-xe)^2 cmpps xmm0,xmm1,6 // This compare instruction will create a bitmask telling us: // -If the z-coordinate distance is greater than the y-coordinate distance // -If the x-coordinate distance is greater than the z-coordinate distance // -If the y-coordinate distance is greater than the x-coordinate distance sub rsp,10 movups [rsp],xmm0 isYGreaterThanX: mov eax,[rsp+C] test eax,eax jne isZGreaterThanY je isXGreaterThanZ isZGreaterThanY: mov eax,[rsp] test eax,eax jne isZMovingTowards je isYMovingTowards isXGreaterThanZ: mov eax,[rsp+8] test eax,eax jne isXMovingTowards je isZMovingTowards isYMovingTowards: add rsp,10 xor rax,rax movups xmm0,[rsp+38] mulps xmm0,xmm0 sub rsp,10 movups [rsp],xmm0 movss xmm0,[rsp+C] // Is the change in Y greater than the change in X? ucomiss xmm0,[rsp+8] jb primaryChangeTooSmall // Is the change in Y greater than the change in Z? ucomiss xmm0,[rsp] jb primaryChangeTooSmall add rsp,10 movss xmm0,[rsp+64] subss xmm0,[rsp+54] movss xmm1,[rsp+44] jmp isChangeMovingTowards isXMovingTowards: add rsp,10 xor rax,rax movups xmm0,[rsp+38] mulps xmm0,xmm0 sub rsp,10 movups [rsp],xmm0 movss xmm0,[rsp+8] // Is the change in X greater than the change in Y? ucomiss xmm0,[rsp+C] jb primaryChangeTooSmall // Is the change in X greater than the change in Z? ucomiss xmm0,[rsp] jb primaryChangeTooSmall add rsp,10 movss xmm0,[rsp+60] subss xmm0,[rsp+50] movss xmm1,[rsp+40] jmp isChangeMovingTowards isZMovingTowards: add rsp,10 xor rax,rax movups xmm0,[rsp+38] mulps xmm0,xmm0 sub rsp,10 movups [rsp],xmm0 movss xmm0,[rsp] // Is the change in Z greater than the change in Y? ucomiss xmm0,[rsp+C] jb primaryChangeTooSmall // Is the change in Z greater than the change in X? ucomiss xmm0,[rsp+8] jb primaryChangeTooSmall add rsp,10 movss xmm0,[rsp+58] subss xmm0,[rsp+48] movss xmm1,[rsp+38] isChangeMovingTowards: movd eax,xmm0 shr eax,1F test eax,eax jne isChangeNegative movd eax,xmm1 shr eax,1F test eax,eax je confirmMovingTowards xor rax,rax jmp isMovingTowardsExit isChangeNegative: movd eax,xmm1 shr eax,1F test eax,eax jne confirmMovingTowards xor rax,rax jmp isMovingTowardsExit confirmMovingTowards: mov eax,1 jmp isMovingTowardsExit primaryChangeTooSmall: add rsp,10 isMovingTowardsExit: movdqu xmm2,[rsp] add rsp,10 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 ret 30
Main Predator System Function Code
The main Predator system function’s role is to evaluate all of the various rules discussed previously in this article and then apply an appropriate speed boost multiplier to an enemy’s movement offsets.
// Main Predator system function. // [rsp+98]: Change to enemy's z-coordinate value // [rsp+A0]: Change to enemy's x-coordinate value // [rsp+A4]: Change to enemy's y-coordinate value // [rsp+A8]: Enemy depth scale // [rsp+B0]: Enemy width scale // [rsp+B4]: Enemy height scale // [rsp+B8]: Enemy's z-coordinate value // [rsp+C0]: Enemy's x-coordinate value // [rsp+C4]: Enemy's y-coordinate value // [rsp+C8]: Player's z-coordinate value // [rsp+D0]: Player's x-coordinate value // [rsp+D4]: Player's y-coordinate value // EAX has updated change to enemy's x-coordinate value // EBX has updated change to enemy's y-coordinate value // ECX has updated change to enemy's z-coordinate value alloc(executePredator,$1000) alloc(indifferenceDistanceX,8) alloc(defaultSpeedX,8) alloc(areaBoostX,8) alloc(aggressionSpeedX,8) alloc(positiveLimit,8) alloc(negativeLimit,8) alloc(positiveLimitCorrection,8) alloc(negativeLimitCorrection,8) registersymbol(executePredator) registersymbol(positiveLimit) registersymbol(negativeLimit) registersymbol(defaultSpeedX) registersymbol(aggressionSpeedX) registersymbol(positiveLimitCorrection) registersymbol(negativeLimitCorrection) executePredator: sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm2 sub rsp,10 movdqu [rsp],xmm3 sub rsp,10 movdqu [rsp],xmm4 sub rsp,10 movdqu [rsp],xmm5 sub rsp,10 movdqu [rsp],xmm6 sub rsp,10 movdqu [rsp],xmm7 sub rsp,10 movdqu [rsp],xmm8 xorps xmm8,xmm8 // xmm8 will hold the speed buff multiplier to be applied. movss xmm8,[defaultSpeedX] // Load the player's coordinates into xmm0. movups xmm0,[rsp+C8] // Load the enemy's coordinates into xmm1. movups xmm1,[rsp+B8] // Load the enemy's scales into xmm2. movups xmm2,[rsp+A8] // Load the movement offsets into xmm3. movups xmm3,[rsp+98] // Submit the scales as a parameter for calculating the scaled speed. sub rsp,10 movdqu [rsp],xmm2 call calculateScaledSpeed // The scaled base speed boost will be held in xmm4 for the remainder // of this function's execution. movd xmm4,eax // Next we submit the player and enemy coordinates are parameters for // finding the distance between them. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 call findCoordinateDistance // The distance between enemy and player will be held in xmm5 for the // remainder of this function's execution. movd xmm5,eax // Is the enemy within the area of aggro? ucomiss xmm5,[aggroDistance] jbe areaOfAggro // If not, are we between the area of indifference and aggro? movss xmm7,[aggroDistance] mulss xmm7,[indifferenceDistanceX] ucomiss xmm5,xmm7 jb areaOfSketchiness areaOfIndifference: // If not, we're in the area of indifference. The scaled base speed boost // becomes the effective speed boost. movss xmm8,xmm4 jmp executePredatorExit areaOfSketchiness: // In the area of sketchiness, the scaled base speed boost is the effective // speed boost, unless the enemy is moving towards the player. movss xmm8,xmm4 sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm3 call isMovingTowards cmp eax,1 jne executePredatorExit mulss xmm8,[areaBoostX] mulss xmm8,[aggressionSpeedX] jmp executePredatorExit areaOfAggro: // If the enemy is in the area of threat, there is no speed boost applied. ucomiss xmm5,[threatDistance] jbe executePredatorExit // Otherwise, in the area of aggro, we either double the boost observed within // the area of sketchiness with the enemy moving towards the player, or we strip // all speed boosts if the enemy isn't moving towards the player. sub rsp,10 movdqu [rsp],xmm0 sub rsp,10 movdqu [rsp],xmm1 sub rsp,10 movdqu [rsp],xmm3 call isMovingTowards cmp eax,0 // If the enemy isn't moving towards the player, remember that we still have defaultSpeedX // loaded into xmm8, so exiting out now will result in no speed buff. je executePredatorExit movss xmm8,xmm4 mulss xmm8,[areaBoostX] mulss xmm8,[areaBoostX] mulss xmm8,[aggressionSpeedX] executePredatorExit: // No speed buff is applied to the axis set as the vertical axis. cmp [yIsVertical],0 je commitZChange mulss xmm3,xmm8 commitZChange: movd ecx,xmm3 shufps xmm3,xmm3,0x87 cmp [yIsVertical],1 je commitYChange mulss xmm3,xmm8 commitYChange: movd ebx,xmm3 shufps xmm3,xmm3,0x87 mulss xmm3,xmm8 movd eax,xmm3 // We now test whether x, y, or z offsets violate designated speed limits. push rax shr eax,1F test eax,eax pop rax jne isXLessThanNegativeLimit movd xmm2,eax ucomiss xmm2,[positiveLimit] jbe isYPastLimit mov eax,[positiveLimitCorrection] jmp isYPastLimit isXLessThanNegativeLimit: movd xmm2,eax ucomiss xmm2,[negativeLimit] ja isYPastLimit mov eax,[negativeLimitCorrection] isYPastLimit: cmp [yIsVertical],1 je isZPastLimit push rbx shr ebx,1F test ebx,ebx pop rbx jne isYLessThanNegativeLimit movd xmm2,ebx ucomiss xmm2,[positiveLimit] jbe isZPastLimit mov ebx,[positiveLimitCorrection] jmp isZPastLimit isYLessThanNegativeLimit: movd xmm2,ebx ucomiss xmm2,[negativeLimit] ja isZPastLimit mov ebx,[negativeLimitCorrection] isZPastLimit: cmp [yIsVertical],0 je executePredatorCleanup push rcx shr ecx,1F test ecx,ecx pop rcx jne isZLessThanNegativeLimit movd xmm2,ecx ucomiss xmm2,[positiveLimit] jbe executePredatorCleanup mov ecx,[positiveLimitCorrection] jmp executePredatorCleanup isZLessThanNegativeLimit: movd xmm2,ecx ucomiss xmm2,[negativeLimit] ja executePredatorCleanup mov ecx,[negativeLimitCorrection] executePredatorCleanup: movdqu xmm8,[rsp] add rsp,10 movdqu xmm7,[rsp] add rsp,10 movdqu xmm6,[rsp] add rsp,10 movdqu xmm5,[rsp] add rsp,10 movdqu xmm4,[rsp] add rsp,10 movdqu xmm3,[rsp] add rsp,10 movdqu xmm2,[rsp] add rsp,10 movdqu xmm1,[rsp] add rsp,10 movdqu xmm0,[rsp] add rsp,10 ret 40 defaultSpeedX: dd (float)1.0 indifferenceDistanceX: dd (float)2.0 areaBoostX: dd (float)2.0 positiveLimit: dd (float)15.0 negativeLimit: dd (float)-15.0 positiveLimitCorrection: dd (float)0 negativeLimitCorrection: dd (float)0 aggressionSpeedX: dd (float)1.0
I believe that this function’s code, as well as all of the preceding code, contains enough documentation within the code to not warrant additional commentary from myself in this article. As always, if you have any questions feel free to ask me here or on my stream.
Please refer to the Omnified hacking framework on the official Bad Echo technologies repository for the full and up-to-date copy of my code.
To see the insane gameplay that the Predator system affords us, check me out live on my Twitch stream at: https://twitch.tv/omni
If you want up to date notifications on when my stream is live, or whenever I’ve written something new, check out my Discord server at: https://discord.gg/omni
Thank you for your interest and your time. Take care!