Difficulty scaling: Difference between revisions

Marked this version for translation
m (adding the translation functionality)
(Marked this version for translation)
Line 1: Line 1:
<translate>
<translate>


<!--T:1-->
'''Difficulty scaling''' is a mechanic in ''Breath of the Wild'' that results in enemies and weapons being progressively replaced by more powerful variants during a playthrough.
'''Difficulty scaling''' is a mechanic in ''Breath of the Wild'' that results in enemies and weapons being progressively replaced by more powerful variants during a playthrough.


== Points ==
== Points == <!--T:2-->
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.


<!--T:3-->
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 &#x3C; 10.
* The current kill count is &#x3C; 10.
Line 14: Line 16:
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.


<!--T:4-->
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.


<!--T:5-->
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]].
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]].


<!--T:6-->
The subsystem provides two functions ([[Difficulty scaling#Ecosystem::LevelSensor::scaleWeapon|<code>scaleWeapon</code>]] and [[Difficulty scaling#Ecosystem::LevelSensor::scaleActor|<code>scaleActor</code>]]) that may be called when a weapon or enemy actor is created.
The subsystem provides two functions ([[Difficulty scaling#Ecosystem::LevelSensor::scaleWeapon|<code>scaleWeapon</code>]] and [[Difficulty scaling#Ecosystem::LevelSensor::scaleActor|<code>scaleActor</code>]]) that may be called when a weapon or enemy actor is created.


== Scaling inhibitors ==
== Scaling inhibitors == <!--T:7-->
Both scaling functions will immediately return without doing anything if:
Both scaling functions will immediately return without doing anything if:
* [[WorldMgr]]::sInstance-&#x3E;stageType == 1 (Open World [[stage]])
* [[WorldMgr]]::sInstance-&#x3E;stageType == 1 (Open World [[stage]])
Line 27: Line 32:
Scaling will also be skipped if the current [[map area]] is 28. This corresponds to &#x22;HateruSea&#x22;, which is the Eventide Island area.
Scaling will also be skipped if the current [[map area]] is 28. This corresponds to &#x22;HateruSea&#x22;, which is the Eventide Island area.


== Weapons ==
== Weapons == <!--T:8-->
'scaleWeapon' is called (i.e. weapons may be scaled) for a weapon if:
'scaleWeapon' 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.
Line 35: Line 40:
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.


== Weapon bonuses ==
== Weapon bonuses == <!--T:9-->
=== Bonus values ===
=== Bonus values ===
Bonus values (e.g. the durability or attack power increase) are determined from [[ActorParam/GeneralParamList]] (with a copy of the information in [[ActorInfoData]]). Valid ranges and bonuses for each weapon are configured in the WeaponCommon section.
Bonus values (e.g. the durability or attack power increase) are determined from [[ActorParam/GeneralParamList]] (with a copy of the information in [[ActorInfoData]]). Valid ranges and bonuses for each weapon are configured in the WeaponCommon section.


==== amiibo ====
==== amiibo ==== <!--T:10-->
{|class="wikitable"
{|class="wikitable"
! Bonus !! Value that is used for the bonus effect
! Bonus !! Value that is used for the bonus effect
Line 60: Line 65:
|}
|}


==== Non-amiibo ====
==== Non-amiibo ==== <!--T:11-->
{|class="wikitable"
{|class="wikitable"
! Bonus !! Value that is used for the bonus effect
! Bonus !! Value that is used for the bonus effect
Line 81: Line 86:
|}
|}


=== Bonus types ===
=== Bonus types === <!--T:12-->
Weapon bonuses (e.g. Durability Up, Attack Up) are entirely random. Each bonus has equal probability.
Weapon bonuses (e.g. Durability Up, Attack Up) are entirely random. Each bonus has equal probability.


<!--T:13-->
{|class="wikitable"
{|class="wikitable"
! Bonus !! Available in modifier tiers
! Bonus !! Available in modifier tiers
Line 107: Line 113:
|}
|}


<!--T:14-->
Note that "Critical Hit" can only be selected if weaponCommonSharpWeaponAddCrit is true ''and'' if the weapon modifier tier is Blue/White. This means that it becomes impossible to get a weapon with a Critical Hit bonus after enough enemies have been killed.
Note that "Critical Hit" can only be selected if weaponCommonSharpWeaponAddCrit is true ''and'' if the weapon modifier tier is Blue/White. This means that it becomes impossible to get a weapon with a Critical Hit bonus after enough enemies have been killed.


== Enemies ==
== Enemies == <!--T:15-->
When loading enemies, the game will always try to scale enemies.
When loading enemies, the game will always try to scale enemies.


<!--T:16-->
However, the scaling function won't do anything if 'LevelSensorMode' is &#x3C; 1 and will leave the enemy and any weapons they may hold unscaled.
However, the scaling function won't do anything if 'LevelSensorMode' is &#x3C; 1 and will leave the enemy and any weapons they may hold unscaled.


<!--T:17-->
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.
Line 134: Line 143:
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 == <!--T:18-->


=== <code>LevelSensorMode</code> ===
=== <code>LevelSensorMode</code> === <!--T:19-->
This actor property controls whether scaling is enabled for an enemy or weapon. Also applies to any weapons held by an enemy since 'scaleWeapon' 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 'scaleWeapon' is called when an enemy drops their weapon.


<!--T:20-->
Note that this doesn't apply to weapons that are attached to a Hinox's necklace, because Hinoxes use a different underlying enemy actor which overrides the 'on weapon dropped' function and ignores 'LevelSensorMode'.
Note that this doesn't apply to weapons that are attached to a Hinox's necklace, because Hinoxes use a different underlying enemy actor which overrides the 'on weapon dropped' function and ignores 'LevelSensorMode'.


=== <code>SharpWeaponJudgeType</code> ===
=== <code>SharpWeaponJudgeType</code> === <!--T:21-->
This actor property controls the ''minimum'' modifier tier that a weapon can receive.
This actor property controls the ''minimum'' modifier tier that a weapon can receive.


<!--T:22-->
Internally and in assets such as [[Map unit|map units]], the following values are used for modifiers:
Internally and in assets such as [[Map unit|map units]], the following values are used for modifiers:
{| class="wikitable"
{| class="wikitable"
Line 165: Line 176:
|}
|}


<!--T:23-->
If [[Difficulty scaling#LevelSensorMode|scaling]] is enabled, the weapon may receive modifiers from an even higher tier if point requirements are met.
If [[Difficulty scaling#LevelSensorMode|scaling]] is enabled, the weapon may receive modifiers from an even higher tier if point requirements are met.


<!--T:24-->
Otherwise, the weapon will get modifiers from exactly the specified tier.
Otherwise, the weapon will get modifiers from exactly the specified tier.


<!--T:25-->
For example, 0 ('None') doesn't mean a weapon will never receive a modifier. It just means that the developers haven't forced the weapon to spawn with a blue/yellow modifier. If scaling requirements are satisfied, the weapon will receive blue or yellow modifiers.
For example, 0 ('None') doesn't mean a weapon will never receive a modifier. It just means that the developers haven't forced the weapon to spawn with a blue/yellow modifier. If scaling requirements are satisfied, the weapon will receive blue or yellow modifiers.


== Scaling algorithm ==
== Scaling algorithm == <!--T:26-->


=== <code>Ecosystem::LevelSensor::loadByml</code> ===
=== <code>Ecosystem::LevelSensor::loadByml</code> === <!--T:27-->
This function is called by <code>Ecosystem::init</code> from <code>ksys::InitializeApp</code>
This function is called by <code>Ecosystem::init</code> from <code>ksys::InitializeApp</code>


<!--T:28-->
Sets up byml structures for reading Ecosystem/[[LevelSensor.byml]].
Sets up byml structures for reading Ecosystem/[[LevelSensor.byml]].


=== <code>Ecosystem::LevelSensor::calculatePoints</code> ===
=== <code>Ecosystem::LevelSensor::calculatePoints</code> === <!--T:29-->
Called by [[PlacementMgr]] when spawning actors.
Called by [[PlacementMgr]] when spawning actors.


<!--T:30-->
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.


<!--T:31-->
All flags that are referenced in the configuration file are of the form <code>Defeated_%s_Num</code>, but technically the configuration format allows for other flags to be specified.
All flags that are referenced in the configuration file are of the form <code>Defeated_%s_Num</code>, but technically the configuration format allows for other flags to be specified.


<!--T:32-->
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++">
Line 192: Line 210:
     points += kill_count * kill_flag["point"];
     points += kill_count * kill_flag["point"];


<!--T:33-->
this->points = points;
this->points = points;
this->weapon_points = points * this->byml["setting"].Level2WeaponPower;
this->weapon_points = points * this->byml["setting"].Level2WeaponPower;
Line 198: Line 217:
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::scaleWeapon</code> ===
=== <code>Ecosystem::LevelSensor::scaleWeapon</code> === <!--T:34-->
Called from treasure chest code, enemy actors{{Check}}, <code>Ecosystem::LevelSensor::scaleActor</code>
Called from treasure chest code, enemy actors{{Check}}, <code>Ecosystem::LevelSensor::scaleActor</code>


<!--T:35-->
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).


<!--T:36-->
If the algorithm fails to find an appropriate weapon that satisfies all conditions (point requirements, weapon series, modifier), the originally specified weapon and modifier will be used directly.
If the algorithm fails to find an appropriate weapon that satisfies all conditions (point requirements, weapon series, modifier), the originally specified weapon and modifier will be used directly.


<!--T:37-->
Pseudocode (1.0.0):
Pseudocode (1.0.0):
<source lang="c++">
<source lang="c++">
Line 215: Line 237:
   // some checks using 'unknown' here which seems to be a pointer to the actor
   // some checks using 'unknown' here which seems to be a pointer to the actor


   for (weapon_table : this->byml["weapon"]) {
   <!--T:38-->
for (weapon_table : this->byml["weapon"]) {
     // find the first weapon entry for which the player has enough points
     // find the first weapon entry for which the player has enough points
     // with the specified name and modifier
     // with the specified name and modifier
Line 223: Line 246:
       float points_for_next_transition = entry["value"];
       float points_for_next_transition = entry["value"];


       if (this->weapon_points > points_for_next_transition &&
       <!--T:39-->
if (this->weapon_points > points_for_next_transition &&
           weapon_to_look_up == entry["name"] &&
           weapon_to_look_up == entry["name"] &&
           convert_to_modifier(entry["plus"]) == required_modifier) {
           convert_to_modifier(entry["plus"]) == required_modifier) {
Line 231: Line 255:
     }
     }


     if (i == -1)
     <!--T:40-->
if (i == -1)
       continue;
       continue;


     do {
     <!--T:41-->
do {
       entry = weapon_table["actors"][i];
       entry = weapon_table["actors"][i];


       // not_rank_up means there is no link between weapons;
       <!--T:42-->
// not_rank_up means there is no link between weapons;
       // this table is just used to look up modifiers.
       // this table is just used to look up modifiers.
       // so go down the list until there are no more entries for the requested weapon
       // so go down the list until there are no more entries for the requested weapon
Line 244: Line 271:
         break;
         break;


       // otherwise, just go down the list until we reach the end or a weapon which
       <!--T:43-->
// otherwise, just go down the list until we reach the end or a weapon which
       // requires more points. this will possibly upgrade the weapon (e.g. Knight -> Royal).
       // requires more points. this will possibly upgrade the weapon (e.g. Knight -> Royal).
       if (this->weapon_points <= entry["value"])
       if (this->weapon_points <= entry["value"])
         break;
         break;


       ++i;
       <!--T:44-->
++i;
     } while (i < weapon_table["actors"].size);
     } while (i < weapon_table["actors"].size);


     *weapon_to_use_name = entry["name"];
     <!--T:45-->
*weapon_to_use_name = entry["name"];
     *modifier_to_use = convert_to_modifier(entry["plus"]);
     *modifier_to_use = convert_to_modifier(entry["plus"]);
     return true;
     return true;
Line 260: Line 290:
</source>
</source>


=== <code>Ecosystem::LevelSensor::scaleActor</code> ===
=== <code>Ecosystem::LevelSensor::scaleActor</code> === <!--T:46-->
Analogous to <code>LevelSensor::scaleWeapon</code>.
Analogous to <code>LevelSensor::scaleWeapon</code>.


<!--T:47-->
Pseudocode (1.0.0):
Pseudocode (1.0.0):
<source lang="c++">
<source lang="c++">
Line 268: Line 299:
   return false;
   return false;


<!--T:48-->
if (actor_name.contains("Enemy")) {
if (actor_name.contains("Enemy")) {
   for (enemy_table : this->byml["enemy"]) {
   for (enemy_table : this->byml["enemy"]) {
Line 279: Line 311:
     }
     }


     if (i == -1)
     <!--T:49-->
if (i == -1)
       continue;
       continue;


     do {
     <!--T:50-->
do {
       entry = enemy_table["actors"][i];
       entry = enemy_table["actors"][i];
       if (this->enemy_points <= entry["value"])
       if (this->enemy_points <= entry["value"])
Line 289: Line 323:
     } while (i < enemy_table["actors"].size);
     } while (i < enemy_table["actors"].size);


     *actor_to_use = entry["name"];
     <!--T:51-->
*actor_to_use = entry["name"];
     return true;
     return true;
   }
   }
Line 295: Line 330:
}
}


<!--T:52-->
if (actor_name.contains("Weapon")) {
if (actor_name.contains("Weapon")) {
   weapon_name = actor->getWeaponName();
   weapon_name = actor->getWeaponName();
Line 301: Line 337:
     modifier = get_random_blue_modifier(actor->getWeaponName());
     modifier = get_random_blue_modifier(actor->getWeaponName());


   if (scaleWeapon(weapon_name, &weapon_to_use, &modifier_to_use)) {
   <!--T:53-->
if (scaleWeapon(weapon_name, &weapon_to_use, &modifier_to_use)) {
     actor->setProperty("SharpWeaponJudgeType", modifier_to_use);
     actor->setProperty("SharpWeaponJudgeType", modifier_to_use);
     *actor_to_use = weapon_to_use;
     *actor_to_use = weapon_to_use;
Line 310: Line 347:
</source>
</source>


== The Data ==
== The Data == <!--T:54-->
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]
Line 316: Line 353:
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.


== Ganon Blights ==
== Ganon Blights == <!--T:55-->
Ganon blights also have varying difficulty but follow a different system. Their health is determined by the base HP (set in [[ActorParam/GeneralParamList|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 [[ActorParam/GeneralParamList|GeneralParamList]]) and blight defeat flags.
<source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
<source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
Line 338: Line 375:
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 === <!--T:56-->
Castle blights have <code>IsRemainBoss</code> set to false in their root AI parameters (see [[AIDef:AI/SiteBossSpearRoot]] for example), which sets flag 4.
Castle blights have <code>IsRemainBoss</code> set to false in their root AI parameters (see [[AIDef:AI/SiteBossSpearRoot]] for example), which sets flag 4.


<!--T:57-->
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.


<!--T:58-->
If flag 4 is set, the [[AIDef:Action/SiteBossDie]] code will NOT increment the &#x22;defeated&#x22; counter. This means castle blights do not give any scaling points.
If flag 4 is set, the [[AIDef:Action/SiteBossDie]] code will NOT increment the &#x22;defeated&#x22; counter. This means castle blights do not give any scaling points.


=== Special case 2: DLC2 Blights ===
=== Special case 2: DLC2 Blights === <!--T:59-->
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.


<!--T:60-->
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 />
translator
745

edits