Difficulty scaling/zh: Difference between revisions

no edit summary
(Created page with "'''Yellow''': 至少黃標。")
No edit summary
 
(49 intermediate revisions by 3 users not shown)
Line 1: Line 1:
<languages/>
<languages/>


Multi-shot burst (bows) 多發弓發數增加
'''Difficulty scaling'''(等級系統)是曠野之息用來控制敵人跟武器等級的一個機制。你會注意到,在遊玩的過程中,同一個地點的敵人可能在若干次血月後變成了更強的敵人,且其手上的武器也有所變化。


== 經驗值 ==  
== 經驗值 ==  
Line 27: Line 27:
== 阻止升級的情況 ==  
== 阻止升級的情況 ==  


下面這個情況會導致敵人跟武器維持原樣(總而言之就是林克在劍之考驗裡)。
下面這個情況會導致敵人跟武器維持原樣。
* WorldMgr::sInstance->stageType == 1 (Open World stage)
* WorldMgr::sInstance->stageType == 1 (Open World stage)
* 且 WorldMgr::sInstance->isAocField (current map is Trial of the Sword)
* 且 WorldMgr::sInstance->isAocField (current map is Trial of the Sword)
* 且 WorldMgr::sInstance->disableScaling (set to true when entering Trial of the Sword)
* 且 WorldMgr::sInstance->disableScaling (set to true when entering Trial of the Sword)
總而言之,遊戲不會升級劍之考驗裡的武器和敵人。
另一種會跳過升級的情況是 [[map area]] 等於 28, 也就是林克位在 "HateruSea",野外的考驗的塞哈特諾島。
另一種會跳過升級的情況是 [[map area]] 等於 28, 也就是林克位在 "HateruSea",野外的考驗的塞哈特諾島。


Line 42: Line 45:
* '''敵人手上的武器''': The flag <code>{MapName}_WeaponDrop_{ID}</code> is false, '''and''' [the actor property 'LevelSensorMode' is higher than 1 ''or'' the enemy is a Guardian Scout ('Enemy_Guardian_Mini' 廟裡的守護者)].
* '''敵人手上的武器''': The flag <code>{MapName}_WeaponDrop_{ID}</code> is false, '''and''' [the actor property 'LevelSensorMode' is higher than 1 ''or'' the enemy is a Guardian Scout ('Enemy_Guardian_Mini' 廟裡的守護者)].


註記:從 NPC 那裡拿到的武器(阿卡莱古代研究所、英傑武器)不在上面的列表裡,從而不升級。
註記:從 NPC 那裡拿到的武器(阿卡莱古代研究所、英傑武器、怪物商人)不在上面的列表裡,從而不升級。


== 武器加成 ==  
== 武器加成 ==  
Line 50: Line 53:
二是武器的攻擊、防禦、耐久等數值擇一增加;
二是武器的攻擊、防禦、耐久等數值擇一增加;
三是增加多少。
三是增加多少。
藍標、白標(Blue/White)是比較低階的升級,標記成「某某某提升」。黃標(Yellow)是高階的升級,標記成「某某某大提升」。以攻擊力為例,藍標可能會增加 10~20 的攻擊力(攻擊力提升 +14),而黃標會增加 20~30(攻擊力大提升 +24)。


=== 加成的方向 ===  
=== 加成的方向 ===  
Line 56: Line 61:


{|class="wikitable"
{|class="wikitable"
! 加成的方向 !! Available in modifier tiers
! 加成的方向 !! Available in bonus tiers
|-
|-
|-
|-
Line 77: Line 82:
| ZoomRapid || Yellow
| ZoomRapid || Yellow
|}
|}
藍標、白標(Blue/White)是比較低階的升級,標記成「某某某提升」。黃標(Yellow)是高階的升級,標記成「某某某大提升」。以攻擊力為例,藍標可能會增加 10~20 的攻擊力(攻擊力提升 +14),而黃標會增加 20~30(攻擊力大提升 +24)。


