Difference between revisions of "Difficulty scaling/zh"

From ZeldaMods (Breath of the Wild)
Jump to navigation Jump to search
 
Line 184: Line 184:
 
|-
 
|-
 
| 1
 
| 1
| '''RandomBlue''':  可能是藍標也可能是黃標,隨機決定。 (with <code>weaponCommonSharpWeaponPer</code> being the probability).
+
| '''RandomBlue''':  要麼是藍標以上的加成,要麼不加成,隨機決定。 (with <code>weaponCommonSharpWeaponPer</code> being the probability).
 
|-
 
|-
 
| 2
 
| 2

Latest revision as of 19:37, 23 May 2020

Other languages:
English • ‎français • ‎中文

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 那裡拿到的武器(阿卡莱古代研究所、英傑武器、怪物商人)不在上面的列表裡,從而不升級。

武器加成

武器的加成有三個要素。 一是武器本身會被更新成更好的武器(騎士之劍 -> 王族之劍); 二是武器的攻擊、防禦、耐久等數值擇一增加; 三是增加多少。

藍標、白標(Blue/White)是比較低階的升級,標記成「某某某提升」。黃標(Yellow)是高階的升級,標記成「某某某大提升」。以攻擊力為例,藍標可能會增加 10~20 的攻擊力(攻擊力提升 +14),而黃標會增加 20~30(攻擊力大提升 +24)。

加成的方向

要往哪個方向加成(加攻擊還是加耐久?還是別的?)是隨機的, 每個可能的方向有同樣的機率被選到。

加成的方向 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.

加成的程度

武器加成多少(如攻擊力應當提升多少)的數值來源於這個表 ActorParam/GeneralParamList (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 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.