Enemies¶
Enemies data are split in 3 TextAssets in the game loaded on boot:
- By LoadEssentials:
Ressources/data/EnemyData
- By SetVariables:
Ressources/data/TattleListEnemyTattlefrom the corresponding dialogue directory of the current languageid
EnemyData is also loaded on the Start and Update of the PauseMenu.
EnemyData data¶
The asset contains one line per enemy whose id corresponds to the line index. Each line contains fields separated by @:
| Loaded index | Name | Type | Description |
|---|---|---|---|
| 0 | battleentity.animid | int | The AnimID of the entity bound to this enemy (can be overriden if it's a variant, see the section below about it for details) |
| 1 | hp | int | The base max HP of the enemy (can be influenced by difficulty scaling, see the section below about it for details) |
| 2 | def | int | The base defense of the enemy (can be influenced by difficulty scaling, see the section below about it for details) |
| 3 | exp | int | The base amount of exploration points this enemy gives (can be overriden to 0 or be scaled by the instance.partylevel, see the section below about it for details) |
| 4 | money | int | The base amount of berries this enemy drops |
| 5 | cursoroffset.x | float | The x component of the offset to apply to the selection cursor upon targeting the enemy |
| 6 | cursoroffset.y | float | The y component of the offset to apply to the selection cursor upon targeting the enemy |
| 7 | cursoroffset.z | float | The z component of the offset to apply to the selection cursor upon targeting the enemy |
| 8 | poisonres | int | The base poison resistance of the enemy |
| 9 | freezeres | int | The base freeze resistance of the enemy |
| 10 | numbres | int | The base numb resistance of the enemy |
| 11 | sleepres | int | The base sleep resistance of the enemy |
| 12 | size | float | The width of the enemy used for calculating positioning |
| 13 | battleentity.freezesize.x | float | The x component of the ice cube size when the entity is frozen |
| 14 | battleentity.freezesize.y | float | The y component of the ice cube size when the entity is frozen |
| 15 | battleentity.freezesize.z | float | The z component of the ice cube size when the entity is frozen |
| 16 | battleentity.freezeoffset.x | float | The x component of the ice cube offset from the entity's center when the entity is frozen |
| 17 | battleentity.freezeoffset.y | float | The y component of the ice cube offset from the entity's center when the entity is frozen |
| 18 | battleentity.freezeoffset.z | float | The z component of the ice cube offset from the entity's center when the entity is frozen |
| 19 | position | BattlePosition (string or int) |
The starting battle position of the enemy on the battle field (Random has special logic, see the special fields logic section below for details) |
| 20 | battleentity.height | float | The height of the entity, If the position is Flying and the value is below 2.0, it is overriden to 2.0 |
| 21 | battleentity.bobspeed | float | The bobspeed of the entity |
| 22 | battleentity.bobrange | float | The bobrange of the entity |
| 23 | weakness | { separated list where the first element is the length (in int) and the rest represents a list of AttackProperty (int or string) |
The list of AttackProperty that applies to this enemy. NOTE: The length portion of the field is redundant, but its value is required and it needs to be above 0 if the list isn't empty as otherwise, the list will be ignored when reading. Additionally, every element whose value is an empty string are skipped. |
| 24 | weight | float | A visual modifier that controls if and how much the enemy can get launched if attacked, should be between 0.0 (furthest launch) and 100.0 (no launch) |
| 25 | Base Enemy id (loaded as animid) | int | The Enemy id that this is a variant of (see the section below about enemy variant for details), can be omitted if it's a negative number |
| 26 | eventondeath | int | The EventDialogue to trigger when the enemy dies as detected by CheckDead, can be ommited if it's -1 |
| 27 | moves | int | The amount of actor turn the enemy has normally each main turn during the enemy phase |
| 28 | notaunt | bool | Tells if the enemy can't receive the Taunted condition by using the Taunt skill |
| 29 | cantfall | bool | Tells if the enemy cannot drop or have its position set to Ground |
| 30 | fixedexp | bool | Tells if the exp field should not be scaled and is a fixed number (see the section below about EXP for details) |
| 31 | notired | bool | Tells if exhaution increases won't happen if they were set to happen normally (with caveats, check the documentation of the feature to learn more) |
| 32 | hidehp | bool | Tells if the hp and def stats of the enemy should not be shown even after spying |
| 33 | deathtype | int | The manner in which the enemy dies (This isn't a DeathType, more on the feature's documentation) |
| 34 | chargeonotherenemy | ; separated list of int or -1 or empty |
The list of enemy ids that will cause this enemy to perform a hitaction when any enemy id in that list takes damages in the player phase. See the feature's documentation to learn more. This field also has special loading logic, check the section below for more details |
| 35 | hardatk | int | The amount to increase the attack on Hard Mode or EX (see the section below about difficulty scaling for details). Not loaded if the hard difficulty scaling doesn't apply and the value remains at 0 |
| 36 | int | The base amount to increase the max HP on Hard Mode, EX or HARDEST (this is NOT hardhp which stays at 0 and is UNUSED). See the section below about difficulty scaling for details |
|
| 37 | int | The base amount to increase the defense on Hard Mode, EX or HARDEST (this is NOT hardef which stays at 0 and is UNUSED). See the section below about difficulty scaling for details |
|
| 38 | defenseonhit | int | The amount of point of defenses granted to the enemy when isdefending is true (check the feature's documentation to learn more) |
| 39 | itemoffset.x | float | The x component of the offset to render an item relative to the enemy |
| 40 | itemoffset.y | float | The y component of the offset to render an item relative to the enemy |
| 41 | itemoffset.z | float | The z component of the offset to render an item relative to the enemy |
| 42 | bool | If true, the battleentity.basestate is set to 13 (BattleIdle) |
|
| 43 | Portrait sprite index | int | Index of the sprite in Sprites/Items/EnemyPortraits (if this is negative, this defaults to the enemy id, see the GetEnemyPortrait section below about this for more details) |
| 44 | notattle | bool | Tells if the enemy is not spy-able. Check the feature's documentation for more details. This fields also has special loading logic that can override it to true, check the section below for more detials |
| 45 | eventonfall | int | The EventDialogue to trigger when the enemy drops, can be ommited if it's -1 |
| 46 | onhitaction | int | If 1, the enemy will process a hitaction when hit. If 2, it will only when its position is Flying and if 3, it will only when its position is Ground |
| 47 | actimmobile | bool | Prevent cantmove to be anything higher than 0 or IsStopped to return true as a result of being inflicted with a stopping condition. This works for most, but not all cases, check the feature's documentation to learn more |
| 48 | sizeonfreeze | float | The size of the enemy on freeze. If the value is less than 0.1, it is be overriden to size + 0.25, check the feature's documentation to learn more |
The data will be loaded by into enemydata[id, x], where id is the enemy id and x is the loaded index. On the pause menu, every lines is loaded into enemydata all at once, but the meanings do not change.
GetEnemyData¶
While the data loading happens in LoadEssentials, the actual parsing and usage of it is done by another method called GetEnemyData. It receives an enemy to obtain the data, a bool called createentity that tells whether or not the battleentity should be created too and a bool called noexp that disallows any positive numbers to be set to exp. This returns a BattleData containing all the informations of the enemy.
public static BattleData GetEnemyData(int id)
public static BattleData GetEnemyData(int id, bool createentity)
public static BattleData GetEnemyData(int id, bool createentity, bool noexp)
The overload without a noexp parameter will have its value default to false.
The overload with just the id parameter is UNUSED, but remains functional and the createentity parameter will default to to false.
All battleentity fields are loaded only when the createentity parameter is true. When that happens, battleentity will be created via CreateNewEntity with name enemyX where X is the enemy id with dummy animid and position (animid 0 which is Bee and position (0.0, -10.0, 0.0)) as those can be overriden later.
Enemy variant¶
The base Enemy id field uses a feature to redirect most of the fields loaded to be the ones belonging to another enemy entirely. It is used to add an enemy variant which only shares some fields with its base enemy, but it has its own id. It also has the effect of overriden the animid field to be the base enemy id instead. Here are the list of fields that are shared:
eventondeathbattleentity.heightbattleentity.bobspeedbattleentity.bobrangemovesnotauntcantfallnotiredfixedexpposition
There are however some fields that are excluded from this if the createentity is true and the original Enemies id is FireKrawler, FireWarden, FireCape, IceKrawler or IceWarden. They will load the following fields from their base entry (Krawler, Warden or CursedSkull) instead on top of the standard variant system:
movesnotauntcantfallnotiredfixedexpposition
Every other loading logic or fields will be loaded as if it was the enemy of the corresponding base Enemy id. This imply that every other field of the actual enemy will be ignored and they can be left as dummy values.
BattleEntity special initialisation¶
When createentity is true, on top of actually creating the entity and setting it to the battleentity field, the entity itself has special logic when it comes to initialise it. This section will only mention anything that's outside the scope of loading a data field:
battleentity.overridejumpis set to truebattleentity.battleis set to truebattleentity.alwaysactiveis set to truebattleentity.ongroundis set to true- If this enemy hasn't been seen while the map is
CaveOfTrialsor it's aTANGYBUGor the enemy isFireKrawler,FireCapeorFireWardenwhile flag 664 is false (not yet approached the oven during Chapter 7), the following happens (this can have an impact on theexpfield of theBattleData, see the section below for more details):battleentity.namegets prepended with theCOTModifierbattleentity.hologramis set to truebattleentity.cotunknownis set to true- RefreshCOT is invoked in 0.1 seconds on the
battleentity
battleentity.tagis set toEnemybattleentity.gameObject.layeris set to 9 (Follower)entityis set to thebattleentity- If the
positionisFlyingandbattleentity.heightis below 2.0,battleentity.heightis overriden to 2.0 battleentity.initialheightis set tobattleentity.height- CreateHPBar is called on the entity
battleentity.emoticonoffsetis set to thecursoroffset- If the
positionisUnderground:- InstantDig is called on the
battleentity battleentity.heightandbattleentity.initialheightare set to 0.0
- InstantDig is called on the
- If the field 42 of
enemydatais true,battleentity.basestateis set to 13 (BattleIdle)
After this, the entity.destroytype gets mapped to a DeathType depending on the value of the BattleData's deathtype (this also changes the battleentity's because they hold the same reference).
Exp logic¶
The exp field has particularily complex logic to determine its value:
- It is always 0 if the sent noexp value is true
- It is left at the default value of 0 if instance.
partylevelis at least 27 (it's maxed) or if flag 613 is true (RUIGEE is active) - If the
fixedexpfield is true, then the amount is always the raw one coming fromenemydatawith no modifications to it - If none of the cases above applied, the result is the return of MainManager.GetEXP with the
expfield fromenemydata, the instance.partyleveland the enemy id (the base id if it's a variant)
The return of MainManager.GetEXP implies even more logic where the base exp field can be changed. Here's the method signature:
public static int GetEXP(int input)
public static int GetEXP(int input, int level)
public static int GetEXP(int input, Enemies enemy)
public static int GetEXP(int input, int lv, Enemies? enemy)
Calculates and returns the scaled EXP that enemy would give when the player is at rank lv and the unscaled EXP value is input. Check the GetEXP documentation for the details on how this method calculates the scaled EXP. The enemy scaling part doesn't apply if enemy is null, but this never happens under normal gameplay.
The default lv value for the third overload is partylevel.
The first 2 overloads are UNUSED, but remains functional and works like this:
- First overload:
lvispartylevelandenemyis null - Second overload:
lvislevelandenemyis null
Here is the process used to calculate the final value:
- The base EXP amount is determined. It's the same than the sent exp value unless the enemy is a
WaspTrooperorWaspHealerwhich can increase this value: - The value gets multiplied by map.
expmultiwhich is a field that must be defined on the map's prefab. The result isn't rounded yet - The value gets subtracted by (level - 1) * 2.5 and the result is clamped from 1.0 to 99.0 then floored
- The now integer value is decremented and returned
Additionally, GetEnemyData can also change yet again the value after it was calculated in 2 special cases (they both can happen and stack and they can happen regardless of how the initial exp value was determined):
- flag 162 is true (during a B.O.S.S or Cave Of Trials session): the value gets multiplied by 0.2, floored and then clamped from 0 to 5
- createentity is true, flag 166 is false (EX mode isn't active on the B.O.S.S. system) and the
battleentityis ahologram(see the section above about thebattleentityinitialisation for when this happens): the value is multiplied by 0.1 and floored
Then, StartBattle can further change the exp If all of the following conditions are true:
- The enemy is a
Krawler,CursedSkullorCape - battleentity.
forcefireis true or we are in theGiantLairarea except for theGiantLairFridgeInsidemap - instance.
partylevelis less than 27 (meaning it's not maxed) - flags 613 is false (RUIGEE is inactive)
If that happens, the exp is incremented by the floored result of a lerp from 10.0 to 3.0 with a factor of instance.partylevel / 27.0
Finally, while all of the above describes the exp field itself, CheckDead calls BattleControl.GetEXP (not to be confused with MainManager.GetEXP) to determine the rewarded exp amount. This method contains further special logic where it receives the exp and fixedexp fields of the enemy as well as its enemy id. Here's what the method does:
- If flags 613 is true (RUIGEE is active) or
partylevelis 27 (max rank), the return is 0 (effectively disables all exp gains to the player) - Otherwise:
- If the
DoublePainmedal is equipped or flags 614 is true (HARDEST is active),expincreases by (exp* 0.15 ceiled which is basically 15% ceiled) - If the
EXPBoostmedal is equipped,expincreases by (exp* 0.50 ceiled which is basically 50% ceiled) - A result is determined from there (only the first one that applies is returned):
- If Flags 166 is true (during a B.O.S.S session in EX mode), the return is
expclamped from 0 to 5 - Otherwise, if
fixedexpis true,expis returned without any clamping - Otherwise, if the enemy is among ChomperBrute, ToeBitter, DeadLanderA, DeadLanderB or DeadLanderG, the return is
expclamped from 0 to 20 - Otherwise (neither of the above applied), the return is clamped from 0 to 15
- If Flags 166 is true (during a B.O.S.S session in EX mode), the return is
- If the
Special fields logic¶
Additionally, some BattleData fields have special logic attached to them:
entityname: See the section aboutEnemyTattlebelowholditem: always set to -1chargeonotherenemy: The value depends on the loaded data:- If it's
-1, the value is an empty array - If it's empty, the value is null
- If it's a
;separated list of enemy ids, each element will be converted to int and the value will be that array, but for any element that's empty in that list, the element will be -1 instead
- If it's
notattle: If the loaded data value is false, it will still be overriden to true if any of these is true:sizeonfreezeIf the loaded value is less than 0.1, it will be overriden tosize+ 0.25hardatk,hpanddef: See the section below detailing how enemy stats difficulty scaling worksmaxhp: Always set tohpafter it was calculated post difficulty scalingposition: If its value is Random, then it will be determined randomly and uniformly between Ground and Flying, but there's an exception if the enemy is aMushroomand flag 24 is true (received the Turn Relay tutorial) in which case, the position field is Flyingentity: If createentity is true, this is set to thebattleenentitybeing createdcondition: Always set to a new listcantmove: Always set to -moves+ 1 which gives itmovesamount of actions availableharddefandhardhp: These fields are practicaly unused because they always have a value of 0 which doesn't have any impact in the game
Stats difficulty scaling¶
There are some fields in enemy data that are influenced by the different difficulty tiers the game has. This only happens if any of the following is true:
- The
DoublePainMedal is equipped - Flags 614 is true (HARDEST is active)
- Flags 166 is true (EX mode is active on the B.O.S.S. system)
If the above is met, it will always have the following effect:
hardatk: Set to the loaded data value (this is left at 0 if hard scaling didn't apply in the first place)hp: Increased byenemydatafield 36def: Increased byenemydatafield 37
On top of this, if HARDEST was active, the following applies after:
hardatk: Gets incrementedhp: Gets increased by 15% then ceileddef: If flags 300 is true (Chapter 4 started) and the existingdefwasn't negative, it gets incremented
On top of this, if EX mode is active on the B.O.S.S. system, then there are additional effects after everything if the enemy isn't WaspGeneral, KeyR, KeyL or Tablet:
hardatk: Gets increased by 15% ceiledhp: Gets increased by 15% ceiled. If the result ends up above 90, it is then decreased by 15% ceiled.def: Gets clamped from 1 to 99
GetEnemyPortrait¶
This field is loaded, but never written to. Its only usage is in the GetEnemyPortrait method which by default returns the value directly (or the enemy id if the value is negative).
However, the return can be overridden to 225 for a Cape, 226 for a Krawler and 227 for a CursedSkull if flag 664 is true (approached the oven during Chapter 7). This overrides the portrait sprite to include the fire variants of the enemy under this condition.
EnemyTattle data¶
The asset contains one line per Enemy whose id corresponds to the line index. Each line contains fields separated by @:
| Loaded index | Name | Type | Description |
|---|---|---|---|
| 0 | Name | SetText string | The name of the enemy |
| 1 | Biography | SetText string | The biography of the enemy |
| 2 | Vi's spy | SetText string | Vi's Spy dialogue |
| 3 | Kabbu's spy | SetText string | Kabbu's Spy dialogue |
| 4 | Leif's spy | SetText string | Leif's Spy dialogue |
The data will be loaded into librarydata[1, id, x] where id is the Enemy id and x is the loaded index. The name is also loaded into enemynames.
Enemies not meant to have a Bestiary entry by convention have only the name field defined and the rest are dummy fields. The biography's dummy value is typically "biotattle" as well as the spy text fields being "beetattle", "beetletattle" and "mothtattle" respectively.
During GetEnemyData, the typical entityname value is the one loaded into enemynames but it will be menutext 59 (?????) during Cave Of Trials for an unseen enemy or if createentity is true and the enemy is FireKrawler, FireCape or FireWarden while flag 664 is false (not yet approached the oven during Chapter 7).
TattleList data¶
The asset contains the order to render the Bestiary entry in the Library List Type where each line contains one field:
| Name | Type | Description |
|---|---|---|
| Enemy id | int | The Enemies id of the enemy to render at the position of the line index |
The data will be loaded into libraryorder[1, i] where i is the line index.