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)
Cela signifie que le nivellement est toujours désactivé lors des Épreuves de l'Épée.
Il sera également désactivé lorsque le map area actuel est 28. Cela correspond à "HateruSea" qui est la zone de l'Île Finalis.
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')].
Remarque : les armes achetées dans une boutique ne peuvent pas recevoir de bonus car elles ne rentrent dans aucun des cas mentionnés ci-dessus.
Bonus des armes
Weapon scaling results in weapons being replaced by a different weapon (e.g. a Soldier's Bow which becomes a Royal Bow), or weapons gaining a random, bonus stat boost (e.g. Attack Up, Durability Up, etc.). The range of those boosts is also affected by weapon scaling.
There are two bonus tiers in the game: one for low-level types (which appear in blue/white in the game UI) and another for high-level types (yellow). Those that belong to the Yellow tier are usually superior to the other ones. For instance, Attack Up + is the superior variant of Attack Up and it typically grants a higher attack power boost compared to Attack Up.
Types de bonus
Pour les armes, les bonus (par exemple Durabilité ↑ ou Attaque ↑) sont entièrement aléatoires. Chaque type de bonus est équiprobable.
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 |
Remarques :
- Coup de grâce ne peut être sélectionné que si weaponCommonSharpWeaponAddCrit est true et le palier est bleu/blanc. Cela signifie qu'il devient impossible d'obtenir une arme avec le bonus Coup de grâce après avoir tué un nombre suffisant d'ennemis.
- Glisse ↑ (AddSurfMaster) est un bonus qui ne s'applique qu'aux boucliers et qui leur donne un coefficient de frottement plus faible pour la glisse sur bouclier. Ce type de bonus n'est pas utilisé dans le jeu.
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
Lorsque le jeu charge des ennemis, il essayera toujours de les niveler.
Cependant, la fonction de nivellement ne fera rien si 'LevelSensorMode' est < 1 et elle laissera dans ce cas l'ennemi et toute arme qu'il pourrait détenir à leurs états initiaux.
Remarque : Les ennemis qui ne sont sur aucune liste de mise à niveau (tels que les Lézalfos élémentaux) ne seront pas nivelés, mais leurs armes peuvent malgré tout recevoir des mises à niveau si :
- 'LevelSensorMode' est non nul et la condition qui porte sur le nombre de points d'arme est satisfaite
- ou alors le palier de bonus est outrepassé par 'SharpWeaponJudgeType'.
[1.3.0] En mode expert, tous les ennemis sont automatiquement montés d'un palier par défaut, à l'issue du nivellement, et ce indépendamment de 'LevenSensorMode'. Les acteurs peuvent recevoir deux paramètres supplémentaires :
Paramètre | Valeur par défaut | Description |
---|---|---|
IsHardModeActor | false | Contrôle l'apparition ou non d'un ennemi dans le Mode Expert. |
DisableRankUpForHardMode | false | Détermine si l'ennemi est automatiquement monté d'un palier ou non. |
En mode expert, IsHardModeActor, DisableRankUpForHardMode and LevelSensorMode sont combinés sur quelques acteurs pour garder des ennemis de bas niveau dans le monde (par exemple pour garder un Bokoblin rouge au sud du Plateau du Prélude.)
Propriétés
LevelSensorMode
Cette propriété actor détermine si le nivellement est activé pour un ennemi ou une arme. Elle s'applique également à toute arme que possède un ennemi puisque 'scaleWeapon' est appelée lorsqu'un ennemi lâche son arme.
Remarquez que ceci ne s'applique pas aux armes qui sont attachées au collier d'un Hinox, car les Hinox utilisent un acteur ennemi différent qui redéfinit (override) la fonction 'on weapon dropped' et ignore 'LevelSensorMode'.
SharpWeaponJudgeType
Cette propriété acteur détermine le palier minimum de bonus qu'une arme peut recevoir.
En interne, et dans des ressources telles que les map units, les valeurs suivantes sont utilisées pour régler les paliers :
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. |
Si le nivellement est activé, l'arme peut recevoir des bonus d'un palier supérieur à celui configuré si le joueur a assez de points.
Sinon, l'arme recevra un bonus qui appartient exactement au palier configuré.
Par exemple, 0 ('None') ne signifie pas qu'une arme ne recevra jamais de bonus. Cela veut juste dire que les développeurs n'ont pas forcé l'apparition d'un bonus bleu ou jaune. Si le joueur dispose d'un nombre suffisant de points, l'arme recevra des bonus bleus ou jaunes.
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.
Calcule les points d'ennemi et d'arme en utilisant une liste de flags et de valeurs de configuration.
Tous les flags auxquels le fichier de configuration fait référence sont de la forme Defeated_%s_Num
, mais techniquement le format de configuration permet l'utilisation d'autres flags.
Fait intéressant : le jeu calcule un seul total de points à partir des flags "kill counter", mais calcule deux valeurs distinctes pour les armes et les ennemis avec deux facteurs différents. Ce format permet de changer le nivellement facilement.
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;
}
En pratique, cela signifie que la première ombre que Link bat disposera de 800+0×400 = 800 HP, la seconde aura 800+1×400 = 1200 HP, la troisième 800+2×400 = 1600 HP et la dernière 800+3×400 = 2000 HP.
Cas spécial 1 : Les ombres du château
Les ombres du château d'Hyrule ont IsRemainBoss
réglé sur false dans les paramètres de leur AI root (voir AIDef:AI/SiteBossSpearRoot par exemple), ce qui active le flag 4.
Ainsi, les ombres combattues au château disposent toujours de 800+3×400 = 2000 HP quelle que soit la progression de l'histoire.
Si le flag 4 est activé, le code AIDef:Action/SiteBossDie n'incrémenta PAS le compteur Defeated. Cela signifie que les ombres du château ne donnent aucun point de nivellement.
Cas spécial 2 : Les ombres du DLC2
Les ombres dans l'univers onirique possèdent le tag acteur EnemySiteBoss_R
. Cela conduit à l'activation du flag 8. Donc celles-ci auront toujours 500+4×250 = 1500 HP.
Fait intéressant, la fonction AI de l'ombre de vent de Ganon (Windblight) ne vérifie pas le tag acteur mais plutôt le nom d'acteur. Pour que le flag 8 soit activé, le nom doit absolument être Enemy_SiteBoss_Bow_R
.