Difficulty scaling: Difference between revisions
Jump to navigation
Jump to search
no edit summary
imported>Leoetlino No edit summary |
imported>Leoetlino No edit summary |
||
Line 2: | Line 2: | ||
== Points == | == Points == | ||
The scaling system is based on a point system. Killing enemies is the ''only way'' to receive points. | The scaling system is based on a point system. Killing enemies is the ''only way'' to receive points. | ||
Whenever an enemy dies, the game increments a flag 'Defeated_{SameGroupActorName}_Num' if all of the following conditions are satisfied: | Whenever an enemy dies, the game increments a flag 'Defeated_{SameGroupActorName}_Num' if all of the following conditions are satisfied: | ||
* The current kill count is < 10. | |||
* The current kill count is & | |||
* The actor does not have the NotCountDefeatedNum flag. | * The actor does not have the NotCountDefeatedNum flag. | ||
* '''For Monk Maz Koshia''': 'Defeated_Priest_Boss_Normal_Num' is 0. | * '''For Monk Maz Koshia''': 'Defeated_Priest_Boss_Normal_Num' is 0. | ||
* '''For Dark Beast Ganon''': It is the first time the boss is beaten. (Ganon's Defeated flag has 1 as the maximum value.) | * '''For Dark Beast Ganon''': It is the first time the boss is beaten. (Ganon's Defeated flag has 1 as the maximum value.) | ||
* '''For Blights''': It is the first time the blight is beaten in the Divine Beast, or in the Illusory Realm. Blights fought in Hyrule Castle do not count. | * '''For Blights''': It is the first time the blight is beaten in the Divine Beast, or in the Illusory Realm. Blights fought in Hyrule Castle do not count. | ||
This happens every time ''any'' enemy dies, even if they don't necessarily play a role in the point system (see below) and even if the player is not responsible for their death. | This happens every time ''any'' enemy dies, even if they don't necessarily play a role in the point system (see below) and even if the player is not responsible for their death. | ||
Because enemies have to be killed throughout the main quest and bosses are considered as enemies too, difficulty scaling is unavoidable. | Because enemies have to be killed throughout the main quest and bosses are considered as enemies too, difficulty scaling is unavoidable. | ||
Only the defeated counter flags are stored in the save file. The <code>Ecosystem::LevelSensor</code> subsystem is responsible for [[#Ecosystem::LevelSensor::calculatePoints|converting these kill counts to points]] using a [[#Ecosystem::LevelSensor::loadByml|configuration file]]. | Only the defeated counter flags are stored in the save file. The <code>Ecosystem::LevelSensor</code> subsystem is responsible for [[Difficulty scaling#Ecosystem::LevelSensor::calculatePoints|converting these kill counts to points]] using a [[Difficulty scaling#Ecosystem::LevelSensor::loadByml|configuration file]]. | ||
The subsystem provides two functions ([[#Ecosystem::LevelSensor::loadWeaponInfo|<code>loadWeaponInfo</code>]] and [[#Ecosystem::LevelSensor::loadActorInfo|<code>loadActorInfo</code>]]) that may be called when a weapon or enemy actor is loaded. | The subsystem provides two functions ([[Difficulty scaling#Ecosystem::LevelSensor::loadWeaponInfo|<code>loadWeaponInfo</code>]] and [[Difficulty scaling#Ecosystem::LevelSensor::loadActorInfo|<code>loadActorInfo</code>]]) that may be called when a weapon or enemy actor is loaded. | ||
== Scaling inhibitors == | == Scaling inhibitors == | ||
Both scaling functions will immediately return without doing anything if: | Both scaling functions will immediately return without doing anything if: | ||
* [[WorldMgr]]::sInstance->stageType == 1 (Open World stage) | |||
* [[WorldMgr]]::sInstance-& | * and WorldMgr::sInstance->isAocField (current map is Trial of the Sword) | ||
* and WorldMgr::sInstance-& | * and WorldMgr::sInstance->disableScaling (set to true when entering Trial of the Sword) | ||
* and WorldMgr::sInstance-& | Scaling will also be skipped if the current [[map area]] is 28. This corresponds to "HateruSea", which is the Eventide Island area. | ||
Scaling will also be skipped if the current [[map area]] is 28. This corresponds to & | |||
== Weapons == | == Weapons == | ||
'loadWeaponInfo' is called (i.e. weapons may be scaled) for a weapon if: | 'loadWeaponInfo' is called (i.e. weapons may be scaled) for a weapon if: | ||
* '''For standalone weapons''': The actor property 'LevelSensorMode' is higher than 1 '''and''' it wasn't already picked up. | * '''For standalone weapons''': The actor property 'LevelSensorMode' is higher than 1 '''and''' it wasn't already picked up. | ||
* '''For treasure chest drops''': Always upon opening or destroying the chest. | * '''For treasure chest drops''': Always upon opening or destroying the chest. | ||
* '''For Hinox weapons''': The flag <code>{MapName}_Necklace_{i}_{HinoxName}_{ID}</code> is false. | * '''For Hinox weapons''': The flag <code>{MapName}_Necklace_{i}_{HinoxName}_{ID}</code> is false. | ||
* '''For other enemy drops''': The flag <code>{MapName}_WeaponDrop_{ID}</code> is false, '''and''' [the actor property 'LevelSensorMode' is higher than 1 ''or'' the enemy is a Guardian Scout ('Enemy_Guardian_Mini')]. | * '''For other enemy drops''': The flag <code>{MapName}_WeaponDrop_{ID}</code> is false, '''and''' [the actor property 'LevelSensorMode' is higher than 1 ''or'' the enemy is a Guardian Scout ('Enemy_Guardian_Mini')]. | ||
Note: Weapons that are bought from a shop cannot receive modifiers because they do not fit into any of the above cases. | Note: Weapons that are bought from a shop cannot receive modifiers because they do not fit into any of the above cases. | ||
== Enemies == | == Enemies == | ||
When loading enemies, the game will always try to scale enemies. | When loading enemies, the game will always try to scale enemies. | ||
However, the scaling function won't do anything if 'LevelSensorMode' is & | However, the scaling function won't do anything if 'LevelSensorMode' is < 1 and will leave the enemy and any weapons they may hold unscaled. | ||
Note: Enemies that are not in any upgrade list (such as elemental Lizalfos) will not be scaled, but their weapon can still receive upgrades if: | Note: Enemies that are not in any upgrade list (such as elemental Lizalfos) will not be scaled, but their weapon can still receive upgrades if: | ||
* 'LevelSensorMode' is non zero. | * 'LevelSensorMode' is non zero. | ||
* Weapon point requirements are satisfied | * Weapon point requirements are satisfied | ||
* ''or'' the modifier tier is overridden using 'SharpWeaponJudgeType'. | * ''or'' the modifier tier is overridden using 'SharpWeaponJudgeType'. | ||
[1.3.0] In Master Mode, '''all''' enemies are automatically ranked up one tier by default '''post scaling''', independently of 'LevelSensorMode'. Actors can receive two additional parameters: | [1.3.0] In Master Mode, '''all''' enemies are automatically ranked up one tier by default '''post scaling''', independently of 'LevelSensorMode'. Actors can receive two additional parameters: | ||
{| class="wikitable" | |||
{|class="wikitable" | ! Parameter | ||
! Parameter | ! Default | ||
! Description | |||
|- | |- | ||
| IsHardModeActor | | IsHardModeActor | ||
Line 67: | Line 56: | ||
| Controls whether the automatic rankup applies to an enemy. | | Controls whether the automatic rankup applies to an enemy. | ||
|} | |} | ||
In Master Mode, IsHardModeActor, DisableRankUpForHardMode and LevelSensorMode are combined on some actors to keep low-level enemies in the overworld (e.g. Red Bokoblin south of the Great Plateau). | In Master Mode, IsHardModeActor, DisableRankUpForHardMode and LevelSensorMode are combined on some actors to keep low-level enemies in the overworld (e.g. Red Bokoblin south of the Great Plateau). | ||
== Properties == | == Properties == | ||
=== <code>LevelSensorMode</code> === | === <code>LevelSensorMode</code> === | ||
This actor property controls whether scaling is enabled for an enemy or weapon. Also applies to any weapons held by an enemy since 'loadWeaponInfo' is called when an enemy drops their weapon. | This actor property controls whether scaling is enabled for an enemy or weapon. Also applies to any weapons held by an enemy since 'loadWeaponInfo' is called when an enemy drops their weapon. | ||
Line 78: | Line 66: | ||
=== <code>SharpWeaponJudgeType</code> === | === <code>SharpWeaponJudgeType</code> === | ||
This actor property controls the ''minimum'' modifier tier that a weapon can receive. Type: [[Difficulty scaling#weaponmodifier-s32-enum|<code>enum WeaponModifier</code>]]. | |||
If [[Difficulty scaling#LevelSensorMode|scaling]] is enabled, the weapon may receive modifiers from an even higher tier if point requirements are met. | |||
If [[#LevelSensorMode|scaling]] is enabled, the weapon may receive modifiers from an even higher tier if point requirements are met. | |||
Otherwise, the weapon will get modifiers from exactly the specified tier. | Otherwise, the weapon will get modifiers from exactly the specified tier. | ||
Line 91: | Line 78: | ||
==== <code>BymlWeaponModifier</code> (s32 enum) ==== | ==== <code>BymlWeaponModifier</code> (s32 enum) ==== | ||
There are three possible values for <code>weapons[].actors[].plus</code> in the LevelSensor config: | There are three possible values for <code>weapons[].actors[].plus</code> in the LevelSensor config: | ||
{| class="wikitable" | |||
{|class="wikitable" | ! Value | ||
! Value | ! Description | ||
|- | |- | ||
| -1 | | -1 | ||
Line 107: | Line 94: | ||
==== <code>WeaponModifier</code> (s32 enum) ==== | ==== <code>WeaponModifier</code> (s32 enum) ==== | ||
Internally and in other assets such as mubin map files, the following values are used instead: | Internally and in other assets such as mubin map files, the following values are used instead: | ||
{| class="wikitable" | |||
{|class="wikitable" | ! Value | ||
! Value | !Description | ||
|- | |- | ||
| 0 | | 0 | ||
Line 125: | Line 112: | ||
== Scaling algorithm == | == Scaling algorithm == | ||
=== <code>Ecosystem::LevelSensor::loadByml</code> === | === <code>Ecosystem::LevelSensor::loadByml</code> === | ||
This function is called by <code>Ecosystem::init</code> from <code>ksys::InitializeApp</code> | |||
Sets up byml structures for reading <code>Ecosystem/LevelSensor.byml</code> (stored in romfs as <code>Pack/[/cdn-cgi/l/email-protection <nowiki>[email protected]</nowiki>]/Ecosystem/LevelSensor.sbyml</code>) | |||
All information related to difficulty (enemy and weapon) scaling is stored in that configuration file. Human-readable versions dumped from [[Renotesfiles:game files/1.0.0 LevelSensor.yml|1.0.0]] and [[Renotesfiles:game files/1.5.0 LevelSensor.yml|1.5.0]] are included in the [https://github.com/leoetlino/botw-re-notes botw-re-notes] repo. A [[Renotesfiles:game files/1.0.0 1.5.0 LevelSensor.yml.diff|diff between 1.0.0 and 1.5.0]] is also in the repo. | |||
All information related to difficulty (enemy and weapon) scaling is stored in that configuration file. Human-readable versions dumped from [[ | |||
A [[ | |||
[1.4.0] Flag entries for Golden enemies, Igneo Talus Titan and Monk Maz Koshia were added to the kill point table. Weapon entries for the One-Hit Obliterator and Weapon_Sword_503 were also added to the weapon scaling list. They cannot receive any modifier. (Yes, the developers forgot to add golden enemies to the config in 1.3.0.) | [1.4.0] Flag entries for Golden enemies, Igneo Talus Titan and Monk Maz Koshia were added to the kill point table. Weapon entries for the One-Hit Obliterator and Weapon_Sword_503 were also added to the weapon scaling list. They cannot receive any modifier. (Yes, the developers forgot to add golden enemies to the config in 1.3.0.) | ||
=== <code>Ecosystem::LevelSensor::calculatePoints</code> === | === <code>Ecosystem::LevelSensor::calculatePoints</code> === | ||
Called by [[PlacementMgr]] when spawning actors. | |||
Called when | |||
Calculates weapon and enemy scaling points using a list of flags and configuration values. | Calculates weapon and enemy scaling points using a list of flags and configuration values. | ||
Line 146: | Line 130: | ||
Interestingly, the game calculates a single point value based on the kill counter flags but calculates two separate values for weapons and enemies with two different multipliers. This format makes it possible to easily change the scaling. | Interestingly, the game calculates a single point value based on the kill counter flags but calculates two separate values for weapons and enemies with two different multipliers. This format makes it possible to easily change the scaling. | ||
<source lang="c++"> | <source lang="c++"> | ||
float points = 0.0; | float points = 0.0; | ||
Line 157: | Line 140: | ||
this->enemy_points = points * this->byml["setting"].Level2EnemyPower; | this->enemy_points = points * this->byml["setting"].Level2EnemyPower; | ||
</source> | </source> | ||
In practice, settings have never been modified. 1.5.0 (which will likely be the last game update) still has the same Level2WeaponPower and Level2EnemyPower. | In practice, settings have never been modified. 1.5.0 (which will likely be the last game update) still has the same Level2WeaponPower and Level2EnemyPower. | ||
=== <code>Ecosystem::LevelSensor::loadWeaponInfo</code> === | === <code>Ecosystem::LevelSensor::loadWeaponInfo</code> === | ||
Called from treasure chest code, enemy actors{{Check}}, <code>Ecosystem::LevelSensor::loadActorInfo</code> | |||
Called from treasure chest code, enemy actors | |||
Given a weapon name, its modifier and current point status, this function returns the weapon to actually spawn and the modifier to use (if possible). | Given a weapon name, its modifier and current point status, this function returns the weapon to actually spawn and the modifier to use (if possible). | ||
Line 169: | Line 150: | ||
Pseudocode (1.0.0): | Pseudocode (1.0.0): | ||
<source lang="c++"> | <source lang="c++"> | ||
bool Ecosystem::LevelSensor::loadWeaponInfo(StringView weapon_to_look_up, | bool Ecosystem::LevelSensor::loadWeaponInfo(StringView weapon_to_look_up, | ||
Line 225: | Line 205: | ||
=== <code>Ecosystem::LevelSensor::loadActorInfo</code> === | === <code>Ecosystem::LevelSensor::loadActorInfo</code> === | ||
Analogous to <code>LevelSensor::loadWeaponInfo</code>. | Analogous to <code>LevelSensor::loadWeaponInfo</code>. | ||
Pseudocode (1.0.0): | Pseudocode (1.0.0): | ||
<source lang="c++"> | <source lang="c++"> | ||
if (actor->params["LevelSensorMode"] < 1) | if (actor->params["LevelSensorMode"] < 1) | ||
Line 277: | Line 255: | ||
== The Data == | == The Data == | ||
To make things easier to understand, here are links to: | To make things easier to understand, here are links to: | ||
* [https://docs.google.com/spreadsheets/d/e/2PACX-1vRSlyOD7FLAn1TUBn64Pu8Pld-WOfgcVByuywHMWvBTEV0j8potD1wkBs-MJJXf-gvEkpfItUCMqMk6/pubhtml kill point, enemy scaling and weapon scaling tables] | * [https://docs.google.com/spreadsheets/d/e/2PACX-1vRSlyOD7FLAn1TUBn64Pu8Pld-WOfgcVByuywHMWvBTEV0j8potD1wkBs-MJJXf-gvEkpfItUCMqMk6/pubhtml kill point, enemy scaling and weapon scaling tables] | ||
* an [https://objmap.zeldamods.org object map with all the scaling information] embedded into the object names. | * an [https://objmap.zeldamods.org object map with all the scaling information] embedded into the object names. | ||
This makes it possible to see both the required points for enemy/weapon upgrades, as well as all of the special cases extremely easily. | This makes it possible to see both the required points for enemy/weapon upgrades, as well as all of the special cases extremely easily. | ||
Line 292: | Line 267: | ||
== Ganon Blights == | == Ganon Blights == | ||
Ganon blights also have varying difficulty but follow a different system. Their health is determined by the base HP (set in GeneralParamList) and blight defeat flags. | Ganon blights also have varying difficulty but follow a different system. Their health is determined by the base HP (set in GeneralParamList) and blight defeat flags. | ||
<source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4 | <source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4 | ||
{ | { | ||
Line 313: | Line 286: | ||
return baseHp + multiplier * halfBaseHp; | return baseHp + multiplier * halfBaseHp; | ||
}</source> | }</source> | ||
Effectively, this means that the first blight Link fights will have 800+0×400 = 800 HP, the second will have 800+1×400 = 1200 HP, the third 800+2×400 = 1600 HP and the last one 800+3×400 = 2000 HP. | Effectively, this means that the first blight Link fights will have 800+0×400 = 800 HP, the second will have 800+1×400 = 1200 HP, the third 800+2×400 = 1600 HP and the last one 800+3×400 = 2000 HP. | ||
=== Special case 1: Castle Blights === | === Special case 1: Castle Blights === | ||
Castle blights have <code>IsRemainBoss</code> set to false in the root AI parameters, which sets flag 4. | Castle blights have <code>IsRemainBoss</code> set to false in the root AI parameters, which sets flag 4. | ||
Thus, blights that are fought in the Castle always have 800+3×400 = 2000 HP regardless of story progression. | Thus, blights that are fought in the Castle always have 800+3×400 = 2000 HP regardless of story progression. | ||
If flag 4 is set, the AI_Action_SiteBossDie code will NOT increment the & | If flag 4 is set, the AI_Action_SiteBossDie code will NOT increment the "defeated" counter. This means castle blights do not give any scaling points. | ||
=== Special case 2: DLC2 Blights === | === Special case 2: DLC2 Blights === | ||
Illusory Realm blights possess the <code>EnemySiteBoss_R</code> actor tag. This causes flag 8 to be set. So they will always have 500+4×250 = 1500 HP. | Illusory Realm blights possess the <code>EnemySiteBoss_R</code> actor tag. This causes flag 8 to be set. So they will always have 500+4×250 = 1500 HP. | ||
Interestingly, the Windblight AI function relies doesn't check the actor tag but the actor name instead. For flag 8 to be set, the actor name must be <code>Enemy_SiteBoss_Bow_R</code>. | Interestingly, the Windblight AI function relies doesn't check the actor tag but the actor name instead. For flag 8 to be set, the actor name must be <code>Enemy_SiteBoss_Bow_R</code>. | ||
<references /> | <references /> | ||
[[Category:Internals]] | [[Category:Internals]] | ||
[[Category:Game mechanics]] | [[Category:Game mechanics]] |