需注意的是:
需注意的是:
Line 120: Line 123:
| Attack up 攻擊力 || addAtkMin 到 addAtkMax 之間的隨機數字
| Attack up 攻擊力 || addAtkMin 到 addAtkMax 之間的隨機數字
|-
|-
| 不過西諾克斯的 LevelSensorMode 屬性並不控制他脖子上的武器如何升級(到頭來是脖子上而不是手上)。總之,西諾克斯用的是另一個機制來決定脖子上的武器如何升級,而且這個機制可以繞過 'LevelSensorMode'。 || addLifeMin 到 addLifeMax 之間的隨機數字
| Durability up 耐久度提升 || addLifeMin 到 addLifeMax 之間的隨機數字
|-
|-
| Long throw 遠距離投擲 || addThrowMin 到 addThrowMax 之間的隨機數字
| Long throw 遠距離投擲 || addThrowMin 到 addThrowMax 之間的隨機數字
Line 181: Line 184:
|-
|-
| 1
| 1
| '''RandomBlue''':  可能是藍標也可能是黃標,隨機決定。 (with <code>weaponCommonSharpWeaponPer</code> being the probability).
| '''RandomBlue''':  要麼是藍標以上的加成,要麼不加成,隨機決定。 (with <code>weaponCommonSharpWeaponPer</code> being the probability).
|-
|-
| 2
| 2
Line 190: Line 193:
|-
|-
| 4
| 4
| '''NoneForced''' (chests only): Weapon will ''never'' spawn with any modifiers. ''This overrides regular scaling.''
| '''NoneForced''' (限於寶箱): Weapon will ''never'' spawn with any modifiers. 寶箱的這個屬性會抹去其他一切升級。
|}
|}


