等级系统
Difficulty scaling(等级系统)是旷野之息用来控制敌人跟武器等级的一个机制。你会注意到,在游玩的过程中,同一个地点的敌人可能在若干次血月后变成了更强的敌人,且其手上的武器也有所变化。
经验值
等级系统是根据存档里的一个“经验值”系统决定的。只有怪物死掉可以获得经验值。(意即解神庙、解任务之类的事情不算。)
每当有敌人死掉,游戏就会想增加 Defeated_{SameGroupActorName}_Num 这个计数器。 花括号的意思是,如果是波克布林死掉,就会更新波克布林专属的计数器。 不过,只有下列条件满足的时候,计数器才会真的增加。
- 计数器本身还不到 10(意思是大部分的怪物只有杀死前十只才算)
- 死掉的那个敌人没有 NotCountDefeatedNum flag(意思是某些特殊情况下不算)
- 导师米兹·乔希亚专用的计数器 'Defeated_Priest_Boss_Normal_Num' 是 0(意思是只有杀第一次才算)
- 魔兽加侬也是只有第一次才算
- 厄咒加侬只有在神兽里打败的才算,原始游戏打一次,DLC 跟幻影打一次,总共两次(在城堡里打的不算)
需要注意的是只要有任何敌人死掉,不管死法,也不管是不是林克亲手杀的,游戏都会试图增加计数器。下面会讲到哪些情况下不会算。
由于主线剧情会要求玩家杀怪,所以玩家或多或少都会累积经验值,并且相应地导致等级上升。
游戏的存档里会储存死了几只怪的计数器,有了计数之后 Ecosystem::LevelSensor 这个子系统会用 #Ecosystem::LevelSensor::calculatePoints 计算你的经验值。不同的怪会给你不同的经验,表格存在这里 #Ecosystem::LevelSensor::loadByml.
算出经验值之后,这个子系统会用这两个函数 #Ecosystem::LevelSensor::scaleWeapon 跟 #Ecosystem::LevelSensor::scaleActor 来决定敌人跟武器应该分别是什么等级。
阻止升级的情况
下面这个情况会导致敌人跟武器维持原样(总而言之就是林克在剑之考验里)。
- WorldMgr::sInstance->stageType == 1 (Open World stage)
- 且 WorldMgr::sInstance->isAocField (current map is Trial of the Sword)
- 且 WorldMgr::sInstance->disableScaling (set to true when entering Trial of the Sword)
另一种会跳过升级的情况是 map area 等于 28, 也就是林克位在 "HateruSea",野外的考验的塞哈特诺岛。
武器
下面这些武器在生成的时候会呼叫它的 'scaleWeapon' 函数
- 单独存在的武器: The actor property 'LevelSensorMode' is higher than 1 and it wasn't already picked up. 设定上可以升级而且还没被捡走
- 宝箱里的武器: If SharpWeaponJudgeType is not 4, when AIDef:AI/TreasureBox initialises the drop actor. 同样是设定上允许升级
- 西诺克斯脖子上的武器: The flag
{MapName}_Necklace_{i}_{HinoxName}_{ID}
is false. - 敌人手上的武器: 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' 庙里的守护者)].
注记:从 NPC 那里拿到的武器(阿卡莱古代研究所、英杰武器)不在上面的列表里,从而不升级。
武器加成
武器的加成有三个要素。 一是武器本身会被更新成更好的武器(骑士之剑 -> 王族之剑); 二是武器的攻击、防御、耐久等数值择一增加; 三是增加多少。
加成的方向
要往哪个方向加成(加攻击还是加耐久?还是别的?)是随机的, 每个可能的方向有同样的机率被选到。
加成的方向 | Available in modifier tiers |
---|---|
Attack up 攻击力 | Blue/White and Yellow |
Durability up 耐久度提升 | Blue/White and Yellow |
Long throw 远距离投掷 | Yellow |
Multi-shot burst (bows) 多发弓发数增加 | Yellow |
Quick shot (bows) 速射 | Yellow |
AddSurfMaster | Yellow |
Shield guard up 防御力 | Blue/White and Yellow |
Critical Hit 最后爆击 | Blue/White |
ZoomRapid | Yellow |
蓝标、白标(Blue/White)是比较低阶的升级,标记成“某某某提升”。黄标(Yellow)是高阶的升级,标记成“某某某大提升”。以攻击力为例,蓝标可能会增加 10~20 的攻击力(攻击力提升 +14),而黄标会增加 20~30(攻击力大提升 +24)。
需注意的是:
- 最后爆击 "Critical Hit" 只可能在 weaponCommonSharpWeaponAddCrit 是 true 且 加成的 tier 程度是 White/Blue (白色或蓝色的“提升”而不是黄色的“大提升”)时出现。 但是随著经验值的提升,所有武器的加成都会渐渐变成 Yellow (黄色的“大提升”),所以“最后爆击”在游戏后期就不会再出现。
- 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.
加成的程度
武器加成多少(如攻击力应当提升多少)的数值来源于这个表 bgparamlist (with a copy of the information in ActorInfoData). Valid ranges and bonuses for each weapon are configured in the WeaponCommon section.
amiibo
加成的方向 | 加成的程度 |
---|---|
None 不加成 | - |
Attack up 攻击力 | addAtkMax |
Durability up 耐久度提升 | addLifeMax |
Long throw 远距离投掷 | addThrowMax |
Multi-shot burst (bows) | 5-shot burst |
Quick shot (bows) | addRapidFireMin |
AddSurfMaster | GlobalParameter::shieldSurfMasterFrictionRatio |
Shield guard up | addGuardMax |
In summary, for amiibo weapons, an attack up always brings it to the greatest possible attack power. No randomness is involved.
Non-amiibo
Bonus | Value that is used for the bonus effect |
---|---|
None | - |
Attack up | Random integer between addAtkMin and addAtkMax |
Durability up | Random integer between addLifeMin and addLifeMax |
Long throw | Random float between addThrowMin and addThrowMax |
Multi-shot burst (bows) | 5-shot burst |
Quick shot (bows) | Random float between addRapidFireMin and addRapidFireMax |
AddSurfMaster | GlobalParameter::shieldSurfMasterFrictionRatio |
Shield guard up | Random integer between addGuardMin and addGuardMax |
In summary, for non-amiibo weapons, an attack up results in a randomized attack power.
Enemies
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:
Parameter | Default | Description |
---|---|---|
IsHardModeActor | false | Controls whether an enemy only shows up in Master Mode. |
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).
Properties
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:
Value | 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.
Scaling algorithm
Ecosystem::LevelSensor::loadByml
This function is called by Ecosystem::init
from ksys::InitializeApp
Sets up byml structures for reading Ecosystem/LevelSensor.byml.
Ecosystem::LevelSensor::calculatePoints
Called by PlacementMgr when spawning actors.
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;
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.
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
Analogous to 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
}
The Data
To make things easier to understand, here are links to:
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 also have varying difficulty but follow a different system. Their health is determined by the base HP (set in bgparamlist) and blight defeat flags.
__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
.