Quest flag overrides

From ZeldaMods (Breath of the Wild)
Revision as of 17:29, 5 May 2021 by Leoetlino (talk | contribs) (→‎Implementation in 1.5.0: add link to decomp source)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

To ensure that quests do not become uncompletable if the player leaves an area when they have only been partially solved, some GameData flags are always manually overwritten by the GameDataMgr when a stage is loaded.

GameDataMgr is also responsible for always resetting the "get flag" for the Bow of Light to ensure it spawns and for making Kass spawn in Rito Village (see #Kass quests).

Kass quests

Sometime after the release of 1.0.0, completion checks for Kass' quests were added to the same GameDataMgr function. These are exactly the same as the ones that can be found in the BloodyMoonRelief<Musician_Check> event flow but implemented in the executable instead. The reason why Nintendo has decided to duplicate the checks is unknown, but it might be a workaround for some kind of event bug: Kass not spawning in Rito Village even after all of his quests have been completed used to be a widespread issue.

In 1.5.0, Npc_Musician_Come (the flag that causes Kass to spawn in Rito Village) is set iff the following flags are set:

  • Animal_Forest_Finish
  • HateeluMini_Treasure_Finish
  • Thunder_Sword_Finish
  • Relief_Landing_Finish
  • TwoWheels_Finish
  • Shadow_Sign_Finish
  • MouthofDragon_Finish
  • BloodyMoonRelief_Finish
  • Rito_BrosRock_Finish

Implementation in 1.5.0

See https://github.com/zeldaret/botw/blob/7bda72574e1d7cf3e76de20155c1f37b74971f4e/src/KingSystem/GameData/gdtManager.cpp#L373 for a fully accurate version of this code.

// utility functions (inlined in the actual executable)
void GameDataMgr::setFlag(const sead::SafeString& flagName, bool value)
{
  if (this->paramB.changeOnlyOnce)
    return;
  if (!TriggerParam::setBoolByKey(*this->paramB.param1, value, flag_name, this->paramB.x, 1LL, 1))
    return;
  if (!this->paramB.propagateParam1Changes)
    return;
  TriggerParam::setBoolByKey(*this->paramB.param, value, flag_name, this->paramB.x, 1LL, 1);
}

bool GameDataMgr::flagIsSet(const sead::SafeString& flagName)
{
  bool value;
  return TriggerParam::getFlagByKey(*this->param.param1, &value, flagName, this->param.x) && value;
}

// 0x7100DD0A88
// called from GameScene
void GameDataMgr::getAndSetShrineQuestAndKassFlags()
{
  if (flagIsSet("DarkWoods_Giant_Clear") && !flagIsSet("DarkWoods_Finish"))
    setFlag("DarkWoods_Giant_Clear", false);

  if ( TriggerParam::getFlagByKey(*this->param.param1, &giant_ball1, "giant_ball1", this->param.x)
    && TriggerParam::getFlagByKey(*this->param.param1, &giant_ball2, "giant_ball2", this->param.x)
    && TriggerParam::getFlagByKey(*this->param.param1, &giant_ball3, "giant_ball3", this->param.x)
    && TriggerParam::getFlagByKey(*this->param.param1, &giant_dungeon, "giant_dungeon", this->param.x)
    && TriggerParam::getFlagByKey(*this->param.param1, &lithograph1, "MainField_DgnObj_RemainsLithogragh_A_02_789666109", this->param.x)
    && TriggerParam::getFlagByKey(*this->param.param1, &lithograph2, "MainField_DgnObj_RemainsLithogragh_A_02_2456751716", this->param.x)
    && TriggerParam::getFlagByKey(*this->param.param1, &lithograph3, "MainField_DgnObj_RemainsLithogragh_A_02_1822262999", this->param.x) )
  {
    if (giant_ball1 && !lithograph1 && !(this->flags & 0x40000))
      setFlag("MainField_DgnObj_RemainsLithogragh_A_02_789666109", true);
    if (giant_ball2 && !lithograph2 && !(this->flags & 0x40000))
      setFlag("MainField_DgnObj_RemainsLithogragh_A_02_2456751716", true);
    if (giant_ball3 && !lithograph3 && !(this->flags & 0x40000))
      setFlag("MainField_DgnObj_RemainsLithogragh_A_02_1822262999", true);
    if (!giant_dungeon && giant_ball1 && giant_ball2 && giant_ball3)
      setFlag("giant_dungeon", true);
  }

  if (flagIsSet("MainField_Weapon_Bow_071_2178255681"))
    setFlag("MainField_Weapon_Bow_071_2178255681", false);

  if (flagIsSet("BalladOfHeroes_Step02") && !flagIsSet("BalladOfHeroes_Step03"))
  {
    int defeatedCount =
      flagIsSet("Defeat_OneHitDungeon001") +
      flagIsSet("Defeat_OneHitDungeon002") +
      flagIsSet("Defeat_OneHitDungeon003") +
      flagIsSet("Defeat_OneHitDungeon004");

    bool defeatedNonLockedOneHitDungeon = !Lock_OneHitDungeon001 && Defeat_OneHitDungeon001;
    defeatedNonLockedOneHitDungeon |= !Lock_OneHitDungeon002 && Defeat_OneHitDungeon002;
    defeatedNonLockedOneHitDungeon |= !Lock_OneHitDungeon003 && Defeat_OneHitDungeon003;
    defeatedNonLockedOneHitDungeon |= !Lock_OneHitDungeon004 && Defeat_OneHitDungeon004;

    if ( defeatedNonLockedOneHitDungeon && defeatedCount >= 1 )
    {
      if (!flagIsSet("BalladOfHeroes_Step02_Dungeon01"))
        setFlag("BalladOfHeroes_Step02_Dungeon01", true);
      if ( defeatedCount >= 2 )
      {
        if (!flagIsSet("BalladOfHeroes_Step02_Dungeon02"))
          setFlag("BalladOfHeroes_Step02_Dungeon02", true);
        if ( defeatedCount >= 3 )
        {
          if (!flagIsSet("BalladOfHeroes_Step02_Dungeon03"))
            setFlag("BalladOfHeroes_Step02_Dungeon03", true);
          if (defeatedCount >= 4 && !flagIsSet("BalladOfHeroes_Step2_Dungeon4"))
            setFlag("BalladOfHeroes_Step2_Dungeon4", true);
        }
      }
    }
  }

  if (flagIsSet("IsGet_Armor_005_Head") &&
      flagIsSet("IsGet_Armor_005_Upper") &&
      flagIsSet("IsGet_Armor_005_Lower") &&
      !(this->flags & 0x40000))
  {
    setFlag("CompleteDungeon_Finish", true);
  }

  if (flagIsSet("NightStoneBreak") && !flagIsSet("NightStoneDungeonAppear"))
    setFlag("NightStoneBreak", false);

  if (flagIsSet("Animal_Forest_Finish") &&
      flagIsSet("HateeluMini_Treasure_Finish") &&
      flagIsSet("Thunder_Sword_Finish") &&
      flagIsSet("Relief_Landing_Finish") &&
      flagIsSet("TwoWheels_Finish") &&
      flagIsSet("Shadow_Sign_Finish") &&
      flagIsSet("MouthofDragon_Finish") &&
      flagIsSet("BloodyMoonRelief_Finish") &&
      flagIsSet("Rito_BrosRock_Finish"))
  {
    setFlag("Npc_Musician_Come", true);
  }
}