Skip to content

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 the target
  • ref target: The actor receiving the damage. Passed as ref to allow modifying the actor's data. If the attacker 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 deal
  • property: The property of the attack
  • overrides: The overrides to apply to the damage pipeline. May be null or an empty array to have no overrides
  • block: For an enemy attack, whether or not the player sucessfully blocked it (this is intended to receive commandsuccess 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 whose trueid is playerid (it will falback to the first player party member if none exists)
  • target: The enemy party member in availabletargets whose index is enemytarget. An exception is thrown if no enemy party member exists at that index
  • damageammount: The return of GetPlayerAttack for the playerid and commandsuccess which is the atk of the corresponding player party member if commandsuccess is true and if it's false, it's its atk clamped from -99 to 1
  • property: The same as the parameter sent
  • overrides: If commandsuccess is true, this is null and if it's false, it's an array with one element being FailSound
  • 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 a HardSeed 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 in playerdata, 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 is Beetle)
  • 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 of SpikeBod medals equipped on Beetle then clamped from 1 to its maxhp (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 change isdefending when property is Flip 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 an Underling enemy under normal gameplay
  • It's set to !enemy (false on the enemy phase / delproj / enemy firststrike / enemy hitaction, 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 is Flying
    • If it's 3, it's only fufilled if target.position is Ground

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 of commandword 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

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) or lastskill is not 9 (this attack came from any player skills other than PeebleToss)

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 removing Sleep occured, the cantmove 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's maxhp
  • 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