等级系统

From ZeldaMods (Breath of the Wild)
Revision as of 15:00, 11 May 2020 by Leoetlino (talk | contribs)
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 那里拿到的武器(阿卡莱古代研究所、英杰武器)不在上面的列表里,从而不升级。

武器加成

武器的加成有三个要素。 一是武器本身会被更新成更好的武器(骑士之剑 -> 王族之剑); 二是武器的攻击、防御、耐久等数值择一增加; 三是增加多少。

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.

加成的方向

要往哪个方向加成(加攻击还是加耐久?还是别的?)是随机的, 每个可能的方向有同样的机率被选到。

加成的方向 Available in bonus 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

需注意的是:

  • 最后爆击 "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

这段的意思是 amiibo 武器的加成都会是固定的值。譬如说,如果游戏决定增加攻击力,而且这个武器的攻击力最多 +20,那就会 +20。

非 amiibo

加成的方向 加成的程度
None 不加成 -
Attack up 攻击力 addAtkMin 到 addAtkMax 之间的随机数字
Durability up 耐久度提升 addLifeMin 到 addLifeMax 之间的随机数字
Long throw 远距离投掷 addThrowMin 到 addThrowMax 之间的随机数字
Multi-shot burst (bows) 多发弓发数增加 5-shot burst 五连发
Quick shot (bows) 速射 addRapidFireMin 到 addRapidFireMax 之间的随机数字
AddSurfMaster GlobalParameter::shieldSurfMasterFrictionRatio
Shield guard up 防御力 addGuardMin 到 addGuardMax 之间的随机数字

这段的意思是非 amiibo 武器的加成都是随机的值。譬如说,如果游戏决定增加攻击力,而且这个武器的攻击力从 +10 到 +20 都有可能,那最后生成的武器就会是 10 到 20 之间的随机数字(应该是均匀分布)。

敌人

游戏在载入敌人时会根据一定的规则升级敌人。

不过 'LevelSensorMode' < 1 的时候所有敌人(跟武器)都不会升级。

注记:有些敌人本身是无法升级的(例如带冰、火、电的蜥蜴), 这仅仅是代表他们自身就已经是最高级了。 而他们手上的武器只要满足下列条件就可以升级:

  • 'LevelSensorMode' 非零且经验值足够高
  • 或是某个 flag 被强制写入应该升级 the modifier tier is overridden using 'SharpWeaponJudgeType'.

[1.3.0] 版本加入了大师模式。大师模式里所有敌人都会先验地提高一级。这个部分跟 'LevelSensorMode' 造成的升级互不冲突而且可以叠加。更精确的控制是由下面这些参数决定的

参数 预设值 Description
IsHardModeActor false 是否只在大师模式出现
DisableRankUpForHardMode false 在大师模式里是否避免升级

在大师模式里,某些敌人的 IsHardModeActor、DisableRankUpForHardMode、LevelSensorMode这三个参数会同时被游戏设计师手动调整。调整的目的是让大师模式的后期还是会存在低等级的怪物(例如台地南边有个红色小波)。

属性

LevelSensorMode

这个 actor 属性决定了一个武器或敌人会不会升级,一个敌人的 LevelSensorMode 属性同时控制了它手上的武器升不升级。

不过西诺克斯的 LevelSensorMode 属性并不控制他脖子上的武器如何升级(到头来是脖子上而不是手上)。总之,西诺克斯用的是另一个机制来决定脖子上的武器如何升级,而且这个机制可以绕过 'LevelSensorMode'。

SharpWeaponJudgeType

这个属性决定某个敌人或武器“至少”会被加成多少。 (意即:就算经验值不够还是会加成。)

map units 这类的地方,下面这些值记载了游戏打算如何加成武器。

Description
0 None: 不加成
1 RandomBlue: 可能是蓝标也可能是黄标,随机决定。 (with weaponCommonSharpWeaponPer being the probability).
2 Blue: 至少蓝/白标,按经验值往上加。
3 Yellow: 至少黄标。
4 NoneForced (限于宝箱): Weapon will never spawn with any modifiers. 宝箱的这个属性会抹去其他一切升级。

如果 scaling 处在打开状态的话,武器有可能因为经验值足够的关系被升到更高级。

经验值不够则维持原升级(不会降级)。

换句话说,就算是设成 0 ('None') 的武器也不是永远不升级了, 而是游戏开发者有意给玩家最低等级的武器。 只要玩家的经验值高过某个门槛,就会得到 Blue 蓝标加成,再高一点 Yellow 黄标加成。

升级的计算过程

Ecosystem::LevelSensor::loadByml

这个函数会被 Ecosystem::init 呼叫, 后者是被 ksys::InitializeApp 呼叫。

它的功用是载入 byml 档案 Ecosystem/LevelSensor.byml

Ecosystem::LevelSensor::calculatePoints

每当游戏生成 actors 时,PlacementMgr 会呼叫这个函数。(actor 可以是武器或是敌人)

它会用各种表格跟各种经验值计算武器或敌人要升级多少。

游戏到目前为止都只用杀敌数 Defeated_%s_Num 来计算经验值并以此推测升级程度,但是从程式设计的角度来说可以用别的东西来计算经验值。

有趣的是,游戏利用杀敌数计算经验值时,会把一个“原始经验值”转化成“用在武器升级的经验值”跟“用在敌人升级的经验值”。这样设计师就可以在后期微调“原始”转化到“武器”的汇率;“敌人”亦同。

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; // 換算成敵人用分數

就我们能看到的部分,任天堂从来没有调整过这两个数值。

Ecosystem::LevelSensor::scaleWeapon

它负责处理武器的升级。有三个东西会呼叫它:宝箱、敌人[check]Ecosystem::LevelSensor::scaleActor

呼叫时给定一个武器的名字、预设的加成(至少多少)、跟目前玩家的经验值。然后这个函数会回传一个武器名称(可能跟给定的不一样),其中包括应当使用何种加成。

如果函数找半天找不到满足给定的条件武器、加成,游戏就会用预设的武器、预设的加成。(程式码里最后的 return false 会处理所有例外、失败的情形。)

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
    // 不同的武器系列儲存在不同的 table 裡,要枚舉 table 再枚舉 table 裡的細項
    i = -1; // 應該是 index,但是 -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; // ???這個 table 裡的武器不能升級,或是 entry 是 null (boundary check)

      // 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

处理敌人的升级。方法跟上面 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 // 沒找到
}

大数据

精确的数值可以在这里找到:

This makes it possible to see both the required points for enemy/weapon upgrades, as well as all of the special cases extremely easily.

厄咒加侬

厄咒加侬也有升级版,不过是依据的是另一个完全独立的系统。 具体升级的内容是:神兽里的厄咒加侬的血量是由玩家打死过多少别的加侬决定的。

__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
{
  const int baseHp = Enemy::getInitialHP(this); // 基礎血量(底)
  const int halfBaseHp = baseHp >> 1; // 除以 2(台)
  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;
}

换句换说,玩家第一个击倒的厄咒加侬的血量是 800,下一个 1200,再下一个 1600,最后一个 2000。

Special case 1: 城堡里的厄咒加侬

Castle blights have IsRemainBoss set to false in their root AI parameters (see AIDef:AI/SiteBossSpearRoot for example), which sets flag 4.

城堡里的厄咒加侬,不管是第几只,血量都是 800+3×400 = 2000。

打死城堡里的厄咒加侬并不计入经验值。(相应的计数器不会增加。)

Special case 2: 幻影空间里的厄咒加侬

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. 补充:幻影里的厄咒加侬基础血量是 500,其他情况是 800。

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.