If [[Difficulty scaling#LevelSensorMode|scaling]] is enabled, the weapon may receive modifiers from an even higher tier if point requirements are met.
如果 [[Difficulty scaling#LevelSensorMode|scaling]] 處在打開狀態的話,武器有可能因為經驗值足夠的關係被升到更高級。


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.
換句話說,就算是設成 0 ('None') 的武器也不是永遠不升級了,
而是遊戲開發者有意給玩家最低等級的武器。
只要玩家的經驗值高過某個門檻,就會得到 Blue 藍標加成,再高一點 Yellow 黃標加成。


== Scaling algorithm ==  
== 升級的計算過程 ==  


=== <code>Ecosystem::LevelSensor::loadByml</code> ===  
=== <code>Ecosystem::LevelSensor::loadByml</code> ===  


This function is called by <code>Ecosystem::init</code> from <code>ksys::InitializeApp</code>
這個函數會被 <code>Ecosystem::init</code> 呼叫,
後者是被 <code>ksys::InitializeApp</code> 呼叫。


Sets up byml structures for reading Ecosystem/[[LevelSensor.byml]].
它的功用是載入 byml 檔案 Ecosystem/[[LevelSensor.byml]]


=== <code>Ecosystem::LevelSensor::calculatePoints</code> ===  
=== <code>Ecosystem::LevelSensor::calculatePoints</code> ===  


Called by [[PlacementMgr]] when spawning actors.
每當遊戲生成 actors 時,[[PlacementMgr]] 會呼叫這個函數。(actor 可以是武器或是敵人)


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 <code>Defeated_%s_Num</code>, but technically the configuration format allows for other flags to be specified.
遊戲到目前為止都只用殺敵數 <code>Defeated_%s_Num</code> 來計算經驗值並以此推測升級程度,但是從程式設計的角度來說可以用別的東西來計算經驗值。


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.
有趣的是,遊戲利用殺敵數計算經驗值時,會把一個「原始經驗值」轉化成「用在武器升級的經驗值」跟「用在敵人升級的經驗值」。這樣設計師就可以在後期微調「原始」轉化到「武器」的匯率;「敵人」亦同。


<source lang="c++">
<source lang="c++">
float points = 0.0;
float points = 0.0;
for (kill_flag : this->byml["flag"])
for (kill_flag : this->byml["flag"]) // 枚舉所有敵人
    int kill_count = GameData::getIntegerFlag(kill_flag["name"]);
   int kill_count = GameData::getIntegerFlag(kill_flag["name"]);
    points += kill_count * kill_flag["point"];
   points += kill_count * kill_flag["point"]; // 殺敵數 * 此敵人的分數


this->points = points;
this->points = points; // 原始經驗值
this->weapon_points = points * this->byml["setting"].Level2WeaponPower;
this->weapon_points = points * this->byml["setting"].Level2WeaponPower; // 換算成武器用分數
this->enemy_points = points * this->byml["setting"].Level2EnemyPower;
this->enemy_points = points * this->byml["setting"].Level2EnemyPower; // 換算成敵人用分數
</source>
</source>


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.
就我們能看到的部分,任天堂從來沒有調整過這兩個數值。


=== <code>Ecosystem::LevelSensor::scaleWeapon</code> ===  
=== <code>Ecosystem::LevelSensor::scaleWeapon</code> ===  


Called from treasure chest code, enemy actors{{Check}}, <code>Ecosystem::LevelSensor::scaleActor</code>
它負責處理武器的升級。有三個東西會呼叫它:寶箱、敵人{{Check}}<code>Ecosystem::LevelSensor::scaleActor</code>


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.
如果函數找半天找不到滿足給定的條件武器、加成,遊戲就會用預設的武器、預設的加成。(程式碼裡最後的 return false 會處理所有例外、失敗的情形。)


Pseudocode (1.0.0):
Pseudocode (1.0.0):


<source lang="c++">
<source lang="c++">
bool Ecosystem::LevelSensor::scaleWeapon(const sead::SafeString& weapon_to_look_up,
bool Ecosystem::LevelSensor::scaleWeapon(const sead::SafeString& weapon_to_look_up, // 基礎武器
                                        WeaponModifier required_modifier,
                                        WeaponModifier required_modifier, // 預設至少加這麼多
                                        const char** weapon_to_use_name,
                                        const char** weapon_to_use_name, // 回傳的武器寫在這裡
                                        WeaponModifier* modifier_to_use,
                                        WeaponModifier* modifier_to_use, // 回傳的加成寫在這裡
                                         void* unknown)
                                         void* unknown)
{
{
   // some checks using 'unknown' here which seems to be a pointer to the actor
   // some checks using 'unknown' here which seems to be a pointer to the actor
  //
 
   for (weapon_table : this->byml["weapon"]) {
   for (weapon_table : this->byml["weapon"]) {
     // find the first weapon entry for which the player has enough points
     // find the first weapon entry for which the player has enough points
     // with the specified name and modifier
     // with the specified name and modifier
    i = -1;
   // 不同的武器系列儲存在不同的 table 裡,要枚舉 table 再枚舉 table 裡的細項
    for (j = 0; j < weapon_table["actors"].size; ++j) {
   i = -1; // 應該是 index,但是 -1 代表尚未找到
      entry = weapon_table["actors"][j];
   for (j = 0; j < weapon_table["actors"].size; ++j) { // 枚舉含有加成訊息的武器
      float points_for_next_transition = entry["value"];
     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"] &&
     if (this->weapon_points > points_for_next_transition && // 你的經驗值夠
          convert_to_modifier(entry["plus"]) == required_modifier) {
         weapon_to_look_up == entry["name"] && // 武器的名字是對的
        i = j;
         convert_to_modifier(entry["plus"]) == required_modifier) { // 加成的方向跟程度是對的
        break;
       i = j;
      }
       break; // 找到一個了(但是可能還有更多)
     }
     }
     }
    //
 
     if (i == -1)
     if (i == -1)
      continue;
     continue; // 找不到就試下一個
    //
 
    do {
   do { // 已知找到一個的情況下,試圖找更多
      entry = weapon_table["actors"][i];
     entry = weapon_table["actors"][i];
      //
 
       // not_rank_up means there is no link between weapons;
       // not_rank_up means there is no link between weapons;
       // this table is just used to look up modifiers.
       // this table is just used to look up modifiers.
Line 276: Line 283:
       // or until we reach a modifier that requires more points.
       // or until we reach a modifier that requires more points.
       if (weapon_table["not_rank_up"] && entry["name"] != weapon_to_look_up)
       if (weapon_table["not_rank_up"] && entry["name"] != weapon_to_look_up)
        break;
       break; // ???這個 table 裡的武器不能升級,或是 entry 是 null (boundary check)
      //
 
       // otherwise, just go down the list until we reach the end or a weapon which
       // 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).
       // requires more points. this will possibly upgrade the weapon (e.g. Knight -> Royal).
       if (this->weapon_points <= entry["value"])
       if (this->weapon_points <= entry["value"])
        break;
       break; // 枚舉到經驗值最高又符合條件的才停
      //
 
       ++i;
       ++i;
     } while (i < weapon_table["actors"].size);
     } while (i < weapon_table["actors"].size);
    //
 
     *weapon_to_use_name = entry["name"];
     *weapon_to_use_name = entry["name"];
     *modifier_to_use = convert_to_modifier(entry["plus"]);
     *modifier_to_use = convert_to_modifier(entry["plus"]);
    return true;
   return true; // 有找到
   }
   }
   return false;  // cannot scale up
   return false;  // cannot scale up // 沒找到
}
}
</source>
</source>
Line 296: Line 303:
=== <code>Ecosystem::LevelSensor::scaleActor</code> ===  
=== <code>Ecosystem::LevelSensor::scaleActor</code> ===  


Analogous to <code>LevelSensor::scaleWeapon</code>.
處理敵人的升級。方法跟上面 <code>LevelSensor::scaleWeapon</code> 處理武器的升級類似。


Pseudocode (1.0.0):
Pseudocode (1.0.0):
Line 303: Line 310:
if (actor->params["LevelSensorMode"] < 1)
if (actor->params["LevelSensorMode"] < 1)
   return false;
   return false;
//
 
if (actor_name.contains("Enemy")) {
if (actor_name.contains("Enemy")) {
   for (enemy_table : this->byml["enemy"]) {
   for (enemy_table : this->byml["enemy"]) {
Line 310: Line 317:
       entry = enemy_table["actors"][j];
       entry = enemy_table["actors"][j];
       if (entry["name"] == actor_name && this->enemy_points > entry["value"]) {
       if (entry["name"] == actor_name && this->enemy_points > entry["value"]) {
        i = j;
       i = j; // 名字對,而且經驗值也夠
        break;
       break;
       }
       }
     }
     }
    //
 
    if (i == -1)
   if (i == -1)
      continue;
     continue; // 找不到換下一個
    //
 
     do {
     do {
       entry = enemy_table["actors"][i];
       entry = enemy_table["actors"][i];
       if (this->enemy_points <= entry["value"])
       if (this->enemy_points <= entry["value"])
        break;
       break; // 有找到就枚舉到經驗值最高又符合條件的才停
       ++i;
       ++i;
     } while (i < enemy_table["actors"].size);
     } while (i < enemy_table["actors"].size);
    //
 
     *actor_to_use = entry["name"];
     *actor_to_use = entry["name"];
     return true;
     return true;
Line 330: Line 337:
   return false;  // cannot scale up
   return false;  // cannot scale up
}
}
//
 
if (actor_name.contains("Weapon")) {
if (actor_name.contains("Weapon")) { // 找到敵人後,手上的武器單獨處理
   weapon_name = actor->getWeaponName();
   weapon_name = actor->getWeaponName();
   modifier = actor->params["SharpWeaponJudgeType"];
   modifier = actor->params["SharpWeaponJudgeType"];
   if (modifier == WeaponModifier::RandomBlue)
   if (modifier == WeaponModifier::RandomBlue)
    modifier = get_random_blue_modifier(actor->getWeaponName());
   modifier = get_random_blue_modifier(actor->getWeaponName()); // 抽獎
  //
 
   if (scaleWeapon(weapon_name, &weapon_to_use, &modifier_to_use)) {
   if (scaleWeapon(weapon_name, &weapon_to_use, &modifier_to_use)) {
    actor->setProperty("SharpWeaponJudgeType", modifier_to_use);
   actor->setProperty("SharpWeaponJudgeType", modifier_to_use); // 用找的
    *actor_to_use = weapon_to_use;
   *actor_to_use = weapon_to_use;
    return true;
   return true; // 有找到
   }
   }
   return false;  // cannot scale up
   return false;  // cannot scale up // 沒找到
}
}
</source>
</source>


== The Data ==  
== 大數據 ==  


To make things easier to understand, here are links to:
精確的數值可以在這裡找到:
* [https://docs.google.com/spreadsheets/d/e/2PACX-1vRSlyOD7FLAn1TUBn64Pu8Pld-WOfgcVByuywHMWvBTEV0j8potD1wkBs-MJJXf-gvEkpfItUCMqMk6/pubhtml kill point, enemy scaling and weapon scaling tables]
* [https://docs.google.com/spreadsheets/d/e/2PACX-1vRSlyOD7FLAn1TUBn64Pu8Pld-WOfgcVByuywHMWvBTEV0j8potD1wkBs-MJJXf-gvEkpfItUCMqMk6/pubhtml kill point, enemy scaling and weapon scaling tables]
* an [https://objmap.zeldamods.org object map with scaling information].
* [https://objmap.zeldamods.org 地圖裡也有].
This makes it possible to see both the required points for enemy/weapon upgrades, as well as all of the special cases extremely easily.
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 [[ActorParam/GeneralParamList|GeneralParamList]]) and blight defeat flags.
厄咒加儂也有升級版,不過是依據的是另一個完全獨立的系統。
具體升級的內容是:神獸裡的厄咒加儂的血量是由玩家打死過多少別的加儂決定的。


<source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
<source lang="c++">__int64 SiteBoss::getInitialHP(SiteBoss *this) // 0x71002D01F4
{
{
   const int baseHp = Enemy::getInitialHP(this);
   const int baseHp = Enemy::getInitialHP(this); // 基礎血量(底)
   const int halfBaseHp = baseHp >> 1;
   const int halfBaseHp = baseHp >> 1; // 除以 2(台)
   const bool dieGanonWind = hasFlag_Die_PGanonWind(0);
   const bool dieGanonWind = hasFlag_Die_PGanonWind(0); // 打過風嗎?
   const bool dieGanonWater = hasFlag_Die_PGanonWater(0);
   const bool dieGanonWater = hasFlag_Die_PGanonWater(0); // 打過水嗎?
   const bool dieGanonFire = hasFlag_Die_PGanonFire(0);
   const bool dieGanonFire = hasFlag_Die_PGanonFire(0); // 打過火嗎?
   const bool dieGanonElectric = hasFlag_Die_PGanonElectric(0);
   const bool dieGanonElectric = hasFlag_Die_PGanonElectric(0); // 打過雷嗎?
   const int flags = this->siteBossFlags & 0xFFFFFFFC;
   const int flags = this->siteBossFlags & 0xFFFFFFFC; // 玩家在哪裡
   int multiplier;
   int multiplier;
   if ( flags == 4 )
   if ( flags == 4 ) // 城堡裡
    multiplier = 3;
   multiplier = 3;
   else if ( flags == 8 )
   else if ( flags == 8 ) // 幻影空間
    multiplier = 4;
   multiplier = 4;
   else
   else
     multiplier = dieGanonFire + dieGanonWind + dieGanonWater + dieGanonElectric;
     multiplier = dieGanonFire + dieGanonWind + dieGanonWater + dieGanonElectric;
Line 376: Line 384:
}</source>
}</source>


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.
換句換說,玩家第一個擊倒的厄咒加儂的血量是 800,下一個 1200,再下一個 1600,最後一個 2000。


=== Special case 1: Castle Blights ===  
=== Special case 1: 城堡裡的厄咒加儂 ===  


Castle blights have <code>IsRemainBoss</code> set to false in their root AI parameters (see [[AIDef:AI/SiteBossSpearRoot]] for example), which sets flag 4.
Castle blights have <code>IsRemainBoss</code> 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.
城堡裡的厄咒加儂,不管是第幾隻,血量都是 800+3×400 = 2000。


If flag 4 is set, the [[AIDef:Action/SiteBossDie]] code will NOT increment the &#x22;defeated&#x22; counter. This means castle blights do not give any scaling points.
打死城堡裡的厄咒加儂並不計入經驗值。(相應的計數器不會增加。)


=== Special case 2: DLC2 Blights ===  
=== Special case 2: 幻影空間裡的厄咒加儂 ===  


Illusory Realm blights possess the <code>EnemySiteBoss_R</code> actor tag. This causes flag 8 to be set. So they will always have 500+4×250 = 1500 HP.
Illusory Realm blights possess the <code>EnemySiteBoss_R</code> 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 <code>Enemy_SiteBoss_Bow_R</code>.
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 <code>Enemy_SiteBoss_Bow_R</code>.




<references />
<references />
[[Category:Internals]]
[[Category:Internals{{#translation:}}]]
[[Category:Game mechanics]]
[[Category:Game mechanics{{#translation:}}]]
translator
745

edits