Nivellement de la difficulté
Difficulty scaling (nivellement de la difficulté) est un mécanisme dans Breath of the Wild qui fait que les ennemis et les armes sont progressivement remplacés par des variantes plus puissantes au cours d'une partie de jeu.
Points
Le système de nivellement se base sur un système de points. Tuer des ennemis est la seule manière d'en recevoir.
Quand un ennemi meurt, le jeu incrémente un flag 'Defeated_{SameGroupActorName}_Num' si toutes les conditions suivantes sont remplies :
- Le nombre de kills est strictement inférieur à 10.
- L'acteur n'a pas le flag NotCountDefeatedNum.
- Pour Maz Koshia : 'Defeated_Priest_Boss_Normal_Num' est 0.
- Pour Dark Beast Ganon : C'est la première fois que le boss final est battu. (Son flag Defeated a pour valeur maximale 1.)
- Pour les Ombres de Ganon : C'est la première fois que l'Ombre est battue au sein de la créature divine, ou dans l'univers onirique (DLC2). Les Ombres que vous battez au château d'Hyrule ne comptent pas.
Cela se produit à chaque fois qu'un ennemi quelconque meurt, même s'il ne joue pas forcément de rôle dans le système de points (voir ci-dessous) et même si le joueur n'est pas responsable pour leur mort.
Vu que des ennemis doivent être tués tout au long de la quête principale et que les boss sont considérés comme des ennemis, le nivellement de la difficulté est inévitable.
Only the defeated counter flags are stored in the save file. The Ecosystem::LevelSensor
subsystem is responsible for converting these kill counts to points using a configuration file.
The subsystem provides two functions (scaleWeapon
and scaleActor
) that may be called when a weapon or enemy actor is created.
Inhibiteurs du nivellement
Les deux fonctions de nivellement ne feront absolument rien si :
- WorldMgr::sInstance->stageType == 1 (Open World stage)
- and WorldMgr::sInstance->isAocField (la map actuelle est les épreuves de l'épée)
- and WorldMgr::sInstance->disableScaling (réglée à true à l'entrée des épreuves de l'épée)
Armes
'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 treasure chest drops: If SharpWeaponJudgeType is not 4, when AIDef:AI/TreasureBox initialises the drop actor.
- For Hinox weapons: The flag
{MapName}_Necklace_{i}_{HinoxName}_{ID}
is false. - For other enemy drops: The flag
{MapName}_WeaponDrop_{ID}
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.
Bonus
Scaling of weapons are divided into three steps: Does the weapon get replaced by a different (better weapon)? Which of the stats (among attack, guard, durability etc) is increased? And how much it is increased.
Types de bonus
Weapon bonuses (e.g. Durability Up, Attack Up) are entirely random. Each bonus has equal probability.
Bonus | Disponible pour les paliers |
---|---|
Attaque ↑ | Bleu/blanc et jaune |
Durabilité ↑ | Bleu/blanc et jaune |
Distance lancers ↑ | Jaune |
Tirs x N (arcs) | Jaune |
Visée rapide (arcs) | Jaune |
Glisse ↑ | Jaune |
Garde ↑ | Bleu/blanc et jaune |
Coup de grâce | Bleu/blanc |
ZoomRapid | Jaune |
Notes:
- "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.
- AddSurfMaster is a bonus that only applies to shields and gives them a lower friction for shield surfing. That bonus type is unused in the game.
Valeurs de bonus
Bonus values (e.g. the durability or attack power increase) are determined from bgparamlist (with a copy of the information in ActorInfoData). Valid ranges and bonuses for each weapon are configured in the WeaponCommon section.
amiibo
Bonus | Valeur qui est utilisée pour l'effet bonus |
---|---|
Aucun | - |
Attaque ↑ | addAtkMax |
Durabilité ↑ | addLifeMax |
Distance lancers ↑ | addThrowMax |
Tirs x N (arcs) | Tirs x 5 |
Visée rapide (arcs) | addRapidFireMin |
Glisse ↑ | GlobalParameter::shieldSurfMasterFrictionRatio |
Garde ↑ | addGuardMax |
En résumé, pour les armes amiibo, Attaque ↑ donne toujours le pouvoir d'attaque maximum. Il n'y a pas d'aléatoire.
Non amiibo
Bonus | Valeur qui est utilisée pour l'effet bonus |
---|---|
Aucun | - |
Attaque ↑ | Entier aléatoire entre addAtkMin et addAtkMax |
Durabilité ↑ | Entier aléatoire entre addLifeMin et addLifeMax |
Distance lancers ↑ | Flottant aléatoire entre addThrowMin et addThrowMax |
Tirs x N (arcs) | Tirs x 5 |
Visée rapide (arcs) | Flottant aléatoire entre addRapidFireMin et addRapidFireMax |
Glisse ↑ | GlobalParameter::shieldSurfMasterFrictionRatio |
Garde ↑ | Entier aléatoire entre addGuardMin et addGuardMax |
En résumé, pour les armes non issues d'amiibo, Attaque ↑ donne un pouvoir d'attaque aléatoire.
Ennemis
When loading enemies, the game will always try to scale enemies.
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:
- 'LevelSensorMode' is non zero.
- Weapon point requirements are satisfied
- 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:
Paramètre | Valeur par défaut | Description |
---|---|---|
IsHardModeActor | false | Contrôle l'apparition ou non d'un ennemi dans le Mode Expert. |
DisableRankUpForHardMode | false | 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).
Propriétés
LevelSensorMode
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.
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'.
SharpWeaponJudgeType
This actor property controls the minimum modifier tier that a weapon can receive.
Internally and in assets such as map units, the following values are used for modifiers:
Valeur | Description |
---|---|
0 | None: No modifiers. |
1 | RandomBlue: Weapon will randomly get at least a blue modifier (with weaponCommonSharpWeaponPer being the probability).
|
2 | Blue: Weapon will get at least a blue modifier. |
3 | Yellow: Weapon will get at least a yellow modifier. |
4 | NoneForced (chests only): Weapon will never spawn with any modifiers. This overrides regular scaling. |
If 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.
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.
Algorithme de nivellement
Ecosystem::LevelSensor::loadByml
Cette fonction est appelée par Ecosystem::init
depuis ksys::InitializeApp
Met en place les structures byml pour la lecture de Ecosystem/LevelSensor.byml.
Ecosystem::LevelSensor::calculatePoints
Appelée par PlacementMgr au moment du spawn des acteurs.
Calculates weapon and enemy scaling points using a list of flags and configuration values.
All flags that are referenced in the configuration file are of the form Defeated_%s_Num
, but technically the configuration format allows for other flags to be specified.
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.
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->points = points;
this->weapon_points = points * this->byml["setting"].Level2WeaponPower;
this->enemy_points = points * this->byml["setting"].Level2EnemyPower;
En pratique, ces paramètres n'ont jamais été modifiés. 1.5.0 (qui sera sans doute la dernière mise à jour du jeu) a toujours les mêmes Level2WeaponPower et Level2EnemyPower.
Ecosystem::LevelSensor::scaleWeapon
Called from treasure chest code, enemy actors[check], Ecosystem::LevelSensor::scaleActor
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).
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.
Pseudocode (1.0.0) :
bool Ecosystem::LevelSensor::scaleWeapon(const sead::SafeString& 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
//
for (weapon_table : this->byml["weapon"]) {
// find the first weapon entry for which the player has enough points
// with the specified name and modifier
i = -1;
for (j = 0; j < weapon_table["actors"].size; ++j) {
entry = weapon_table["actors"][j];
float points_for_next_transition = entry["value"];
//
if (this->weapon_points > points_for_next_transition &&
weapon_to_look_up == entry["name"] &&
convert_to_modifier(entry["plus"]) == required_modifier) {
i = j;
break;
}
}
//
if (i == -1)
continue;
//
do {
entry = weapon_table["actors"][i];
//
// not_rank_up means there is no link between weapons;
// this table is just used to look up modifiers.
// 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.
if (weapon_table["not_rank_up"] && entry["name"] != weapon_to_look_up)
break;
//
// 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).
if (this->weapon_points <= entry["value"])
break;
//
++i;
} while (i < weapon_table["actors"].size);
//
*weapon_to_use_name = entry["name"];
*modifier_to_use = convert_to_modifier(entry["plus"]);
return true;
}
return false; // cannot scale up
}
Ecosystem::LevelSensor::scaleActor
Analogue à LevelSensor::scaleWeapon
.
Pseudocode (1.0.0) :
if (actor->params["LevelSensorMode"] < 1)
return false;
//
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;
}
}
//
if (i == -1)
continue;
//
do {
entry = enemy_table["actors"][i];
if (this->enemy_points <= entry["value"])
break;
++i;
} while (i < enemy_table["actors"].size);
//
*actor_to_use = entry["name"];
return true;
}
return false; // cannot scale up
}
//
if (actor_name.contains("Weapon")) {
weapon_name = actor->getWeaponName();
modifier = actor->params["SharpWeaponJudgeType"];
if (modifier == WeaponModifier::RandomBlue)
modifier = get_random_blue_modifier(actor->getWeaponName());
//
if (scaleWeapon(weapon_name, &weapon_to_use, &modifier_to_use)) {
actor->setProperty("SharpWeaponJudgeType", modifier_to_use);
*actor_to_use = weapon_to_use;
return true;
}
return false; // cannot scale up
}
Les données
Pour rendre tout cela plus facile à comprendre, voici des liens vers :
- des tableaux de points, de nivellement des ennemis et des armes
- une carte objets (object map) avec les infos de nivellement.
Cela permet de voir très aisément à la fois les points nécessaires pour mettre à niveau les ennemis ou les armes, ainsi que tous les cas spéciaux.
Ombres de Ganon
Les Ombres de Ganon varient elles aussi en difficulté mais suivent un système différent. Leur HP est déterminé par le HP de base (réglé dans bgparamlist) et les flags de défaite des ombres.
__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
{
const int baseHp = Enemy::getInitialHP(this);
const int halfBaseHp = baseHp >> 1;
const bool dieGanonWind = hasFlag_Die_PGanonWind(0);
const bool dieGanonWater = hasFlag_Die_PGanonWater(0);
const bool dieGanonFire = hasFlag_Die_PGanonFire(0);
const bool dieGanonElectric = hasFlag_Die_PGanonElectric(0);
const int flags = this->siteBossFlags & 0xFFFFFFFC;
int multiplier;
if ( flags == 4 )
multiplier = 3;
else if ( flags == 8 )
multiplier = 4;
else
multiplier = dieGanonFire + dieGanonWind + dieGanonWater + dieGanonElectric;
return baseHp + multiplier * halfBaseHp;
}
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
Castle blights have IsRemainBoss
set to false in their root AI parameters (see AIDef:AI/SiteBossSpearRoot for example), which sets flag 4.
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 AIDef:Action/SiteBossDie code will NOT increment the "defeated" counter. This means castle blights do not give any scaling points.
Special case 2: DLC2 Blights
Illusory Realm blights possess the EnemySiteBoss_R
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 Enemy_SiteBoss_Bow_R
.