等級系統

From ZeldaMods (Breath of the Wild)
Revision as of 12:30, 11 May 2020 by Leoetlino (talk | contribs) (Created page with "Critical Hit 最後爆擊")
Jump to navigation Jump to search
Other languages:

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

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.

Bonus values

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 Value that is used for the bonus effect
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.