DoDamage¶
DoDamage is the entrypoint of the damage pipeline. Any damage must go through it in order to be properly calculated and processed with visual effects and counters.
Entry point¶
There are 10 overloads, but all of them ends up at a particular one which is the following (it will be refered to as the final overload):
private int DoDamage(MainManager.BattleData? attacker, ref MainManager.BattleData target, int damageammount, AttackProperty? property, DamageOverride[] overrides, bool block)
It returns the amount of damage actually dealt.
Here are the parameters:
attacker
: The actor dealing the damage. If null, the damage didn't come from an attacker. If it exists, it must belong to the opposite party of thetarget
- ref
target
: The actor receiving the damage. Passed as ref to allow modifying the actor's data. If theattacker
exists, it must belong to its opposite party damageammount
: The base amount of damage to deal. This number can be heavilly changed by the damage pipeline, it is only the base one to dealproperty
: The property of the attackoverrides
: The overrides to apply to the damage pipeline. May be null or an empty array to have no overridesblock
: For an enemy attack, whether or not the player sucessfully blocked it (this is intended to receivecommandsuccess
as it was determined by GetBlock). It doesn't matter if it's a regular block or a super block, this parameter is to signal a block of any kind as the damage pipeline will process what to do with it
General purpose overloads¶
The following are general purpose overloads which just ends up calling the final one leaving some parameters to default.
(1) Calls the final overload with null/false as the other parameters (no attacker, no property, no overrides and no block)
private int DoDamage(ref MainManager.BattleData target, int ammount)
(2) Calls the final overload with the other parameters being null (No attacker, no property and no overrides)
private int DoDamage(ref MainManager.BattleData target, int ammount, bool block)
(3) Calls the final overload with the parameters sent leaving overrides to an empty array and block to false
private int DoDamage(MainManager.BattleData? attacker, ref MainManager.BattleData target, int damageammount, AttackProperty? property)
(4) Calls (5) with the parameters sent leaving attacker to null (meaning no attacker and no overrides)
private int DoDamage(ref MainManager.BattleData target, int damage, AttackProperty? property)
(5) Calls the final overload with the parameters sent leaving overrides to an empty array
private int DoDamage(MainManager.BattleData? attacker, ref MainManager.BattleData target, int damageammount, AttackProperty? property, bool block)
Enemy attack overloads¶
The following overloads are specifically made for an enemy party member attacking a player party member.
(6) Calls the final overload with the attacker being enemydata[enemyid]
, the target being playerdata[playertarget]
, overrides being null and the rest being the same as the parameters sent
private int DoDamage(int enemyid, int playertarget, int damage, AttackProperty? property, bool block)
(7) Calls the final overload with the attacker being enemydata[enemyid]
, the target being playerdata[playertarget]
and the rest being the same as the parameters sent
private int DoDamage(int enemyid, int playertarget, int damage, AttackProperty? property, DamageOverride[] overrides, bool block)
Player attack overloads¶
The following overloads are specifically made for a player party member attacking an enemy party member.
(8) Calls (9) with the parameter sent and enemytarget being the BattleControl's target
private int DoDamage(int playerid, AttackProperty? property)
(9) Calls the final overload with the following:
attacker
: The player party member whosetrueid
isplayerid
(it will falback to the first player party member if none exists)target
: The enemy party member inavailabletargets
whose index isenemytarget
. An exception is thrown if no enemy party member exists at that indexdamageammount
: The return of GetPlayerAttack for theplayerid
andcommandsuccess
which is theatk
of the corresponding player party member ifcommandsuccess
is true and if it's false, it's itsatk
clamped from -99 to 1property
: The same as the parameter sentoverrides
: Ifcommandsuccess
is true, this is null and if it's false, it's an array with one element beingFailSound
block
: false
private int DoDamage(int playerid, int enemytarget, AttackProperty? property)
Procedure¶
The following is a high level overview of the damage pipeline divided by sections for each groups of effects that happens.
An actor is considered to be a player party member if its battleentity has the Player
tag. It's assumed to be an enemy party member if it doesn't. If the attacker exists, its party is assumed to be the opposte of the target's.
block override¶
There are ways that block can be overriden to false. Here are all of them (they aren't mutually exclusive, if any is true, block becomes false):
- The target has the Freeze condition
- The target has the Sleep condition
- The target has the Numb condition
- The target is a player party member with the
Berserker
medal equipped - This is a non super block while flags 15 and 615 are true (chapter 1 started after the combat tutorial while FRAMEONE is active) and
overridechallengeblock
is false
Damage calculation¶
This is where the final damage amount is determined.
If the target isn't Invulnerable, the final damage amount is set to the return of CalculateBaseDamage with all the parameters sent (with the overriden block if applicable and property to null if it was None
). The weaknesshit and superguarded ref parameters are passed via locals initially set to false.
Otherwise (the target is invulnerable), it depends if the property is NoException
:
- If it isn't, the amount is 0
- If it is, it's the original damageammount
The target is invulnerable if any of the following is true:
- Its
plating
is true - It has the Shield condition
- It is a player party member with the Numb condition and the
ShockTrooper
medal equipped
NOTE: If the target is invulnerable, it will cause the weaknesshit and superguarded locals to remain with values of false. This can lead to missleading ShowSuccessWord later (see the block processing and player action commands sections below for details).
plating
expiration¶
If the target doesn't have the Shield condition, its plating
is set to false. This allows the plating
to not expire when hit while shielded.
DamageOverride application¶
8 of the 11 available DamageOverride
are processed here if they are present in overrides. Each are processed in order they appear in the array. The other 3 were processed by CalculateBaseDamage if it was called earlier:
ShowCombo
¶
ShowComboMessage is called with target.battleentity and block as block.
NoSound
¶
The DoDamageAnim call towards the end will have nosound set to true. NOTE: this is ignored if NoDamageAnim
or BlockSoundOnly
is processed after
BlockSoundOnly
¶
The DoDamageAnim call towards the end will have nosound set to true if block is false. NOTE: this is ignored if NoDamageAnim
or NoSound
is processed after
FailSound
¶
The DoDamageAnim call towards the end will have fail set to true. NOTE: this is ignored if NoDamageAnim
is processed after
NoDamageAnim
¶
There will not be a DoDamageAnim call towards the end of the method. NOTE: processing this overrides any NoSound
, BlockSoundOnly
, FailSound
and FakeAnim
because the call won't happen
NoCounter
¶
There will not be a ShowDamageCounter call later
FakeAnim
¶
The DoDamageAnim call towards the end will have fakeanim set to true. NOTE: this is ignored if NoDamageAnim
is processed after
DontAwake
¶
Prevents the attack from removing the Sleep
condition on the target later even if all conditions to do so are fufilled. This is only used as part of poison and fire damages from AdvanceTurnEntity.
Player damage processing¶
This section only happens if the target is a player party member.
Block processing¶
This part is applicable in 2 cases:
- block is true (this is the standard reason to process the block)
- block was overriden to false due to flags 615 being true (FRAMEONE is active) while
commandsuccess
was true (meaning a non super block happened, but it didn't count due to FRAMEONE).
The second case is an edge case where the only thing that happens is hasblocked
is set to true which is only used in HardSeedVenus meaning exceptionally, a HardSeed
can be given with that attack with a non super block even if FRAMEONE is active.
The first case is the standard one:
hasblocked
is set to true (allows HardSeedVenus to give aHardSeed
to the player if it gets called later as part of the enemy action)- If this is a non super block (or the target was invulnerable earlier), ShowComboMessage is called with target.battleentity as the entity and true as the block. NOTE: If the target was invulnerable earlier, this will always count as a regular block because superguarded's value wasn't gathered from CalculateBaseDamage. The ShowComboMessage call can be missleading in such case because it can override the presentation of a previous
wordroutine
from a previous DoDamage call since the previous one gets destroyed. This can happen in cases like PartyDamage or in any situations where multiple DoDamage calls happens close to each other. Essentially, it means that a super block when the target is invulnerable will be presented as if it was a regular block. - Otherwise (meaning this was a super block AND the target wasn't invulnerable earlier):
combo
is set to 2- The
AtkSuccess
sound is played at 1.1 pitch and 0.6 volume if it wasn't playing already (or it was, but its time was past 0.1 seconds) - ShowComboMessage is called with target.battleentity as the entity and false as the block
- If the
NeedlePincer
medal is equipped on the target and the target's battleentity still exists inplayerdata
, the target will get healed later due to the medal
SpikeBod
medal processing¶
This part only happens if all of the following are true:
- The
SpikeBod
medal is equipped - target.
trueid
is 1 (The target's animid isBeetle
) - block is true
nonphyscal
is false- There is an attacker (assumed to be an enemy party member)
In that case, the attacker will get damaged which goes like the following:
- The
Damage0
sound is played with 0.8 pitch and 0.5 volume - The
hp
of the enemy party member corresponding to the attacker is decreased by 1 + the amount ofSpikeBod
medals equipped onBeetle
then clamped from 1 to itsmaxhp
(this implies that this damage cannot be lethal) - ShowDamageCounter is called with type 0 (damage) with the amount of damage dealt just now starting from the attacker.battleentity's position + attacker.
cursoroffset
and ending to (0.0, 1.0, 1.0)
PoisonTouch
medal processing¶
This part only happens if all of the following are true:
- The
PoisonTouch
medal is equipped on the target - The target has the Poison condition
- There is an attacker (assumed to be an enemy party member) and its
poisonres
is less than 100 (it's not immune to poison inflictions) nonphyscal
is false
If all of the above are fufilled, SetCondition is called to set the Poison condition on the corresponding enemy party member of the attacker for 2 turns
FavoriteOne
medal processing¶
This part only happens if the FavoriteOne
medal is equipped on the target.
The only thing that happens is attackedally
is set to target.trueid
. This will only take into effect later during AdvanceMainTurn.
ShakeGUI call¶
This part always happen as part of the player damage processing.
The only thing that happens here is a ShakeGUI coroutine is started to shake the corresponding hud
element of the target.battleentity.animid with a range of (0.1, 0.1, 0.0) for 15.0 frames. This coroutine will change the local position of the hud
's first chiled to be random between (0.0 - shake.x, 0.0 - shake.y) and (shake.x, shake.y) for the next 15.0 frames before being reset to Vector3.zero.
Enemy damage processing¶
This section happens if the player damage processing didn't meaning the target is an enemy party member
isdefending update¶
This part only happens if all of the following are true:
- target.defenseonhit is above 0
- The final amount of damage calculated is above 0
- property isn't
Flip
. NOTE: CalculateBaseDamage can changeisdefending
when property isFlip
so this is why it can't change here (damage calculation needs to ne able to override this logic hence why this check is done)
If all the above are fufilled, it means the enemy supports defending itself and it sustained damage which causes updates to its guarding state. When that happens, isdefending
is set to false if the target IsStopped or to true if it's not.
hitaction update¶
target.hitaction
can change under 2 conditions (these aren't mutually exclusive, they are applied in order, but hitaction
may not be set if neither apply):
- It's set to true if target.defenseonhit is -1 and its position isn't
Underground
. This is only the case for anUnderling
enemy under normal gameplay - It's set to !
enemy
(false on the enemy phase / delproj / enemyfirststrike
/ enemyhitaction
, true otherwise) if the target.onhitaction condition is fufilled which is:- If it's 1, it's always fufilled
- If it's 2, it's only fufilled if target.
position
isFlying
- If it's 3, it's only fufilled if target.
position
isGround
After, every other enemy party members other than the target who has the target.animid
(its enemy id) in their chargeonotherenemy has their hitaction
set to !enemy
(false on the enemy phase / delproj / enemy firststrike
/ enemy hitaction
, true otherwise).
Undigging¶
This part only happens if all of the following are true:
- The final amount of damages calculated earlier is higher than 0
- target.position is
Underground
mainturn
(AdvanceMainTurn) isn't in progress
If the above conditions are fufilled, then UnDig is called with the target as ref which does the following on it:
- battleentity.
digging
is set to false - battleentity.
overridejump
is set to true - Jump is called on the battleentity with a height of 15.0
- position is set to
Ground
turnsnodamage
reset¶
If the final amount of damages calculated earlier is higher than 0, target.turnsnodamage
is set to -1.
Player action command success¶
If commandsuccess
is true and the attacker is a player party member:
wordroutine
is stopped if it was in progress (followed by the destruction ofcommandword
if it was)wordroutine
is set to a new ShowSuccessWord call with target.battleenetity as the t, without block and the super being the ref weaknessonhit value obtained from CalculateBaseDamage done earlier (false if it wasn't called). NOTE: If the target was invulnerable earlier, weaknessonhit will always be false since this information wasn't gathered by CalculateBaseDamage
PoisonDamUp effects¶
If all of the following conditions are fufilled, the final damage amount is increased by 2:
- The attacker exists has
PoisonDamUp
in its weakness (assumed to be an enemy party member) - The attacker has the Poison condition
- The target doesn't fall into any of the invulnerable cases mentioned in the damage calculations above
DoBlock effects¶
This section applies if all of the following are true:
- The target is an enemy party member that has
DoBlock
in its weakness - The target isn't IsStoppedLite (the same as IsStopped, but the Flipped condition doesn't count as a stopping condition)
If the conditions above are fufilled, the following happens:
- The target.
blockTimes
is incremented (this is a DoDamage counter) - If target.
blockTimes
is 1 (meaning that this is the first DoDamage call done to this target on the same actor turn while they weren't stopped), nothing happens and the game skips to the next section - If target.
blockTimes
is 2 or more (second or further DoDamage call on this target for the same actor turn):- The final damage amount gets changed depending if
blockTimes
is 2 or more than 2:- 2 (second DoDamage call): The final amount is / 2 floored
- More than 2 (third DoDamage call or further): The final amount is / 3 floored
- block is overriden to true (this simulates the target as if they are blocking when DoDamageAnim gets called towards the end later)
- A new GameObject is created with a SpriteRenderer using the
battlemessage[4]
sprite (the word BLOCK!) on layer 15 (3DUI
) positioned at the target + (3.0, 3.0, 0.0) with an intial scale of 0.0x - Start a GradualScale on the SpriteRenderer with a target of 1.0x with a frametime of 10.0 without destroy
- The GameObject gets destroyed in 2.0 seconds
- The final damage amount gets changed depending if
target.hp
decrease and clamping¶
target.hp
gets decreased by the final amount of damage calculated earlier, but it also gets clamped after. The higher bound of the clamp is always target.maxhp
, but the lower bound depends on some conditions.
Here they are in order (they are mutually exclusive, the first one applies):
Clamp from 10¶
This happens if all the following are true:
- The target is an enemy party member
- The target has a
SurviveWith10
weakness - currentaction is
ItemList
(meaning this was an item use) orlastskill
is not 9 (this attack came from any player skills other thanPeebleToss
)
Clamp from 1¶
This happens if the target is an enemy party member with an AlwaysSurvive
weakness.
Clamp from 0¶
This is the default clamp that happens if the other 2 didn't.
Player selection cycle reset¶
If the target is a player party member with an hp
of 0 (meaning it just died as a result of the attack), SetLastTurns is called which resets lastturns
to a new aray with the length being the amount of free players - 1 and all elements being -1 (this resets the player selection cycle).
Target waking up¶
This section only applies if all of the following is true:
- The target had the Sleep condition at the start of the method
- The property isn't
Sleep
- target.
hp
is above 0 - The final damage amount calculated earlier is above 0. NOTE: CalculateBaseDamage may override this if the property is one of the status infliction one (except
Flip
) and the infliction occured because this clause isn't part of the damage calculation. This means that if the amount is 0, but an infliction removingSleep
occured, thecantmove
won't change which is a different logic than this method enforces - There was no DamageOverride of
DontAwake
processed earlier - If the target is an player party member, the
HeavySleeper
medal must not be equipped
If all of the above are fufilled, the following happens:
- RemoveCondition is called to remove the Sleep condition on the target
- target.
isasleep
is set to false - target.
cantmove
is set to 1. NOTE: This is incorrect because it means for a player party member, that remaining actor turn will advance immeidately after the enemy phase so they get to act, but for an enemy party member, they loose their entire main turn because the actor turn won't advance after the player phase - UpdateAnim is called with true as the onlyplayer
LifeSteal
processing¶
This section only happens if the attacker is a player party member with the LifeSteal
medal equipped while nolifesteal
is false.
If the above is fufilled, the following happens:
- The
Heal
sound is played - The amount to heal is set to the final damage amount calculated earlier / 2.0 (or 4.0 if the
HPFunnel
medal is equipped on the attacker) then clamped from 1.0 to 99.0 then floored - The attacker's
hp
is increased by the amount just calculated clamped from 0 to the attacker'smaxhp
- ShowDamageCounter is called with type 1 (HP) for the amount to heal calculated earlier (without
maxhp
clamp) starting from the attacker's battleentity's position + (0.0, 1.75, 0.0) and ending at (0.0, 3.0, 0.0) nolifesteal
is set to true which prevents the medal from processing until the next DoAction call
Stat down on block properties processing¶
This section applies if the target.hp
is above 0, the target doesn't have the Shield
condition and the property is DefDownOnBlock
or AtkDownOnBlock
.
This will call StatusEffect to give the target a condition with visual effect. The amount of turn to inflict is 2 turns unless block is true where it's 1 turn instead. On top of this, it inflicts for one more turn if the target is a player party member (meaning 3 turns or 2 with block).
As for the actual condition, it's DefenseDown
if the property is DefDownOnBlock
and it's AttackDown
if the property is AtkDownOnBlock
.
NeedlePincer
medal processing¶
If a heal was requested earlier as a result of blocking as a player party member with a NeedlePincer
medal, this is where the Heal call happens with the target as the entity and the ammount being the amount of NeedlePincer
equipped on the target.
DoDamageAnim call¶
Unless there was a NoDamageAnim
damage override processed earlier, DoDamageAnim is called with these parameters:
- entity: target
- damage: The final amount of damage calculated
- block: block
- noanim: true
- nosound, fail and fakeanim, they are false unless a damage override processed earlier decided otherwise
Final processing¶
This section always happen at the end of the method:
combo
is incremented- RefreshMusic is called which is an empty method
lastdamage
is set to the final amount of damage calculated earlier- If the target is an enemy party member,
damagethisturn
is increased by the final amount of damage calculated earlier - The final amount of damage calculated earlier is returned, ending the method