Difficulty scaling: Difference between revisions

no edit summary
imported>Leoetlino
(Created page with "== Overview == Difficulty scaling in ''The Legend of Zelda: Breath of the Wild'' is based on a point system. Killing enemies is the only way to receive points. Enemies and w...")
 
imported>Leoetlino
No edit summary
Line 88: Line 88:
== <code>WeaponModifier</code> ==
== <code>WeaponModifier</code> ==


### <code>BymlWeaponModifier</code> (s32 enum) There are three possible values for <code>weapons[].actors[].plus</code> in the LevelSensor config:
=== <code>BymlWeaponModifier</code> (s32 enum) ===
There are three possible values for <code>weapons[].actors[].plus</code> in the LevelSensor config:


{|class="wikitable"
{|class="wikitable"
Line 103: Line 104:
|}
|}


### <code>WeaponModifier</code> (s32 enum) Internally and in other assets such as mubin map files, the following values are used instead:
=== <code>WeaponModifier</code> (s32 enum) ===
Internally and in other assets such as mubin map files, the following values are used instead:


{|class="wikitable"
{|class="wikitable"
Line 127: Line 129:
Sets up byml structures for reading <code>Ecosystem/LevelSensor.byml</code> (stored in romfs as <code>Pack/Bootup.pack@/Ecosystem/LevelSensor.sbyml</code>)
Sets up byml structures for reading <code>Ecosystem/LevelSensor.byml</code> (stored in romfs as <code>Pack/Bootup.pack@/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 repo.
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 [[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.
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.


[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.)
Line 143: Line 145:
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.


```c++ float points = 0.0; for (kill_flag : this-&gt;byml[&quot;flag&quot;]) int kill_count = GameData::getIntegerFlag(kill_flag[&quot;name&quot;]); points += kill_count * kill_flag[&quot;point&quot;];
<source lang="c++">
float points = 0.0;
for (kill_flag : this->byml["flag"])
    int kill_count = GameData::getIntegerFlag(kill_flag["name"]);
    points += kill_count * kill_flag["point"];


this-&gt;points = points; this-&gt;weapon_points = points * this-&gt;byml[&quot;setting&quot;].Level2WeaponPower; this-&gt;enemy_points = points * this-&gt;byml[&quot;setting&quot;].Level2EnemyPower; ```
this->points = points;
this->weapon_points = points * this->byml["setting"].Level2WeaponPower;
this->enemy_points = points * this->byml["setting"].Level2EnemyPower;
</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.
Line 159: Line 168:
Pseudocode (1.0.0):
Pseudocode (1.0.0):


```c++ bool Ecosystem::LevelSensor::loadWeaponInfo(StringView weapon_to_look_up, WeaponModifier required_modifier, const char** weapon_to_use_name, WeaponModifier* modifier_to_use, void* unknown) { // some checks using 'unknown' here which seems to be a pointer to the actor
<source lang="c++">
bool Ecosystem::LevelSensor::loadWeaponInfo(StringView weapon_to_look_up,
                                            WeaponModifier required_modifier,
                                            const char** weapon_to_use_name,
                                            WeaponModifier* modifier_to_use,
                                            void* unknown)
{
  // some checks using 'unknown' here which seems to be a pointer to the actor


<pre>for (weapon_table : this-&gt;byml[&quot;weapon&quot;]) {
  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
  i = -1;
    i = -1;
  for (j = 0; j &lt; weapon_table[&quot;actors&quot;].size; ++j) {
    for (j = 0; j < weapon_table["actors"].size; ++j) {
    entry = weapon_table[&quot;actors&quot;][j];
      entry = weapon_table["actors"][j];
    float points_for_next_transition = entry[&quot;value&quot;];
      float points_for_next_transition = entry["value"];


    if (this-&gt;weapon_points &gt; points_for_next_transition &amp;&amp;
      if (this->weapon_points > points_for_next_transition &&
        weapon_to_look_up == entry[&quot;name&quot;] &amp;&amp;
          weapon_to_look_up == entry["name"] &&
        convert_to_modifier(entry[&quot;plus&quot;]) == required_modifier) {
          convert_to_modifier(entry["plus"]) == required_modifier) {
      i = j;
        i = j;
      break;
        break;
      }
     }
     }
  }


  if (i == -1)
    if (i == -1)
    continue;
      continue;


  do {
    do {
    entry = weapon_table[&quot;actors&quot;][i];
      entry = weapon_table["actors"][i];


    // not_rank_up means there is no link between weapons;
      // 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
    // or until we reach a modifier that requires more points.
      // or until we reach a modifier that requires more points.
    if (weapon_table[&quot;not_rank_up&quot;] &amp;&amp; entry[&quot;name&quot;] != weapon_to_look_up)
      if (weapon_table["not_rank_up"] && entry["name"] != weapon_to_look_up)
      break;
        break;


    // otherwise, just go down the list until we reach the end or a weapon which
      // 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 -&gt; Royal).
      // requires more points. this will possibly upgrade the weapon (e.g. Knight -> Royal).
    if (this-&gt;weapon_points &lt;= entry[&quot;value&quot;])
      if (this->weapon_points <= entry["value"])
      break;
        break;


    ++i;
      ++i;
  } while (i &lt; weapon_table[&quot;actors&quot;].size);
    } while (i < weapon_table["actors"].size);


  *weapon_to_use_name = entry[&quot;name&quot;];
    *weapon_to_use_name = entry["name"];
  *modifier_to_use = convert_to_modifier(entry[&quot;plus&quot;]);
    *modifier_to_use = convert_to_modifier(entry["plus"]);
   return true;
    return true;
  }
   return false; // cannot scale up
}
}
return false;  // cannot scale up</pre>
</source>
} ```


== <code>Ecosystem::LevelSensor::loadActorInfo</code> ==
== <code>Ecosystem::LevelSensor::loadActorInfo</code> ==
Line 211: Line 228:
Pseudocode (1.0.0):
Pseudocode (1.0.0):


```c++ if (actor-&gt;params[&quot;LevelSensorMode&quot;] &lt; 1) return false;
<source lang="c++">
if (actor->params["LevelSensorMode"] < 1)
  return false;


if (actor_name.contains(&quot;Enemy&quot;)) { for (enemy_table : this-&gt;byml[&quot;enemy&quot;]) { i = -1; for (j = 0; j &lt; enemy_table[&quot;actors&quot;].size; ++j) { entry = enemy_table[&quot;actors&quot;][j]; if (entry[&quot;name&quot;] == actor_name &amp;&amp; this-&gt;enemy_points &gt; entry[&quot;value&quot;]) { i = j; break; } }
if (actor_name.contains("Enemy")) {
  for (enemy_table : this->byml["enemy"]) {
    i = -1;
    for (j = 0; j < enemy_table["actors"].size; ++j) {
      entry = enemy_table["actors"][j];
      if (entry["name"] == actor_name && this->enemy_points > entry["value"]) {
        i = j;
        break;
      }
    }


<pre>  if (i == -1)
    if (i == -1)
    continue;
      continue;


  do {
    do {
    entry = enemy_table[&quot;actors&quot;][i];
      entry = enemy_table["actors"][i];
    if (this-&gt;enemy_points &lt;= entry[&quot;value&quot;])
      if (this->enemy_points <= entry["value"])
      break;
        break;
    ++i;
      ++i;
  } while (i &lt; enemy_table[&quot;actors&quot;].size);
    } while (i < enemy_table["actors"].size);


  *actor_to_use = entry[&quot;name&quot;];
    *actor_to_use = entry["name"];
  return true;
    return true;
}
  }
return false;  // cannot scale up</pre>
  return false;  // cannot scale up
}
}


if (actor_name.contains(&quot;Weapon&quot;)) { weapon_name = actor-&gt;getWeaponName(); modifier = actor-&gt;params[&quot;SharpWeaponJudgeType&quot;]; if (modifier == WeaponModifier::RandomBlue) modifier = get_random_blue_modifier(actor-&gt;getWeaponName());
if (actor_name.contains("Weapon")) {
  weapon_name = actor->getWeaponName();
  modifier = actor->params["SharpWeaponJudgeType"];
  if (modifier == WeaponModifier::RandomBlue)
    modifier = get_random_blue_modifier(actor->getWeaponName());


<pre>if (loadWeaponInfo(weapon_name, &amp;weapon_to_use, &amp;modifier_to_use)) {
  if (loadWeaponInfo(weapon_name, &weapon_to_use, &modifier_to_use)) {
  actor-&gt;setProperty(&quot;SharpWeaponJudgeType&quot;, modifier_to_use);
    actor->setProperty("SharpWeaponJudgeType", modifier_to_use);
  *actor_to_use = weapon_to_use;
    *actor_to_use = weapon_to_use;
   return true;
    return true;
  }
   return false; // cannot scale up
}
}
return false;  // cannot scale up</pre>
</source>
} ```


== The Data ==
== The Data ==
Line 246: Line 279:


* [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://f.leolam.fr/botw-map/ 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.


For the map, a few special name suffixes were added:
The map is a modified version of [https://github.com/MrCheeze/botw-object-map MrCheeze's object map] with a few special name suffixes added:
 
* <code>:NO_SCALING</code>: Enemy or weapon won't be scaled.
* <code>:NO_SCALING</code>: Enemy or weapon won't be scaled.
* <code>:NO_RANKUP</code>: Enemy will not be automatically ranked up in master mode.
* <code>:NO_RANKUP</code>: Enemy will not be automatically ranked up in master mode.
Line 261: Line 293:
Their health is determined from the base HP (set in GeneralParamList) and from blight defeat flags.
Their health is determined from the base HP (set in GeneralParamList) and from blight defeat flags.


<source lang="cpp">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
<source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
{
{
   const int baseHp = Enemy::getInitialHP(this);
   const int baseHp = Enemy::getInitialHP(this);
Line 279: Line 311:
   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.


Anonymous user