-
Notifications
You must be signed in to change notification settings - Fork 5
Aptitude
Aptitude appears to be the name of the ability system of the game.
The abilities are implemented through the use of commands, and sequences of commands are described as chains. Within the SDB, the commands each have their own tables, for which the rows describe instances of uses of those commands with the associated parameters. The chains then link together the specific instances of commands.
The game widely makes use of abilities for many scripted actions. For example:
- An ability is triggered when activated by the player
- Pressing the interact key triggers an ability
- Honking the horn on a vehicle is an ability
- When a deployable is spawned, an ability is triggered to apply effects or animations during the creation and destruction of the deployable
- When certain items are equipped on a character, an ability is triggered to apply certain effects
Several tables in the database define parameters for instances of Aptitude commands (CommandDefs).
With our current understanding we can define the following:
- A command is an action to be taken by the client, server or both.
- A chain is an instance and sequence of one or more commands.
Please note we always have a chain for any instance of a command that should execute. This means that if a column is referencing a "chain id", it's referencing the instance of a command - the first command of that chain.
Two ways to trigger chains are:
- Activating an ability, triggering a predetermined chain linked through apt::AbilityData
- The application, update or removal of a status effect triggers chains tied to that status effect and action
This section is meant to be a quick example to give you an outline for approaching this.
These tables are central but keep in mind you also have tables for the commands themselves, with the params.
Indexed by Ability Id. Specifies the Chain triggered by that Ability.
Indexed by Status Effect Id. Specifies which Chain is triggered when the effect is applied, removed, updated, as well as the "duration" chain.
Indexed by Command/Chain Id. Specifies the Command Type (subtype) of that Command Id and optionally the next Command (Chain Id).
Indexed by Command Type Id (subtype in apt::BaseCommandDef). Tells you the name, table name and environment (client, server, both) of that type of command.
When you activate the Omnidyne LGV Calldown it triggers an ability with id 36379
.
Search for ability id 36379
in table apt::AbilityData
.
We see that the chain triggered by this ability is 1494705
.
First, we should determine the Chain, so look up 1494705
in table apt::BaseCommandDef
for the definition.
If the next
column has a non-zero value, include those rows as well to get the "chain" of commands.
Here's what we can determine:
next | subtype | id |
---|---|---|
0 | 102 | 1494703 |
1494703 | 9 | 1494704 |
1494704 | 1 | 1494705 |
Note that the specified chain id is the right most column.
By following the next
value we found two more commands.
The last line has next 0
, we may consider that this first chain that was triggered by the ability finishes at that line.
However, some of these commands could cause additional chains to execute.
If we look up the subtype/command type ids in table apt::CommandType
, we get their names and we can establish this sequence of commands (now in top down sequence for clarity):
chain/command id 1494705, subtype 1: apt::ActiveInitiationCommandDef
chain/command id 1494704, subtype 9: apt::TimeCooldownCommandDef
chain/command id 1494703, subtype 102: apt::ConditionalBranchCommandDef
Each command subtype has it's own table in the SDB where the parameters of each instance of that command is located.
In this case, let's look up the parameters for the ConditionalBranchCommand that is in this chain. Search for its id 1494703
to get a hit in table apt::ConditionalBranchCommandDef
:
if_chain | then_chain | else_chain | id |
---|---|---|---|
1494697 | 1494700 | 1494702 | 1494703 |
In this case, we can quickly understand that the ConditionalBranchCommand is a logic command that references additional chains. So although our initial chain ended with this command, there is still more for us to lookup here to gain a fuller understanding.
We can lookup each chain id specified here, to get hits in table apt::BaseCommandDef
and see what type they are, and hopefully also get results in the specific command tables for the parameters. Remember that in this command, these are referenced as chains, so you have to check the next column in table apt::BaseCommandDef
and unravel them.
if_chain:
1494697, subtype 143, aptfs::RequireHasEffectCommand
then_chain:
1494700, subtype 405, aptfs::AttemptToCalldownVehicleCommandDef
* 1494699, subtype 363, aptgss:agsMountVehicleCommandDef
1494698, subtype 3, apt:InstantActivationCommandDef
else_chain:
1494702, subtype 379, apt::ImpactApplyEffectCommandDef
1494701, subtype 3, apt:InstantActivationCommandDef
Note: The command 1494699
is of a subtype for which, to our current understanding, the parameters are not present in the clientdb.sdb
. This goes for the bulk of the aptgss
commands for which the environment is specified as "server" in table apt::CommandType
.
At this point, it's time to introduce status effects. Status effects effectively work as functions or miniature scripts, and the application and removal of an effect to an entity is relayed to the client over the network.
In our branch command, we have the RequireHasEffectCommand as the condition, and in the else chain, we have ImpactApplyEffectCommand.
These commands are both related to status effects.
The RequireHasEffectCommand with id 1494697
has the effect_id 788
and the negate 1
.
Because negate is 1
, this command could be said to return true if we do not have the effect on our character, and we proceed into the then_chain.
In the case where we do have the effect applied, we land in the else chain with the ImpactApplyEffectCommand.
This command applies a status effect. If we lookup the command in this instance, 1494702
, we find that it will apply the effect with id 11589
.
We can then look up the effect id 11589
in table apt::StatusEffectData
, to find that it specifies an apply_chain
with id 1102440
. This of course means an additional chain of commands that should execute when this effect is applied.
Maybe you were curious and looked up the effect 788
in apt::StatusEffectData
, only to see that it has no chains specified.
If you dig deeper you should find that in the dbvisualrecords::WaterDesc
table, there is a column called moving_restricted_char_status_effect_id
that references 788
.
If you follow through with reading these chain, you should find that the above logic is basically "If you are not in water, create a vehicle and mount it. Else, send a message about not being able to summon vehicles in the water.".
Here's some notes based on current understanding of the system. Please do note however that most of this is just guesswork based on a combination of the command names, the parameters, the chains of specific abilities and our understanding and knowledge of the game, as well as prototype simulation in test environments.
Command chains execute both on the local client and on the server side.
As noted earlier, in table apt::CommandType
there are three different environments specified: client
, server
, and both
.
Skimming this it's simple to see that most client
commands relate to various particle effects and animations. We can likely skip simulation of client
commands on the server side entirely, thanks to us communicating through status effects instead.
server
commands include parameter/stat and flag modifications, unlocks, encounters and spawning/creating of certain entities.
both
commands are a bit of a mix general actions and specific functions, including activation, targeting, conditions, logic, damage dealing. This also includes BullrushCommand, which is the charge ability, and ForcePush, which is a command that pushes the player in a direction.
A way to rationalize this: The client is supposed to be able to simulate and "predict" events before getting confirmation from the server. In order to be able to run prediction on the chains, this means the client needs to be able to do all the logic/conditions, targeting by itself, and physic interactions need to be defined so that it can perform them. We can be very thankful that a lot of parameters are available to us through the clients database.
It may be important to note that the server and client may not always perform/execute chains in parallel, specifically in regards to the activation commands for calldowns. The client does not communicate the attempt to activate a calldown where the player gets a blue marker to place it, until it is placed. But the client itself would already be handling that activation and would know to do this behavior through the specific commands used in those abilities.
The presence of certain logic commands makes it clear that the execution of a command, as well as the execution of a chain, should be treated as thought it has an outcome of either success or failure.
However, given the existence of commands like "UpdateAndWait" and ReturnCommand, there could be more states to consider as well.
The current understanding is that commands should "succeed", unless there is some explicitly specified way for them to fail. The execution of a chain should halt if one of its commands fail, unless that execution is occurring as the result of a logic command like OrChainCommand.
The commands that begin with "Require" appear to be general condition checking commands. It seems that the command should succeed if requirement is met, and fail if requirement is not met.
The commands that begin with "Target" appear to modify the "target" or "targets" of subsequent command and chain.
Some of these commands add new targets (e.g. TargetPBAECommand). Other filter the existing ones (e.g. TargetFriendliesCommand).
It is somewhat unclear which targets are supposed to exist in a chain from the beginning. There are many ways for commands to execute and it seems possible that they can carry certain contexts with them.
Also, some commands seem to refer to just a single target, where as other commands obviously imply multiple. It may be that target should be considered a stack.
It is somewhat unclear how the commands whose names include "Duration" should work. It seems they should succeed if there is duration remaining and fail otherwise.
For example, TimeDurationCommand has a param that specifies how long. When executed, it seems this command should fail if more time than the param has passed since the chain/effect was initiated.
Meanwhile, AirborneDurationCommand has no params. Presumably this should fail if the player is not airborne - as there would be no duration remaining.
The commands that begin with "Logic" seem to be used to execute additional chains and using the results of the chains to determine its actions.
LogicOrCommand specifies an a_chain and a b_chain. It seems to be correct to execute both a_chain and b_chain, and then take the OR result of the two chains to determine success or failure of the source command.
LogicOrChainCommand specifies just a chain. Here, it seems to be appropriate to execute the commands of the chain, treating the result of each command as part of the OR operation. If the first command succeeds, exit early. If it fails, execute the next one. The result of the final command is the result of the chain, e.g. any success turns into success and all failed is failed. If the parameter "always_success" is specified, then the result of the source command is success regardless of what happened during the execution of the chain commands.
LogicAndChainCommand, specifies just a chain. Execute the chain as per normal, but also implement the always_success param like above.
Register appears to refer to a single value existing within the execution context that can be modified and passed to subchains/effects.
"Regop" occurs frequently as parameters in commands, both in those related directly to Register and other commands like ForcePush (which has a strength_regop param).
Guess work is that the regop values themselves are:
0: Assignment
1: Addition/Subtraction
2: Multiplication
So in the case of the ForcePush command, the theory is that if the strength_regop is 1
, then the value of the register at that point is added to the strength param of that command.
This is just one way that the register is used. In the case of gliders for example, the register is loaded with the NamedVar value WingFX, such that the register gets different values depending on which glider is activated, and then a generic effect applies different particle effects depending on the value of the register.
NamedVars, appear to be something that should persist even outside of the initial execution context. For example, the ability activated as a result of the proximity command of a glider pad deployable should somehow have access to the correct WingFX value even though the value is not specified within that ability - meaning it perhaps should either have been persisted when the proximity command was registered, or it should be stored in relation to the deployable entity and be made available upon the execution of the ability triggered by it.
With the RegisterClientProximityCommand executed, the client will report "LocalProximityAbilitySuccess" (for local player) or "RemoteProximityAbilitySuccess" (other players) to the server if the proximity conditions specified in the command are fulfilled.
This is used with glider and jump pad deployables for example. Their spawn ability chain executes these commands and then the client will by itself report if a player comes near enough to them. The command params then specify an ability or chain that should run if the proximity success is triggered, causing an ability to be activated on the player if they walk close enough to the deployable.