2778 lines
119 KiB
C++
2778 lines
119 KiB
C++
#include "logic.h"
|
|
#include "../debugger/performanceTimer.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "soh/OTRGlobals.h"
|
|
#include "dungeon.h"
|
|
#include "SeedContext.h"
|
|
#include "macros.h"
|
|
#include "variables.h"
|
|
#include <spdlog/spdlog.h>
|
|
#include <ship/utils/StringHelper.h>
|
|
#include "soh/resource/type/Scene.h"
|
|
#include "soh/resource/type/scenecommand/SetTransitionActorList.h"
|
|
#include "src/overlays/actors/ovl_En_Door/z_en_door.h"
|
|
#include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h"
|
|
|
|
namespace Rando {
|
|
|
|
bool Logic::HasItem(RandomizerGet itemName) {
|
|
switch (itemName) {
|
|
case RG_FAIRY_OCARINA:
|
|
return CheckInventory(ITEM_OCARINA_FAIRY, false);
|
|
case RG_OCARINA_OF_TIME:
|
|
return CheckInventory(ITEM_OCARINA_TIME, true);
|
|
case RG_DINS_FIRE:
|
|
return CheckInventory(ITEM_DINS_FIRE, true);
|
|
case RG_FARORES_WIND:
|
|
return CheckInventory(ITEM_FARORES_WIND, true);
|
|
case RG_NAYRUS_LOVE:
|
|
return CheckInventory(ITEM_NAYRUS_LOVE, true);
|
|
case RG_LENS_OF_TRUTH:
|
|
return CheckInventory(ITEM_LENS, true);
|
|
case RG_FAIRY_BOW:
|
|
return CheckInventory(ITEM_BOW, true);
|
|
case RG_MEGATON_HAMMER:
|
|
return CheckInventory(ITEM_HAMMER, true);
|
|
case RG_HOOKSHOT:
|
|
return CheckInventory(ITEM_HOOKSHOT, false);
|
|
case RG_LONGSHOT:
|
|
return CheckInventory(ITEM_LONGSHOT, true);
|
|
case RG_PROGRESSIVE_STICK_UPGRADE:
|
|
case RG_STICKS:
|
|
return CurrentUpgrade(UPG_STICKS);
|
|
case RG_FIRE_ARROWS:
|
|
return CheckInventory(ITEM_ARROW_FIRE, true);
|
|
case RG_ICE_ARROWS:
|
|
return CheckInventory(ITEM_ARROW_ICE, true);
|
|
case RG_LIGHT_ARROWS:
|
|
return CheckInventory(ITEM_ARROW_LIGHT, true);
|
|
case RG_PROGRESSIVE_BOMBCHU_BAG:
|
|
case RG_BOMBCHU_5:
|
|
case RG_BOMBCHU_10:
|
|
case RG_BOMBCHU_20:
|
|
return (BombchusEnabled() &&
|
|
(Get(LOGIC_BUY_BOMBCHUS) || Get(LOGIC_COULD_PLAY_BOWLING) || Get(LOGIC_CARPET_MERCHANT))) ||
|
|
CheckInventory(ITEM_BOMBCHU, true);
|
|
case RG_FAIRY_SLINGSHOT:
|
|
return CheckInventory(ITEM_SLINGSHOT, true);
|
|
case RG_BOOMERANG:
|
|
return CheckInventory(ITEM_BOOMERANG, true);
|
|
case RG_PROGRESSIVE_NUT_UPGRADE:
|
|
case RG_NUTS:
|
|
return CurrentUpgrade(UPG_NUTS);
|
|
case RG_MAGIC_BEAN:
|
|
return GetAmmo(ITEM_BEAN) > 0 || CheckInventory(ITEM_BEAN, true);
|
|
case RG_KOKIRI_SWORD:
|
|
case RG_DEKU_SHIELD:
|
|
case RG_GORON_TUNIC:
|
|
case RG_ZORA_TUNIC:
|
|
case RG_HYLIAN_SHIELD:
|
|
case RG_MIRROR_SHIELD:
|
|
case RG_MASTER_SWORD:
|
|
case RG_IRON_BOOTS:
|
|
case RG_HOVER_BOOTS:
|
|
return CheckEquipment(RandoGetToEquipFlag.at(itemName));
|
|
case RG_GIANTS_KNIFE:
|
|
return CheckEquipment(RandoGetToEquipFlag.at(itemName)) || Get(LOGIC_MEDIGORON);
|
|
case RG_BIGGORON_SWORD:
|
|
return CheckEquipment(RandoGetToEquipFlag.at(itemName)) && mSaveContext->bgsFlag;
|
|
case RG_POWER_BRACELET:
|
|
return CheckRandoInf(RAND_INF_CAN_GRAB);
|
|
case RG_GORONS_BRACELET:
|
|
return CurrentUpgrade(UPG_STRENGTH);
|
|
case RG_SILVER_GAUNTLETS:
|
|
return CurrentUpgrade(UPG_STRENGTH) >= 2;
|
|
case RG_GOLDEN_GAUNTLETS:
|
|
return CurrentUpgrade(UPG_STRENGTH) >= 3;
|
|
case RG_PROGRESSIVE_BOMB_BAG:
|
|
case RG_BOMB_BAG:
|
|
return CurrentUpgrade(UPG_BOMB_BAG);
|
|
case RG_MAGIC_SINGLE:
|
|
return GetSaveContext()->magicLevel >= 1 || GetSaveContext()->isMagicAcquired;
|
|
// Songs
|
|
case RG_ZELDAS_LULLABY:
|
|
case RG_EPONAS_SONG:
|
|
case RG_SARIAS_SONG:
|
|
case RG_SUNS_SONG:
|
|
case RG_SONG_OF_TIME:
|
|
case RG_SONG_OF_STORMS:
|
|
case RG_MINUET_OF_FOREST:
|
|
case RG_BOLERO_OF_FIRE:
|
|
case RG_SERENADE_OF_WATER:
|
|
case RG_REQUIEM_OF_SPIRIT:
|
|
case RG_NOCTURNE_OF_SHADOW:
|
|
case RG_PRELUDE_OF_LIGHT:
|
|
// Dungeon Rewards
|
|
case RG_KOKIRI_EMERALD:
|
|
case RG_GORON_RUBY:
|
|
case RG_ZORA_SAPPHIRE:
|
|
case RG_FOREST_MEDALLION:
|
|
case RG_FIRE_MEDALLION:
|
|
case RG_WATER_MEDALLION:
|
|
case RG_SPIRIT_MEDALLION:
|
|
case RG_SHADOW_MEDALLION:
|
|
case RG_LIGHT_MEDALLION:
|
|
// Misc Quest Items
|
|
case RG_STONE_OF_AGONY:
|
|
case RG_GERUDO_MEMBERSHIP_CARD:
|
|
return CheckQuestItem(RandoGetToQuestItem.at(itemName));
|
|
case RG_DOUBLE_DEFENSE:
|
|
return GetSaveContext()->isDoubleDefenseAcquired;
|
|
// Masks
|
|
case RG_SKULL_MASK:
|
|
switch (ctx->GetOption(RSK_MASK_QUEST).Get()) {
|
|
case RO_MASK_QUEST_VANILLA:
|
|
return Get(LOGIC_BORROW_SKULL_MASK);
|
|
case RO_MASK_QUEST_COMPLETED:
|
|
return HasItem(RG_ZELDAS_LETTER) && Get(LOGIC_KAKARIKO_GATE_OPEN);
|
|
case RO_MASK_QUEST_SHUFFLE:
|
|
return CheckRandoInf(RAND_INF_CHILD_TRADES_HAS_MASK_SKULL);
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
case RG_MASK_OF_TRUTH:
|
|
switch (ctx->GetOption(RSK_MASK_QUEST).Get()) {
|
|
case RO_MASK_QUEST_VANILLA:
|
|
return Get(LOGIC_BORROW_RIGHT_MASKS);
|
|
case RO_MASK_QUEST_COMPLETED:
|
|
return HasItem(RG_ZELDAS_LETTER) && Get(LOGIC_KAKARIKO_GATE_OPEN);
|
|
case RO_MASK_QUEST_SHUFFLE:
|
|
return CheckRandoInf(RAND_INF_CHILD_TRADES_HAS_MASK_TRUTH);
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
case RG_FISHING_POLE:
|
|
case RG_ZELDAS_LETTER:
|
|
case RG_WEIRD_EGG:
|
|
case RG_GREG_RUPEE:
|
|
case RG_SPEAK_DEKU:
|
|
case RG_SPEAK_GERUDO:
|
|
case RG_SPEAK_GORON:
|
|
case RG_SPEAK_HYLIAN:
|
|
case RG_SPEAK_KOKIRI:
|
|
case RG_SPEAK_ZORA:
|
|
// Ocarina Buttons
|
|
case RG_OCARINA_A_BUTTON:
|
|
case RG_OCARINA_C_LEFT_BUTTON:
|
|
case RG_OCARINA_C_RIGHT_BUTTON:
|
|
case RG_OCARINA_C_DOWN_BUTTON:
|
|
case RG_OCARINA_C_UP_BUTTON:
|
|
// Bean Souls
|
|
case RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL:
|
|
case RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL:
|
|
case RG_DESERT_COLOSSUS_BEAN_SOUL:
|
|
case RG_GERUDO_VALLEY_BEAN_SOUL:
|
|
case RG_GRAVEYARD_BEAN_SOUL:
|
|
case RG_KOKIRI_FOREST_BEAN_SOUL:
|
|
case RG_LAKE_HYLIA_BEAN_SOUL:
|
|
case RG_LOST_WOODS_BRIDGE_BEAN_SOUL:
|
|
case RG_LOST_WOODS_BEAN_SOUL:
|
|
case RG_ZORAS_RIVER_BEAN_SOUL:
|
|
// Boss Souls
|
|
case RG_GOHMA_SOUL:
|
|
case RG_KING_DODONGO_SOUL:
|
|
case RG_BARINADE_SOUL:
|
|
case RG_PHANTOM_GANON_SOUL:
|
|
case RG_VOLVAGIA_SOUL:
|
|
case RG_MORPHA_SOUL:
|
|
case RG_BONGO_BONGO_SOUL:
|
|
case RG_TWINROVA_SOUL:
|
|
case RG_GANON_SOUL:
|
|
case RG_SKELETON_KEY:
|
|
// Overworld Keys
|
|
case RG_GUARD_HOUSE_KEY:
|
|
case RG_MARKET_BAZAAR_KEY:
|
|
case RG_MARKET_POTION_SHOP_KEY:
|
|
case RG_MASK_SHOP_KEY:
|
|
case RG_MARKET_SHOOTING_GALLERY_KEY:
|
|
case RG_BOMBCHU_BOWLING_KEY:
|
|
case RG_TREASURE_CHEST_GAME_BUILDING_KEY:
|
|
case RG_BOMBCHU_SHOP_KEY:
|
|
case RG_RICHARDS_HOUSE_KEY:
|
|
case RG_ALLEY_HOUSE_KEY:
|
|
case RG_KAK_BAZAAR_KEY:
|
|
case RG_KAK_POTION_SHOP_KEY:
|
|
case RG_BOSS_HOUSE_KEY:
|
|
case RG_GRANNYS_POTION_SHOP_KEY:
|
|
case RG_SKULLTULA_HOUSE_KEY:
|
|
case RG_IMPAS_HOUSE_KEY:
|
|
case RG_WINDMILL_KEY:
|
|
case RG_KAK_SHOOTING_GALLERY_KEY:
|
|
case RG_DAMPES_HUT_KEY:
|
|
case RG_TALONS_HOUSE_KEY:
|
|
case RG_STABLES_KEY:
|
|
case RG_BACK_TOWER_KEY:
|
|
case RG_HYLIA_LAB_KEY:
|
|
case RG_FISHING_HOLE_KEY:
|
|
case RG_RUTOS_LETTER:
|
|
return CheckRandoInf(RandoGetToRandInf.at(itemName));
|
|
// Boss Keys
|
|
case RG_FOREST_TEMPLE_BOSS_KEY:
|
|
case RG_FIRE_TEMPLE_BOSS_KEY:
|
|
case RG_WATER_TEMPLE_BOSS_KEY:
|
|
case RG_SPIRIT_TEMPLE_BOSS_KEY:
|
|
case RG_SHADOW_TEMPLE_BOSS_KEY:
|
|
case RG_GANONS_CASTLE_BOSS_KEY:
|
|
return CheckDungeonItem(DUNGEON_KEY_BOSS, RandoGetToDungeonScene.at(itemName));
|
|
// Maps
|
|
case RG_DEKU_TREE_MAP:
|
|
case RG_DODONGOS_CAVERN_MAP:
|
|
case RG_JABU_JABUS_BELLY_MAP:
|
|
case RG_FOREST_TEMPLE_MAP:
|
|
case RG_FIRE_TEMPLE_MAP:
|
|
case RG_WATER_TEMPLE_MAP:
|
|
case RG_SPIRIT_TEMPLE_MAP:
|
|
case RG_SHADOW_TEMPLE_MAP:
|
|
case RG_BOTTOM_OF_THE_WELL_MAP:
|
|
case RG_ICE_CAVERN_MAP:
|
|
return CheckDungeonItem(DUNGEON_MAP, RandoGetToDungeonScene.at(itemName));
|
|
// Compasses
|
|
case RG_DEKU_TREE_COMPASS:
|
|
case RG_DODONGOS_CAVERN_COMPASS:
|
|
case RG_JABU_JABUS_BELLY_COMPASS:
|
|
case RG_FOREST_TEMPLE_COMPASS:
|
|
case RG_FIRE_TEMPLE_COMPASS:
|
|
case RG_WATER_TEMPLE_COMPASS:
|
|
case RG_SPIRIT_TEMPLE_COMPASS:
|
|
case RG_SHADOW_TEMPLE_COMPASS:
|
|
case RG_BOTTOM_OF_THE_WELL_COMPASS:
|
|
case RG_ICE_CAVERN_COMPASS:
|
|
return CheckDungeonItem(DUNGEON_COMPASS, RandoGetToDungeonScene.at(itemName));
|
|
// Wallets
|
|
case RG_CHILD_WALLET:
|
|
return CheckRandoInf(RAND_INF_HAS_WALLET);
|
|
case RG_ADULT_WALLET:
|
|
return CurrentUpgrade(UPG_WALLET) >= 1;
|
|
case RG_GIANT_WALLET:
|
|
return CurrentUpgrade(UPG_WALLET) >= 2;
|
|
case RG_TYCOON_WALLET:
|
|
return CurrentUpgrade(UPG_WALLET) >= 3;
|
|
// Scales
|
|
case RG_BRONZE_SCALE:
|
|
return CheckRandoInf(RAND_INF_CAN_SWIM);
|
|
case RG_SILVER_SCALE:
|
|
return CurrentUpgrade(UPG_SCALE) >= 1;
|
|
case RG_GOLDEN_SCALE:
|
|
return CurrentUpgrade(UPG_SCALE) >= 2;
|
|
case RG_CLIMB:
|
|
return CheckRandoInf(RAND_INF_CAN_CLIMB);
|
|
case RG_CRAWL:
|
|
return CheckRandoInf(RAND_INF_CAN_CRAWL);
|
|
case RG_OPEN_CHEST:
|
|
return CheckRandoInf(RAND_INF_CAN_OPEN_CHEST);
|
|
case RG_POCKET_EGG:
|
|
return CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG) ||
|
|
CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_CUCCO);
|
|
case RG_COJIRO:
|
|
case RG_ODD_MUSHROOM:
|
|
case RG_ODD_POTION:
|
|
case RG_POACHERS_SAW:
|
|
case RG_BROKEN_SWORD:
|
|
case RG_PRESCRIPTION:
|
|
case RG_EYEBALL_FROG:
|
|
case RG_EYEDROPS:
|
|
case RG_CLAIM_CHECK:
|
|
return CheckRandoInf(itemName - RG_COJIRO + RAND_INF_ADULT_TRADES_HAS_COJIRO);
|
|
case RG_BOTTLE_WITH_BIG_POE:
|
|
case RG_BOTTLE_WITH_BLUE_FIRE:
|
|
case RG_BOTTLE_WITH_BLUE_POTION:
|
|
case RG_BOTTLE_WITH_BUGS:
|
|
case RG_BOTTLE_WITH_FAIRY:
|
|
case RG_BOTTLE_WITH_FISH:
|
|
case RG_BOTTLE_WITH_GREEN_POTION:
|
|
case RG_BOTTLE_WITH_MILK:
|
|
case RG_BOTTLE_WITH_POE:
|
|
case RG_BOTTLE_WITH_RED_POTION:
|
|
case RG_EMPTY_BOTTLE:
|
|
return HasBottle();
|
|
default:
|
|
break;
|
|
}
|
|
SPDLOG_ERROR("HasItem reached `return false;`. Missing case for RandomizerGet of {}",
|
|
static_cast<uint32_t>(itemName));
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
// Can the passed in item be used?
|
|
// RANDOTODO catch magic items explicitly and add an assert on miss.
|
|
bool Logic::CanUse(RandomizerGet itemName) {
|
|
if (!HasItem(itemName))
|
|
return false;
|
|
|
|
switch (itemName) {
|
|
// Magic items
|
|
case RG_MAGIC_SINGLE:
|
|
return true; // AmmoCanDrop || (HasBottle() && Get(LOGIC_BUY_MAGIC_POTION))
|
|
case RG_DINS_FIRE:
|
|
case RG_FARORES_WIND:
|
|
case RG_NAYRUS_LOVE:
|
|
case RG_LENS_OF_TRUTH:
|
|
return CanUse(RG_MAGIC_SINGLE);
|
|
case RG_FIRE_ARROWS:
|
|
case RG_ICE_ARROWS:
|
|
case RG_LIGHT_ARROWS:
|
|
return CanUse(RG_MAGIC_SINGLE) && CanUse(RG_FAIRY_BOW);
|
|
|
|
// Adult items
|
|
// TODO: Uncomment those if we ever implement more item usability settings
|
|
case RG_FAIRY_BOW:
|
|
return IsAdult; // || BowAsChild && (AmmoCanDrop || Get(LOGIC_BUY_ARROW));
|
|
case RG_MEGATON_HAMMER:
|
|
return IsAdult; // || HammerAsChild;
|
|
case RG_IRON_BOOTS:
|
|
return IsAdult; // || IronBootsAsChild;
|
|
case RG_HOVER_BOOTS:
|
|
return IsAdult; // || HoverBootsAsChild;
|
|
case RG_HOOKSHOT:
|
|
case RG_LONGSHOT:
|
|
return IsAdult; // || HookshotAsChild;
|
|
case RG_GORON_TUNIC:
|
|
return IsAdult; // || GoronTunicAsChild;
|
|
case RG_ZORA_TUNIC:
|
|
return IsAdult; // || ZoraTunicAsChild;
|
|
case RG_MIRROR_SHIELD:
|
|
return IsAdult; // || MirrorShieldAsChild;
|
|
case RG_MASTER_SWORD:
|
|
return IsAdult; // || MasterSwordAsChild;
|
|
case RG_GIANTS_KNIFE:
|
|
case RG_BIGGORON_SWORD:
|
|
return IsAdult; // || BiggoronSwordAsChild;
|
|
case RG_SILVER_GAUNTLETS:
|
|
case RG_GOLDEN_GAUNTLETS:
|
|
// Adult Trade
|
|
case RG_POCKET_EGG:
|
|
case RG_COJIRO:
|
|
case RG_ODD_MUSHROOM:
|
|
case RG_ODD_POTION:
|
|
case RG_POACHERS_SAW:
|
|
case RG_BROKEN_SWORD:
|
|
case RG_PRESCRIPTION:
|
|
case RG_EYEBALL_FROG:
|
|
case RG_EYEDROPS:
|
|
case RG_CLAIM_CHECK:
|
|
return IsAdult;
|
|
|
|
// Child items
|
|
case RG_FAIRY_SLINGSHOT:
|
|
return IsChild; // || SlingshotAsAdult && (AmmoCanDrop || Get(LOGIC_LOGIC_BUY_SEED));
|
|
case RG_BOOMERANG:
|
|
return IsChild; // || BoomerangAsAdult;
|
|
case RG_KOKIRI_SWORD:
|
|
return IsChild; // || KokiriSwordAsAdult;
|
|
case RG_NUTS:
|
|
return Get(LOGIC_NUT_ACCESS);
|
|
case RG_STICKS:
|
|
return IsChild /* || StickAsAdult;*/ && Get(LOGIC_STICK_ACCESS);
|
|
case RG_DEKU_SHIELD:
|
|
return IsChild; // || DekuShieldAsAdult;
|
|
case RG_PROGRESSIVE_BOMB_BAG:
|
|
case RG_BOMB_BAG:
|
|
return true; // AmmoCanDrop || Get(LOGIC_BUY_BOMB)
|
|
case RG_PROGRESSIVE_BOMBCHU_BAG:
|
|
case RG_BOMBCHU_5:
|
|
case RG_BOMBCHU_10:
|
|
case RG_BOMBCHU_20:
|
|
return BombchuRefill() && BombchusEnabled();
|
|
case RG_WEIRD_EGG:
|
|
case RG_RUTOS_LETTER:
|
|
return IsChild;
|
|
case RG_MAGIC_BEAN:
|
|
return IsChild;
|
|
case RG_SKULL_MASK:
|
|
case RG_MASK_OF_TRUTH:
|
|
return IsChild;
|
|
|
|
// Songs
|
|
case RG_ZELDAS_LULLABY:
|
|
case RG_EPONAS_SONG:
|
|
case RG_PRELUDE_OF_LIGHT:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_C_LEFT_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_RIGHT_BUTTON) && HasItem(RG_OCARINA_C_UP_BUTTON);
|
|
case RG_SARIAS_SONG:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_C_LEFT_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_RIGHT_BUTTON) && HasItem(RG_OCARINA_C_DOWN_BUTTON);
|
|
case RG_SUNS_SONG:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_C_RIGHT_BUTTON) && HasItem(RG_OCARINA_C_UP_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_DOWN_BUTTON);
|
|
case RG_SONG_OF_TIME:
|
|
case RG_BOLERO_OF_FIRE:
|
|
case RG_REQUIEM_OF_SPIRIT:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_A_BUTTON) && HasItem(RG_OCARINA_C_RIGHT_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_DOWN_BUTTON);
|
|
case RG_SONG_OF_STORMS:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_A_BUTTON) && HasItem(RG_OCARINA_C_UP_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_DOWN_BUTTON);
|
|
case RG_MINUET_OF_FOREST:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_A_BUTTON) && HasItem(RG_OCARINA_C_LEFT_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_RIGHT_BUTTON) && HasItem(RG_OCARINA_C_UP_BUTTON);
|
|
case RG_SERENADE_OF_WATER:
|
|
case RG_NOCTURNE_OF_SHADOW:
|
|
return HasItem(RG_FAIRY_OCARINA) && HasItem(RG_OCARINA_A_BUTTON) && HasItem(RG_OCARINA_C_LEFT_BUTTON) &&
|
|
HasItem(RG_OCARINA_C_RIGHT_BUTTON) && HasItem(RG_OCARINA_C_DOWN_BUTTON);
|
|
|
|
// Misc. Items
|
|
case RG_FISHING_POLE:
|
|
return HasItem(RG_CHILD_WALLET); // as long as you have enough rubies
|
|
case RG_CRAWL:
|
|
return IsChild;
|
|
|
|
// Bottle Items
|
|
case RG_BOTTLE_WITH_BUGS:
|
|
return Get(LOGIC_BUG_ACCESS);
|
|
case RG_BOTTLE_WITH_FISH:
|
|
return Get(LOGIC_FISH_ACCESS);
|
|
case RG_BOTTLE_WITH_BLUE_FIRE:
|
|
return Get(LOGIC_BLUE_FIRE_ACCESS);
|
|
case RG_BOTTLE_WITH_FAIRY:
|
|
return Get(LOGIC_FAIRY_ACCESS);
|
|
|
|
default:
|
|
SPDLOG_INFO("CanUse reached `default` for {}. using HasItem is a minor Optimisation.",
|
|
static_cast<uint32_t>(itemName));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool Logic::HasProjectile(HasProjectileAge age) {
|
|
return HasExplosives() ||
|
|
(age == HasProjectileAge::Child && (CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_BOOMERANG))) ||
|
|
(age == HasProjectileAge::Adult && (CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW))) ||
|
|
(age == HasProjectileAge::Both && (CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_BOOMERANG)) &&
|
|
(CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW))) ||
|
|
(age == HasProjectileAge::Either &&
|
|
(CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_BOOMERANG) || CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW)));
|
|
}
|
|
|
|
bool Logic::HasBossSoul(RandomizerGet itemName) {
|
|
if (!ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS)) {
|
|
return true;
|
|
}
|
|
switch (itemName) {
|
|
case RG_GOHMA_SOUL:
|
|
case RG_KING_DODONGO_SOUL:
|
|
case RG_BARINADE_SOUL:
|
|
case RG_PHANTOM_GANON_SOUL:
|
|
case RG_VOLVAGIA_SOUL:
|
|
case RG_MORPHA_SOUL:
|
|
case RG_BONGO_BONGO_SOUL:
|
|
case RG_TWINROVA_SOUL:
|
|
return HasItem(itemName);
|
|
case RG_GANON_SOUL:
|
|
return ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON) ? HasItem(RG_GANON_SOUL)
|
|
: true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// RANDOMISERTODO intergrate into HasItem
|
|
bool Logic::CanOpenOverworldDoor(RandomizerGet key) {
|
|
if (!ctx->GetOption(RSK_LOCK_OVERWORLD_DOORS)) {
|
|
return true;
|
|
}
|
|
|
|
if (HasItem(RG_SKELETON_KEY)) {
|
|
return true;
|
|
}
|
|
|
|
return HasItem(key);
|
|
}
|
|
|
|
bool Logic::CanGroundJump(bool hasBombflower) {
|
|
return ctx->GetTrickOption(RT_GROUND_JUMP) && CanStandingShield() &&
|
|
(CanUse(RG_BOMB_BAG) || (hasBombflower && HasItem(RG_GORONS_BRACELET)));
|
|
}
|
|
|
|
bool Logic::CanGroundJumpJumpSlash(bool hasBombflower) {
|
|
return ctx->GetTrickOption(RT_GROUND_JUMP_HARD) && CanStandingShield() && CanJumpslash() &&
|
|
(CanUse(RG_BOMB_BAG) || (hasBombflower && HasItem(RG_GORONS_BRACELET)));
|
|
}
|
|
|
|
bool Logic::CanMiddairGroundJump(bool hasBombflower) {
|
|
return ctx->GetTrickOption(RT_GROUND_JUMP_HARD) && CanStandingShield() && CanUse(RG_HOVER_BOOTS) &&
|
|
(CanUse(RG_BOMB_BAG) || (hasBombflower && HasItem(RG_GORONS_BRACELET)));
|
|
}
|
|
|
|
bool Logic::CanOpenUnderwaterChest() {
|
|
return ctx->GetTrickOption(RT_OPEN_UNDERWATER_CHEST) && CanUse(RG_IRON_BOOTS) && CanUse(RG_HOOKSHOT) &&
|
|
HasItem(RG_OPEN_CHEST);
|
|
}
|
|
|
|
uint8_t GetDifficultyValueFromString(Rando::Option& glitchOption) {
|
|
return 0;
|
|
}
|
|
|
|
// todo rewrite glitch section
|
|
|
|
bool Logic::CanEquipSwap(RandomizerGet itemName) {
|
|
if (!HasItem(itemName))
|
|
return false;
|
|
|
|
if (CanDoGlitch(GlitchType::EquipSwapDins) || CanDoGlitch(GlitchType::EquipSwap))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Logic::CanDoGlitch(GlitchType glitch) {
|
|
// TODO: Uncomment when glitches are implemented
|
|
switch (glitch) {
|
|
case GlitchType::EquipSwapDins:
|
|
return ((IsAdult && HasItem(RG_DINS_FIRE)) || (IsChild && (HasItem(RG_STICKS) || HasItem(RG_DINS_FIRE)))) &&
|
|
false; // GlitchEquipSwapDins;
|
|
case GlitchType::EquipSwap: // todo: add bunny hood to adult item equippable list and child trade item to child
|
|
// item equippable list
|
|
return ((IsAdult && (HasItem(RG_DINS_FIRE) || HasItem(RG_FARORES_WIND) || HasItem(RG_NAYRUS_LOVE))) ||
|
|
(IsChild && (HasItem(RG_STICKS) || HasItem(RG_FAIRY_SLINGSHOT) || HasItem(RG_BOOMERANG) ||
|
|
HasBottle() || CanUse(RG_NUTS) || HasItem(RG_FAIRY_OCARINA) ||
|
|
HasItem(RG_LENS_OF_TRUTH) || HasExplosives() || GetAmmo(ITEM_BEAN) > 0 ||
|
|
HasItem(RG_DINS_FIRE) || HasItem(RG_FARORES_WIND) || HasItem(RG_NAYRUS_LOVE)))) &&
|
|
false; // GlitchEquipSwap;
|
|
}
|
|
|
|
// Shouldn't be reached
|
|
return false;
|
|
}
|
|
|
|
// RANDOTODO quantity is a placeholder for proper ammo use calculation logic. in time will want updating to account for
|
|
// ammo capacity
|
|
bool Logic::CanKillEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool wallOrFloor, uint8_t quantity, bool timer,
|
|
bool inWater) {
|
|
bool killed = false;
|
|
switch (enemy) {
|
|
case RE_GERUDO_GUARD:
|
|
case RE_BREAK_ROOM_GUARD:
|
|
return false;
|
|
case RE_GOLD_SKULLTULA:
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
// hammer jumpslash cannot damage these, but hammer swing can
|
|
killed = CanUse(RG_MEGATON_HAMMER);
|
|
[[fallthrough]];
|
|
case ED_SHORT_JUMPSLASH:
|
|
killed = killed || CanUse(RG_KOKIRI_SWORD);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
killed = killed || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
killed = killed || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
killed = killed || CanUse(RG_BOMB_BAG);
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
killed = killed || CanUse(RG_BOOMERANG) || CanUse(RG_DINS_FIRE);
|
|
[[fallthrough]];
|
|
case ED_HOOKSHOT:
|
|
// RANDOTODO test dins and chu range in a practical example
|
|
killed = killed || CanUse(RG_HOOKSHOT);
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
killed = killed || CanUse(RG_LONGSHOT) || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
killed = killed || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return killed;
|
|
case RE_GOHMA_LARVA:
|
|
case RE_MAD_SCRUB:
|
|
case RE_DEKU_BABA:
|
|
case RE_POE:
|
|
return CanAttack();
|
|
case RE_BIG_SKULLTULA:
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
// hammer jumpslash cannot damage these, but hammer swing can
|
|
killed = CanUse(RG_MEGATON_HAMMER);
|
|
[[fallthrough]];
|
|
case ED_SHORT_JUMPSLASH:
|
|
killed = killed || CanUse(RG_KOKIRI_SWORD);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
killed = killed || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
killed = killed || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
killed = killed || CanUse(RG_BOMB_BAG) || CanUse(RG_DINS_FIRE);
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
case ED_HOOKSHOT:
|
|
// RANDOTODO test chu range in a practical example
|
|
killed = killed || CanUse(RG_HOOKSHOT) || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
killed = killed || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
killed = killed || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return killed;
|
|
case RE_DODONGO:
|
|
return CanUseSword() || CanUse(RG_MEGATON_HAMMER) || (quantity <= 5 && CanUse(RG_STICKS)) ||
|
|
HasExplosives() || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
case RE_LIZALFOS:
|
|
return CanJumpslash() || HasExplosives() || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
case RE_KEESE:
|
|
case RE_FIRE_KEESE:
|
|
case RE_GUAY:
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
case ED_SHORT_JUMPSLASH:
|
|
killed = CanUse(RG_MEGATON_HAMMER) || CanUse(RG_KOKIRI_SWORD);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
killed = killed || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
killed = killed || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
// RANDOTODO test chu range in a practical example
|
|
killed = killed || (!inWater && CanUse(RG_BOMB_BAG)) || (enemy == RE_GUAY && CanUse(RG_DINS_FIRE));
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
// RANDOTODO test chu range in a practical example
|
|
killed = killed || CanUse(RG_BOOMERANG);
|
|
[[fallthrough]];
|
|
case ED_HOOKSHOT:
|
|
// RANDOTODO test chu range in a practical example
|
|
killed = killed || CanUse(RG_HOOKSHOT) || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
killed = killed || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
killed = killed || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return killed;
|
|
case RE_BLUE_BUBBLE:
|
|
// RANDOTODO Trick to use shield hylian shield as child to stun these guys
|
|
// RANDOTODO check hammer damage
|
|
return BlastOrSmash() || CanUse(RG_FAIRY_BOW) ||
|
|
((CanJumpslashExceptHammer() || CanUse(RG_FAIRY_SLINGSHOT)) &&
|
|
(CanUse(RG_NUTS) || HookshotOrBoomerang() || CanStandingShield()));
|
|
case RE_DEAD_HAND:
|
|
// RANDOTODO change Dead Hand trick to be sticks Dead Hand
|
|
return CanUseSword() || (CanUse(RG_STICKS) && ctx->GetTrickOption(RT_BOTW_CHILD_DEADHAND));
|
|
case RE_WITHERED_DEKU_BABA:
|
|
return CanUseSword() || CanUse(RG_BOOMERANG);
|
|
case RE_LIKE_LIKE:
|
|
case RE_FLOORMASTER:
|
|
return CanDamage();
|
|
case RE_STALFOS:
|
|
// RANDOTODO Add trick to kill stalfos with sticks, and a second one for bombs without stunning. Higher ammo
|
|
// logic for bombs is also plausible
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
case ED_SHORT_JUMPSLASH:
|
|
killed = CanUse(RG_MEGATON_HAMMER) || CanUse(RG_KOKIRI_SWORD);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
killed = killed || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
killed = killed || CanUse(RG_BIGGORON_SWORD) || (quantity <= 1 && CanUse(RG_STICKS));
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
killed = killed || (quantity <= 2 && !timer && !inWater &&
|
|
(CanUse(RG_NUTS) || HookshotOrBoomerang()) && CanUse(RG_BOMB_BAG));
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
case ED_HOOKSHOT:
|
|
// RANDOTODO test chu range in a practical example
|
|
killed = killed || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
case ED_FAR:
|
|
killed = killed || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return killed;
|
|
// Needs 16 bombs, but is in default logic in N64, probably because getting the hits is quite easy.
|
|
// bow and sling can wake them and damage after they shed their armour, so could reduce ammo requirements for
|
|
// explosives to 10. requires 8 sticks to kill so would be a trick unless we apply higher stick bag logic
|
|
case RE_IRON_KNUCKLE:
|
|
return CanUseSword() || CanUse(RG_MEGATON_HAMMER) || HasExplosives();
|
|
// To stun flare dancer with chus, you have to hit the flame under it while it is spinning. It should eventually
|
|
// return to spinning after dashing for a while if you miss the window it is possible to damage the core with
|
|
// explosives, but difficult to get all 4 hits in even with chus, and if it reconstructs the core heals, so it
|
|
// would be a trick. the core takes damage from hookshot even if it doesn't show Dins killing isn't hard, but is
|
|
// obscure and tight on single magic, so is a trick
|
|
case RE_FLARE_DANCER:
|
|
return CanUse(RG_MEGATON_HAMMER) || CanUse(RG_HOOKSHOT) ||
|
|
(HasExplosives() && (CanJumpslashExceptHammer() || CanUse(RG_FAIRY_BOW) ||
|
|
CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_BOOMERANG)));
|
|
case RE_WOLFOS:
|
|
case RE_WHITE_WOLFOS:
|
|
case RE_WALLMASTER:
|
|
return CanJumpslash() || CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_BOMBCHU_5) ||
|
|
CanUse(RG_DINS_FIRE) ||
|
|
(CanUse(RG_BOMB_BAG) && (CanUse(RG_NUTS) || CanUse(RG_HOOKSHOT) || CanUse(RG_BOOMERANG)));
|
|
case RE_GERUDO_WARRIOR:
|
|
return CanJumpslash() || CanUse(RG_FAIRY_BOW) ||
|
|
(ctx->GetTrickOption(RT_GF_WARRIOR_WITH_DIFFICULT_WEAPON) &&
|
|
(CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_BOMBCHU_5)));
|
|
case RE_GIBDO:
|
|
case RE_REDEAD:
|
|
return CanJumpslash() || CanUse(RG_DINS_FIRE);
|
|
case RE_MEG:
|
|
return CanUse(RG_FAIRY_BOW) || CanUse(RG_HOOKSHOT) || HasExplosives();
|
|
case RE_ARMOS:
|
|
return BlastOrSmash() || CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS) ||
|
|
CanUse(RG_FAIRY_BOW) ||
|
|
((CanUse(RG_NUTS) || CanUse(RG_HOOKSHOT) || CanUse(RG_BOOMERANG)) &&
|
|
(CanUse(RG_KOKIRI_SWORD) || CanUse(RG_FAIRY_SLINGSHOT)));
|
|
case RE_GREEN_BUBBLE:
|
|
// does not technically need to be stunned to kill with dins, but the flame must be off and timing it is
|
|
// awkward Also they don't trigger the kill room in ganons MQ if they die from dins? Vanilla bug?
|
|
return CanJumpslash() || CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT) ||
|
|
HasExplosives() /* || (CanUse(RG_DINS_FIRE) && (CanUse(RG_NUTS) || CanUse(RG_HOOKSHOT) ||
|
|
CanUse(RG_BOOMERANG)))*/
|
|
;
|
|
case RE_DINOLFOS:
|
|
// stunning + bombs is possible but painful, as it loves to dodge the bombs and hookshot. it also dodges
|
|
// chus but if you cook it so it detonates under the dodge it usually gets caught on landing
|
|
return CanJumpslash() || CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT) ||
|
|
(!timer && CanUse(RG_BOMBCHU_5));
|
|
case RE_TORCH_SLUG:
|
|
return CanJumpslash() || HasExplosives() || CanUse(RG_FAIRY_BOW);
|
|
case RE_FREEZARD:
|
|
return CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_MEGATON_HAMMER) ||
|
|
CanUse(RG_STICKS) || HasExplosives() || CanUse(RG_HOOKSHOT) || CanUse(RG_DINS_FIRE) ||
|
|
CanUse(RG_FIRE_ARROWS);
|
|
case RE_SHELL_BLADE:
|
|
return CanJumpslash() || HasExplosives() || CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW) ||
|
|
CanUse(RG_DINS_FIRE);
|
|
case RE_SPIKE:
|
|
return CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_MEGATON_HAMMER) ||
|
|
CanUse(RG_STICKS) || HasExplosives() || CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW) ||
|
|
CanUse(RG_DINS_FIRE);
|
|
case RE_STINGER:
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
case ED_SHORT_JUMPSLASH:
|
|
killed = CanUse(RG_MEGATON_HAMMER) || CanUse(RG_KOKIRI_SWORD);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
killed = killed || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
killed = killed || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
killed = killed || (!inWater && CanUse(RG_BOMB_BAG));
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
case ED_HOOKSHOT:
|
|
// RANDOTODO test chu range in a practical example
|
|
killed = killed || CanUse(RG_HOOKSHOT) || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
killed = killed || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
killed = killed || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return killed;
|
|
case RE_BIG_OCTO:
|
|
// If chasing octo is annoying but with rolls you can catch him, and you need rang to get into this room
|
|
// without shenanigans anyway. Bunny makes it free
|
|
return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_STICKS) || CanUse(RG_MASTER_SWORD);
|
|
case RE_GOHMA:
|
|
return HasBossSoul(RG_GOHMA_SOUL) && CanJumpslash() &&
|
|
(CanUse(RG_NUTS) || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW) || HookshotOrBoomerang());
|
|
case RE_KING_DODONGO:
|
|
return HasBossSoul(RG_KING_DODONGO_SOUL) && CanJumpslash() &&
|
|
(CanUse(RG_BOMB_BAG) || HasItem(RG_GORONS_BRACELET) ||
|
|
(ctx->GetTrickOption(RT_DC_DODONGO_CHU) && IsAdult && CanUse(RG_BOMBCHU_5)));
|
|
case RE_BARINADE:
|
|
return HasBossSoul(RG_BARINADE_SOUL) && CanUse(RG_BOOMERANG) &&
|
|
(CanJumpslashExceptHammer() ||
|
|
(ctx->GetTrickOption(RT_JABU_BARINADE_POTS) && HasItem(RG_POWER_BRACELET)));
|
|
case RE_PHANTOM_GANON:
|
|
return HasBossSoul(RG_PHANTOM_GANON_SOUL) && CanUseSword() &&
|
|
(CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT));
|
|
case RE_VOLVAGIA:
|
|
return HasBossSoul(RG_VOLVAGIA_SOUL) && CanUse(RG_MEGATON_HAMMER);
|
|
case RE_MORPHA:
|
|
return HasBossSoul(RG_MORPHA_SOUL) &&
|
|
(CanUse(RG_HOOKSHOT) ||
|
|
(ctx->GetTrickOption(RT_WATER_MORPHA_WITHOUT_HOOKSHOT) && HasItem(RG_BRONZE_SCALE))) &&
|
|
(CanUseSword() || CanUse(RG_MEGATON_HAMMER));
|
|
case RE_BONGO_BONGO:
|
|
return HasBossSoul(RG_BONGO_BONGO_SOUL) &&
|
|
(CanUse(RG_LENS_OF_TRUTH) || ctx->GetTrickOption(RT_LENS_BONGO)) && CanUseSword() &&
|
|
(CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT) ||
|
|
ctx->GetTrickOption(RT_SHADOW_BONGO));
|
|
case RE_TWINROVA:
|
|
return HasBossSoul(RG_TWINROVA_SOUL) && CanUse(RG_MIRROR_SHIELD) &&
|
|
(CanUseSword() || CanUse(RG_MEGATON_HAMMER));
|
|
case RE_GANONDORF:
|
|
// RANDOTODO: Trick to use hammer (no jumpslash) or stick (only jumpslash) instead of a sword to reflect the
|
|
// energy ball and either of them regardless of jumpslashing to damage and kill ganondorf
|
|
|
|
// Bottle is not taken into account since a sword, hammer or stick are required
|
|
// for killing ganondorf and all of those can reflect the energy ball
|
|
// This will not be the case once ammo logic in taken into account as
|
|
// sticks are limited and using a bottle might become a requirement in that case
|
|
return HasBossSoul(RG_GANON_SOUL) && CanUse(RG_LIGHT_ARROWS) && CanUseSword();
|
|
case RE_GANON:
|
|
return HasBossSoul(RG_GANON_SOUL) && CanUse(RG_MASTER_SWORD);
|
|
case RE_DARK_LINK:
|
|
// RANDOTODO make a function to track our ammo vs his HP when ammo capacity is taken into account in logic
|
|
// all swords can at least trade blows with dark link, and even with 1 damage a slash it works out
|
|
return CanUseSword() ||
|
|
// Boomerang is a relaible, infinite ammo stun, so it enables any way to get enough damage with the
|
|
// ammo we have Max HP dark link has 40 HP, bows and bombs do 2 so 20 ammo, stick jumpslash does 4 so
|
|
// 10 sticks
|
|
(CanUse(RG_BOOMERANG) &&
|
|
(CanUse(RG_FAIRY_BOW) || CanUse(RG_STICKS) || CanUse(RG_MEGATON_HAMMER) || HasExplosives())) ||
|
|
// By using deku nuts against the wall, you can stun him roughly half the time, which makes 4 damage
|
|
// attacks reliable on base nuts
|
|
(CanUse(RG_NUTS) && (CanUse(RG_STICKS) || CanUse(RG_MEGATON_HAMMER)));
|
|
// Dins does 2 damage, but is reliable, so would need 20 casts for max HP dark link. normal magic gives 4
|
|
// casts, double 8, and then potions can add more
|
|
case RE_ANUBIS:
|
|
// there's a restoration that allows beating them with mirror shield + some way to trigger their attack
|
|
return HasFireSource();
|
|
case RE_BEAMOS:
|
|
return HasExplosives();
|
|
case RE_PURPLE_LEEVER:
|
|
// dies on it's own, so this is the conditions to spawn it (killing 10 normal leevers)
|
|
// Sticks and Ice arrows work but will need ammo capacity logic
|
|
// other methods can damage them but not kill them, and they run when hit, making them impractical
|
|
return CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD);
|
|
case RE_TENTACLE:
|
|
return CanUse(RG_BOOMERANG);
|
|
case RE_BARI:
|
|
return HookshotOrBoomerang() || CanUse(RG_FAIRY_BOW) || HasExplosives() || CanUse(RG_MEGATON_HAMMER) ||
|
|
CanUse(RG_STICKS) || CanUse(RG_DINS_FIRE) || (TakeDamage() && CanUseSword());
|
|
case RE_SHABOM:
|
|
return CanUse(RG_BOOMERANG) || CanUse(RG_NUTS) || CanJumpslash() || CanUse(RG_DINS_FIRE) ||
|
|
CanUse(RG_ICE_ARROWS) || EffectiveHealth() * 2 > quantity;
|
|
case RE_OCTOROK:
|
|
return CanReflectNuts() || HookshotOrBoomerang() || CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT) ||
|
|
CanUse(RG_BOMB_BAG) || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
case RE_WALLTULA:
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
case ED_SHORT_JUMPSLASH:
|
|
killed = CanUse(RG_KOKIRI_SWORD);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
killed = killed || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
killed = killed || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
killed = killed || (!inWater && CanUse(RG_BOMB_BAG)) || CanUse(RG_DINS_FIRE);
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
killed = killed || CanUse(RG_BOOMERANG);
|
|
[[fallthrough]];
|
|
case ED_HOOKSHOT:
|
|
killed = killed || CanUse(RG_HOOKSHOT) || CanUse(RG_BOMBCHU_5) || CanUse(RG_MEGATON_HAMMER);
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
killed = killed || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
killed = killed || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return killed;
|
|
default:
|
|
SPDLOG_ERROR("CanKillEnemy reached `default`.");
|
|
assert(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// It is rare for Pass Enemy to need distance, this only happens when the enemy blocks a platform and you can't reach it
|
|
// before it blocks you an example is the Big Skulltula in water room of MQ deku, which is out of sword swing height but
|
|
// blocks off the whole SoT block Can we get past this enemy in a tight space?
|
|
bool Logic::CanPassEnemy(RandomizerEnemy enemy, EnemyDistance distance, bool wallOrFloor) {
|
|
if (CanKillEnemy(enemy, distance, wallOrFloor)) {
|
|
return true;
|
|
}
|
|
switch (enemy) {
|
|
case RE_GOLD_SKULLTULA:
|
|
case RE_GOHMA_LARVA:
|
|
case RE_LIZALFOS:
|
|
case RE_DODONGO: // RANDOTODO do dodongos block the way in tight corridors?
|
|
case RE_MAD_SCRUB:
|
|
case RE_KEESE:
|
|
case RE_FIRE_KEESE:
|
|
case RE_BLUE_BUBBLE:
|
|
case RE_DEAD_HAND:
|
|
case RE_DEKU_BABA:
|
|
case RE_WITHERED_DEKU_BABA:
|
|
case RE_STALFOS:
|
|
case RE_FLARE_DANCER:
|
|
case RE_WOLFOS:
|
|
case RE_WHITE_WOLFOS:
|
|
case RE_FLOORMASTER:
|
|
case RE_MEG:
|
|
case RE_ARMOS:
|
|
case RE_FREEZARD:
|
|
case RE_SPIKE:
|
|
case RE_DARK_LINK:
|
|
case RE_ANUBIS:
|
|
case RE_WALLMASTER:
|
|
case RE_PURPLE_LEEVER:
|
|
case RE_OCTOROK:
|
|
return true;
|
|
case RE_GERUDO_GUARD:
|
|
return ctx->GetTrickOption(RT_PASS_GUARDS_WITH_NOTHING) || HasItem(RG_GERUDO_MEMBERSHIP_CARD) ||
|
|
CanUse(RG_FAIRY_BOW) || CanUse(RG_HOOKSHOT);
|
|
case RE_BREAK_ROOM_GUARD:
|
|
return HasItem(RG_GERUDO_MEMBERSHIP_CARD) || CanUse(RG_FAIRY_BOW) || CanUse(RG_HOOKSHOT);
|
|
case RE_BIG_SKULLTULA:
|
|
// hammer jumpslash can pass, but only on flat land where you can kill with hammer swing
|
|
return CanUse(RG_NUTS) || CanUse(RG_BOOMERANG) ||
|
|
(ctx->GetTrickOption(RT_BIG_SKULLTULA_PAUSE_LIFT) && wallOrFloor && distance == ED_CLOSE);
|
|
case RE_LIKE_LIKE:
|
|
return CanUse(RG_HOOKSHOT) || CanUse(RG_BOOMERANG);
|
|
case RE_GIBDO:
|
|
case RE_REDEAD:
|
|
// You can move slowly to avoid getting screamed at
|
|
return true; // CanUse(RG_HOOKSHOT) || CanUse(RG_SUNS_SONG);
|
|
case RE_IRON_KNUCKLE:
|
|
case RE_BIG_OCTO:
|
|
case RE_WALLTULA: // consistent with RT_SPIRIT_WALL
|
|
return false;
|
|
case RE_GREEN_BUBBLE:
|
|
return TakeDamage() || CanUse(RG_NUTS) || CanUse(RG_BOOMERANG) || CanUse(RG_HOOKSHOT);
|
|
default:
|
|
SPDLOG_ERROR("CanPassEnemy reached `default`.");
|
|
assert(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Can we avoid this enemy while climbing up a wall, or doing a difficult platforming challenge?
|
|
// use grounded if the challenge is such that the enemy interfears even if it cannot hit link out of the air
|
|
bool Logic::CanAvoidEnemy(RandomizerEnemy enemy, bool grounded, uint8_t quantity) {
|
|
// DISTANCE AND WALL ASSUMED, add more arguments later if needed
|
|
if (CanKillEnemy(enemy, ED_CLOSE, true, quantity)) {
|
|
return true;
|
|
}
|
|
switch (enemy) {
|
|
case RE_GOLD_SKULLTULA:
|
|
case RE_GOHMA_LARVA:
|
|
case RE_LIZALFOS:
|
|
case RE_DODONGO:
|
|
case RE_BIG_SKULLTULA:
|
|
case RE_DEAD_HAND:
|
|
case RE_DEKU_BABA:
|
|
case RE_WITHERED_DEKU_BABA:
|
|
case RE_LIKE_LIKE:
|
|
case RE_STALFOS:
|
|
case RE_IRON_KNUCKLE:
|
|
case RE_FLARE_DANCER:
|
|
case RE_WOLFOS:
|
|
case RE_WHITE_WOLFOS:
|
|
case RE_FLOORMASTER:
|
|
case RE_REDEAD:
|
|
case RE_MEG:
|
|
case RE_ARMOS:
|
|
case RE_GREEN_BUBBLE:
|
|
case RE_FREEZARD:
|
|
case RE_SHELL_BLADE:
|
|
case RE_SPIKE:
|
|
case RE_BIG_OCTO:
|
|
case RE_GIBDO:
|
|
case RE_DARK_LINK:
|
|
case RE_WALLMASTER:
|
|
case RE_ANUBIS:
|
|
case RE_PURPLE_LEEVER:
|
|
case RE_WALLTULA:
|
|
return true;
|
|
case RE_BEAMOS:
|
|
return !grounded || CanUse(RG_NUTS) || CanUse(RG_DINS_FIRE) ||
|
|
(quantity == 1 && (CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT)));
|
|
case RE_MAD_SCRUB:
|
|
return !grounded || CanUse(RG_NUTS);
|
|
case RE_KEESE:
|
|
case RE_FIRE_KEESE:
|
|
case RE_GUAY:
|
|
return CanUse(RG_NUTS) || CanUse(RG_SKULL_MASK);
|
|
case RE_BLUE_BUBBLE:
|
|
// RANDOTODO Trick to use shield hylian shield as child to stun these guys
|
|
return !grounded || CanUse(RG_NUTS) || HookshotOrBoomerang() || CanStandingShield();
|
|
case RE_TORCH_SLUG:
|
|
return !grounded || CanUse(RG_NUTS) || CanUse(RG_HOOKSHOT) || CanUse(RG_DINS_FIRE);
|
|
default:
|
|
SPDLOG_ERROR("CanAvoidEnemy reached `default`.");
|
|
assert(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Logic::CanGetEnemyDrop(RandomizerEnemy enemy, EnemyDistance distance, bool aboveLink) {
|
|
if (!CanKillEnemy(enemy, distance)) {
|
|
return false;
|
|
}
|
|
// RANDOTODO assumption broke with RC_WATER_TEMPLE_GS_BEHIND_GATE, redesign GS helpers
|
|
if (distance <= ED_MASTER_SWORD_JUMPSLASH) {
|
|
return true;
|
|
}
|
|
bool drop = false;
|
|
switch (enemy) {
|
|
case RE_GOLD_SKULLTULA:
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
case ED_SHORT_JUMPSLASH:
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
case ED_LONG_JUMPSLASH:
|
|
case ED_BOMB_THROW:
|
|
case ED_BOOMERANG:
|
|
drop = drop || CanUse(RG_BOOMERANG);
|
|
[[fallthrough]];
|
|
case ED_HOOKSHOT:
|
|
drop = drop || CanUse(RG_HOOKSHOT);
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
drop = drop || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
break;
|
|
// RANDOTODO double check all jumpslash kills that might be out of jump/backflip range
|
|
}
|
|
return drop;
|
|
break;
|
|
case RE_KEESE:
|
|
case RE_FIRE_KEESE:
|
|
case RE_GUAY:
|
|
return true;
|
|
default:
|
|
return aboveLink || (distance <= ED_BOOMERANG && CanUse(RG_BOOMERANG));
|
|
}
|
|
}
|
|
|
|
bool Logic::CanBreakMudWalls() {
|
|
return BlastOrSmash() || (ctx->GetTrickOption(RT_BLUE_FIRE_MUD_WALLS) && BlueFire());
|
|
}
|
|
|
|
bool Logic::CanGetDekuBabaSticks() {
|
|
return CanUseSword() || CanUse(RG_BOOMERANG);
|
|
}
|
|
|
|
bool Logic::CanGetDekuBabaNuts() {
|
|
return CanJumpslash() || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW) || HasExplosives() ||
|
|
CanUse(RG_DINS_FIRE);
|
|
}
|
|
|
|
bool Logic::CanHitEyeTargets() {
|
|
return CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT);
|
|
}
|
|
|
|
bool Logic::CanDetonateBombFlowers() {
|
|
return CanUse(RG_FAIRY_BOW) || HasExplosives() || CanUse(RG_DINS_FIRE);
|
|
}
|
|
|
|
bool Logic::CanDetonateUprightBombFlower() {
|
|
return CanDetonateBombFlowers() || HasItem(RG_GORONS_BRACELET) ||
|
|
(ctx->GetTrickOption(RT_BLUE_FIRE_MUD_WALLS) && CanUse(RG_BOTTLE_WITH_BLUE_FIRE) &&
|
|
(EffectiveHealth() != 1 || CanUse(RG_NAYRUS_LOVE)));
|
|
}
|
|
|
|
bool Logic::CanHammerRecoilHover(bool needShield) {
|
|
return CanUse(RG_HOVER_BOOTS) && ctx->GetTrickOption(RT_HOVER_BOOST_SIMPLE) && CanUse(RG_MEGATON_HAMMER) &&
|
|
(!needShield || CanStandingShield());
|
|
}
|
|
|
|
bool Logic::Water3FCentralToHighEmblem() {
|
|
return (IsAdult && (CanUse(RG_HOVER_BOOTS) ||
|
|
(ctx->GetTrickOption(RT_DAMAGE_BOOST_SIMPLE) && CanUse(RG_BOMB_BAG) && TakeDamage()))) ||
|
|
(ctx->GetTrickOption(RT_GROUND_JUMP_HARD) && CanGroundJump() && CanUse(RG_HOVER_BOOTS)) ||
|
|
(Get(LOGIC_WATER_SCARECROW) && CanUse(RG_HOOKSHOT));
|
|
}
|
|
|
|
bool Logic::WaterRisingTargetTo3FCentral() {
|
|
return CanUse(RG_LONGSHOT) ||
|
|
(ctx->GetTrickOption(RT_HOVER_BOOST_SIMPLE) && ctx->GetTrickOption(RT_DAMAGE_BOOST_SIMPLE) &&
|
|
HasExplosives() && CanUse(RG_HOVER_BOOTS));
|
|
}
|
|
|
|
/* Water level has 7 events that govern it's logic.
|
|
* LOGIC_WATER_LOW, LOGIC_WATER_MIDDLE say that the player for sure can set the water to this level
|
|
* the COULD varients of these 2, as well as LOGIC_WATER_HIGH instead check for if using those emblems would be possible
|
|
* if the player had a specific water level and ZL
|
|
* - LOGIC_WATER_COULD_LOW checks if the water level could be set low with water agnostic access
|
|
* - LOGIC_WATER_COULD_MIDDLE checks if the water level could be set mid if it was set to low
|
|
* - LOGIC_WATER_HIGH checks if the water level could be set high with water agnostic access,
|
|
* HIGH is the default, so we don't need to check if we can really set it, only that we could reset it if it was
|
|
* changed out of logic
|
|
*
|
|
* Extending from these 3, LOGIC_WATER_COULD_LOW_FROM_HIGH and LOGIC_WATER_COULD_HIGH_FROM_MID tell us if we can move
|
|
* from 1 level to the next, without us first having to confirm we can always do the preceeding level first. These allow
|
|
* us to check for conditions where we can complete a water level loop and reach any level from any level before we know
|
|
* for sure we have real access. MIDDLE_EMBLEM always requires low water, so FROM_LOW is implied in
|
|
* LOGIC_WATER_COULD_MIDDLE.
|
|
*
|
|
* These exist because we can deduce we have access from knowing we always have access to a water level, and can then
|
|
* change it as needed
|
|
*/
|
|
bool Logic::WaterLevel(RandoWaterLevel level) {
|
|
switch (level) {
|
|
case WL_LOW:
|
|
return Get(LOGIC_WATER_LOW) ||
|
|
// if we could get LOW from HIGH and HIGH from MID, then we can move to LOW from any water level
|
|
(Get(LOGIC_WATER_COULD_LOW_FROM_HIGH) &&
|
|
(Get(LOGIC_WATER_COULD_HIGH_FROM_MID) || Get(LOGIC_WATER_HIGH)) && CanUse(RG_ZELDAS_LULLABY));
|
|
case WL_LOW_OR_MID:
|
|
return Get(LOGIC_WATER_LOW) || Get(LOGIC_WATER_MIDDLE) ||
|
|
// The water level is either at HIGH, in which case we can set it to LOW, LOW, or MID, so we only
|
|
// have to check COULD_LOW and ZL
|
|
((Get(LOGIC_WATER_COULD_LOW_FROM_HIGH) || Get(LOGIC_WATER_LOW)) && CanUse(RG_ZELDAS_LULLABY));
|
|
case WL_MID:
|
|
return Get(LOGIC_WATER_MIDDLE) ||
|
|
// LOGIC_WATER_COULD_MIDDLE is LOGIC_WATER_COULD_MIDDLE_FROM_LOW in practice, due to WL_LOW being a
|
|
// hard requirement for WL_MID
|
|
(Get(LOGIC_WATER_LOW) && Get(LOGIC_WATER_COULD_MIDDLE)) ||
|
|
// If we have COULD_MIDDLE, we know we could move to LOW from HIGH,
|
|
// we're either already MID, on LOW can set MID, or on HIGH so you can set LOW and thus MID.
|
|
((Get(LOGIC_WATER_COULD_LOW_FROM_HIGH) || Get(LOGIC_WATER_COULD_LOW)) &&
|
|
Get(LOGIC_WATER_COULD_MIDDLE) && CanUse(RG_ZELDAS_LULLABY));
|
|
case WL_HIGH:
|
|
// If we don't have ZL, we're stuck on high anyway, so we only need to check for if we can reset it to high
|
|
return Get(LOGIC_WATER_HIGH) ||
|
|
// If water is MID and we COULD_HIGH_FROM_MID, then if water is MID we can set it HIGH
|
|
// so we only need to check if we could make it MID from LOW
|
|
(Get(LOGIC_WATER_COULD_HIGH_FROM_MID) && Get(LOGIC_WATER_COULD_MIDDLE));
|
|
case WL_HIGH_OR_MID:
|
|
// If we don't have ZL, we're stuck on high anyway, so we only need to check for if we can reset it to high
|
|
return Get(LOGIC_WATER_MIDDLE) || Get(LOGIC_WATER_HIGH) ||
|
|
// The water level is either at LOW, in which case COULD_MIDDLE can set it to MID, MID, or HIGH, so
|
|
// we only have to check COULD_MIDDLE if we don't have ZL, then we are at high, so we can skip that
|
|
// too
|
|
(Get(LOGIC_WATER_COULD_MIDDLE));
|
|
}
|
|
SPDLOG_ERROR("WaterLevel reached `return false;`. Missing case for a Water Level");
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
Logic::Logic() {
|
|
}
|
|
|
|
uint8_t Logic::BottleCount() {
|
|
uint8_t count = 0;
|
|
for (int i = SLOT_BOTTLE_1; i <= SLOT_BOTTLE_4; i++) {
|
|
uint8_t item = GetSaveContext()->inventory.items[i];
|
|
switch (item) {
|
|
case ITEM_LETTER_RUTO:
|
|
if (Get(LOGIC_DELIVER_RUTOS_LETTER)) {
|
|
count++;
|
|
}
|
|
break;
|
|
case ITEM_BIG_POE:
|
|
if (Get(LOGIC_CAN_EMPTY_BIG_POES)) {
|
|
count++;
|
|
}
|
|
break;
|
|
case ITEM_NONE:
|
|
break;
|
|
default:
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
uint8_t Logic::OcarinaButtons() {
|
|
return HasItem(RG_OCARINA_A_BUTTON) + HasItem(RG_OCARINA_C_LEFT_BUTTON) + HasItem(RG_OCARINA_C_RIGHT_BUTTON) +
|
|
HasItem(RG_OCARINA_C_UP_BUTTON) + HasItem(RG_OCARINA_C_DOWN_BUTTON);
|
|
}
|
|
|
|
bool Logic::HasBottle() {
|
|
return BottleCount() >= 1;
|
|
}
|
|
|
|
bool Logic::CanUseSword() {
|
|
return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_MASTER_SWORD) || CanUse(RG_BIGGORON_SWORD);
|
|
}
|
|
|
|
bool Logic::CanJumpslashExceptHammer() {
|
|
// Not including hammer as hammer jump attacks can be weird;
|
|
return CanUse(RG_STICKS) || CanUseSword();
|
|
}
|
|
|
|
bool Logic::CanJumpslash() {
|
|
return CanJumpslashExceptHammer() || CanUse(RG_MEGATON_HAMMER);
|
|
}
|
|
|
|
bool Logic::CanClearStalagmite() {
|
|
return CanJumpslash() || HasExplosives() || CanUse(RG_GIANTS_KNIFE) ||
|
|
(ctx->GetTrickOption(RT_ICE_STALAGMITE_HOOKSHOT) && CanUse(RG_HOOKSHOT));
|
|
}
|
|
|
|
bool Logic::CanHitSwitch(EnemyDistance distance, bool inWater) {
|
|
bool hit = false;
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
case ED_SHORT_JUMPSLASH:
|
|
hit = CanUse(RG_KOKIRI_SWORD) || CanUse(RG_MEGATON_HAMMER) || CanUse(RG_GIANTS_KNIFE);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
hit = hit || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
hit = hit || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
hit = hit || (!inWater && CanUse(RG_BOMB_BAG));
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
hit = hit || CanUse(RG_BOOMERANG);
|
|
[[fallthrough]];
|
|
case ED_HOOKSHOT:
|
|
// RANDOTODO test chu range in a practical example
|
|
hit = hit || CanUse(RG_HOOKSHOT) || CanUse(RG_BOMBCHU_5);
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
hit = hit || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
hit = hit || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
bool Logic::CanDamage() {
|
|
return CanUse(RG_FAIRY_SLINGSHOT) || CanJumpslash() || HasExplosives() || CanUse(RG_DINS_FIRE) ||
|
|
CanUse(RG_FAIRY_BOW);
|
|
}
|
|
|
|
bool Logic::CanAttack() {
|
|
return CanDamage() || CanUse(RG_BOOMERANG) || CanUse(RG_HOOKSHOT);
|
|
}
|
|
|
|
bool Logic::BombchusEnabled() {
|
|
return ctx->GetOption(RSK_BOMBCHU_BAG).IsNot(RO_BOMBCHU_BAG_NONE) ? CheckInventory(ITEM_BOMBCHU, true)
|
|
: HasItem(RG_BOMB_BAG);
|
|
}
|
|
|
|
// TODO: Implement Ammo Drop Setting in place of bombchu drops
|
|
bool Logic::BombchuRefill() {
|
|
return Get(LOGIC_BUY_BOMBCHUS) || Get(LOGIC_COULD_PLAY_BOWLING) || Get(LOGIC_CARPET_MERCHANT) ||
|
|
(ctx->GetOption(RSK_ENABLE_BOMBCHU_DROPS).Is(RO_AMMO_DROPS_ON /*_PLUS_BOMBCHU*/));
|
|
}
|
|
|
|
bool Logic::HookshotOrBoomerang() {
|
|
return CanUse(RG_HOOKSHOT) || CanUse(RG_BOOMERANG);
|
|
}
|
|
|
|
bool Logic::ScarecrowsSong() {
|
|
return (ctx->GetOption(RSK_SKIP_SCARECROWS_SONG) && HasItem(RG_FAIRY_OCARINA) && OcarinaButtons() >= 2) ||
|
|
(Get(LOGIC_CHILD_SCARECROW) && Get(LOGIC_ADULT_SCARECROW));
|
|
}
|
|
|
|
bool Logic::BlueFire() {
|
|
return CanUse(RG_BOTTLE_WITH_BLUE_FIRE) || (ctx->GetOption(RSK_BLUE_FIRE_ARROWS) && CanUse(RG_ICE_ARROWS));
|
|
}
|
|
|
|
bool Logic::CanBreakPots(EnemyDistance distance, bool wallOrFloor, bool inWater) {
|
|
bool hit = false;
|
|
switch (distance) {
|
|
case ED_CLOSE:
|
|
hit = HasItem(RG_POWER_BRACELET);
|
|
[[fallthrough]];
|
|
case ED_SHORT_JUMPSLASH:
|
|
hit = hit || CanUse(RG_KOKIRI_SWORD) || CanUse(RG_MEGATON_HAMMER) || CanUse(RG_GIANTS_KNIFE);
|
|
[[fallthrough]];
|
|
case ED_MASTER_SWORD_JUMPSLASH:
|
|
hit = hit || CanUse(RG_MASTER_SWORD);
|
|
[[fallthrough]];
|
|
case ED_LONG_JUMPSLASH:
|
|
hit = hit || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_STICKS);
|
|
[[fallthrough]];
|
|
case ED_BOMB_THROW:
|
|
hit = hit || (!inWater && CanUse(RG_BOMB_BAG));
|
|
[[fallthrough]];
|
|
case ED_BOOMERANG:
|
|
hit = hit || CanUse(RG_BOOMERANG);
|
|
[[fallthrough]];
|
|
case ED_HOOKSHOT:
|
|
hit = hit || CanUse(RG_HOOKSHOT);
|
|
[[fallthrough]];
|
|
case ED_LONGSHOT:
|
|
hit = hit || CanUse(RG_LONGSHOT);
|
|
[[fallthrough]];
|
|
case ED_FAR:
|
|
hit = hit || CanUse(RG_FAIRY_SLINGSHOT) || CanUse(RG_FAIRY_BOW);
|
|
break;
|
|
}
|
|
return hit || (wallOrFloor && CanUse(RG_BOMBCHU_5));
|
|
}
|
|
|
|
bool Logic::CanBreakCrates() {
|
|
return true;
|
|
}
|
|
|
|
bool Logic::CanBreakSmallCrates() {
|
|
return CanJumpslash() || HasExplosives() || HasItem(RG_POWER_BRACELET);
|
|
}
|
|
|
|
bool Logic::CanBonkTrees() {
|
|
return true;
|
|
}
|
|
|
|
bool Logic::HasExplosives() {
|
|
return CanUse(RG_BOMB_BAG) || CanUse(RG_BOMBCHU_5);
|
|
}
|
|
|
|
bool Logic::BlastOrSmash() {
|
|
return HasExplosives() || CanUse(RG_MEGATON_HAMMER);
|
|
}
|
|
|
|
bool Logic::CanSpawnSoilSkull(RandomizerGet bean) {
|
|
return IsChild && CanUse(RG_BOTTLE_WITH_BUGS) && HasItem(bean);
|
|
}
|
|
|
|
bool Logic::CanReflectNuts() {
|
|
return CanUse(RG_DEKU_SHIELD) || (IsAdult && HasItem(RG_HYLIAN_SHIELD));
|
|
}
|
|
|
|
bool Logic::CanCutShrubs() {
|
|
return CanUse(RG_KOKIRI_SWORD) || CanUse(RG_BOOMERANG) || HasExplosives() || CanUse(RG_MASTER_SWORD) ||
|
|
CanUse(RG_MEGATON_HAMMER) || CanUse(RG_BIGGORON_SWORD) || CanUse(RG_GIANTS_KNIFE) ||
|
|
HasItem(RG_GORONS_BRACELET);
|
|
}
|
|
|
|
bool Logic::CanStunDeku() {
|
|
return CanAttack() || CanUse(RG_NUTS) || CanReflectNuts();
|
|
}
|
|
|
|
bool Logic::CallGossipFairyExceptSuns() {
|
|
return CanUse(RG_ZELDAS_LULLABY) || CanUse(RG_EPONAS_SONG) || CanUse(RG_SONG_OF_TIME);
|
|
}
|
|
|
|
bool Logic::CallGossipFairy() {
|
|
return CallGossipFairyExceptSuns() || CanUse(RG_SUNS_SONG);
|
|
}
|
|
|
|
// the number returned by this is in half heart hits taken.
|
|
// RANDOTODO work in OoT side health instead for greater applicability (16 per heart)
|
|
uint8_t Logic::EffectiveHealth() {
|
|
/* Multiplier will be:
|
|
0 for half daamge
|
|
1 for normal damage
|
|
2 for double damage
|
|
3 for quad damage
|
|
4 for 8* damage
|
|
5 for 16* damage
|
|
10 for OHKO.
|
|
This is the number of shifts to apply, not a real multiplier
|
|
*/
|
|
uint8_t Multiplier =
|
|
(ctx->GetOption(RSK_DAMAGE_MULTIPLIER).Get() < 6) ? ctx->GetOption(RSK_DAMAGE_MULTIPLIER).Get() : 10;
|
|
//(Hearts() << (2 + HasItem(RG_DOUBLE_DEFENSE))) is quarter hearts after DD
|
|
//>> Multiplier halves on normal and does nothing on half, meaning we're working with half hearts on normal damage
|
|
return ((Hearts() << (2 + HasItem(RG_DOUBLE_DEFENSE))) >> Multiplier) +
|
|
// As 1 is a quarter heart, (1 << Multiplier) is effectivly half-hearts of unmodified damage
|
|
// Adds an extra hit if the damage is not exact lethal
|
|
((Hearts() << (2 + HasItem(RG_DOUBLE_DEFENSE))) % (1 << Multiplier) > 0);
|
|
}
|
|
|
|
uint8_t Logic::Hearts() {
|
|
return GetSaveContext()->healthCapacity / 16;
|
|
}
|
|
|
|
uint8_t Logic::DungeonCount() {
|
|
return Get(LOGIC_DEKU_TREE_CLEAR) + Get(LOGIC_DODONGOS_CAVERN_CLEAR) + Get(LOGIC_JABU_JABUS_BELLY_CLEAR) +
|
|
Get(LOGIC_FOREST_TEMPLE_CLEAR) + Get(LOGIC_FIRE_TEMPLE_CLEAR) + Get(LOGIC_WATER_TEMPLE_CLEAR) +
|
|
Get(LOGIC_SPIRIT_TEMPLE_CLEAR) + Get(LOGIC_SHADOW_TEMPLE_CLEAR);
|
|
}
|
|
|
|
uint8_t Logic::StoneCount() {
|
|
return HasItem(RG_KOKIRI_EMERALD) + HasItem(RG_GORON_RUBY) + HasItem(RG_ZORA_SAPPHIRE);
|
|
}
|
|
|
|
uint8_t Logic::MedallionCount() {
|
|
return HasItem(RG_FOREST_MEDALLION) + HasItem(RG_FIRE_MEDALLION) + HasItem(RG_WATER_MEDALLION) +
|
|
HasItem(RG_SPIRIT_MEDALLION) + HasItem(RG_SHADOW_MEDALLION) + HasItem(RG_LIGHT_MEDALLION);
|
|
}
|
|
|
|
uint8_t Logic::FireTimer() {
|
|
return CanUse(RG_GORON_TUNIC) ? 255 : (ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS)) ? (Hearts() * 8) : 0;
|
|
}
|
|
|
|
// Tunic is not required if you are using irons to do something that a simple gold scale dive could do, and you are not
|
|
// in water temple. (celing swimming and long walks through water do not count)
|
|
uint8_t Logic::WaterTimer() {
|
|
return CanUse(RG_ZORA_TUNIC) ? 255 : (ctx->GetTrickOption(RT_FEWER_TUNIC_REQUIREMENTS)) ? (Hearts() * 8) : 0;
|
|
}
|
|
|
|
bool Logic::TakeDamage() {
|
|
return CanUse(RG_BOTTLE_WITH_FAIRY) || EffectiveHealth() != 1 || CanUse(RG_NAYRUS_LOVE);
|
|
}
|
|
|
|
bool Logic::CanOpenBombGrotto() {
|
|
return BlastOrSmash() && (HasItem(RG_STONE_OF_AGONY) || ctx->GetTrickOption(RT_GROTTOS_WITHOUT_AGONY));
|
|
}
|
|
|
|
bool Logic::CanOpenStormsGrotto() {
|
|
return CanUse(RG_SONG_OF_STORMS) && (HasItem(RG_STONE_OF_AGONY) || ctx->GetTrickOption(RT_GROTTOS_WITHOUT_AGONY));
|
|
}
|
|
|
|
bool Logic::CanGetNightTimeGS() {
|
|
return AtNight && (CanUse(RG_SUNS_SONG) || !ctx->GetOption(RSK_SKULLS_SUNS_SONG));
|
|
}
|
|
|
|
bool Logic::CanBreakUpperBeehives() {
|
|
return HookshotOrBoomerang() || (ctx->GetTrickOption(RT_BOMBCHU_BEEHIVES) && CanUse(RG_BOMBCHU_5)) ||
|
|
(ctx->GetOption(RSK_SLINGBOW_BREAK_BEEHIVES) && (CanUse(RG_FAIRY_BOW) || CanUse(RG_FAIRY_SLINGSHOT)));
|
|
}
|
|
|
|
bool Logic::CanBreakLowerBeehives() {
|
|
return CanBreakUpperBeehives() || CanUse(RG_BOMB_BAG);
|
|
}
|
|
|
|
bool Logic::HasFireSource() {
|
|
return CanUse(RG_DINS_FIRE) || CanUse(RG_FIRE_ARROWS);
|
|
}
|
|
|
|
bool Logic::HasFireSourceWithTorch() {
|
|
return HasFireSource() || CanUse(RG_STICKS);
|
|
}
|
|
|
|
bool Logic::SunlightArrows() {
|
|
return ctx->GetOption(RSK_SUNLIGHT_ARROWS) && CanUse(RG_LIGHT_ARROWS);
|
|
}
|
|
|
|
// Is this best off signaling what you have already traded, or what step you are currently on?
|
|
bool Logic::TradeQuestStep(RandomizerGet rg) {
|
|
if (ctx->GetOption(RSK_SHUFFLE_ADULT_TRADE)) {
|
|
return false; // This does not apply when we are shuffling trade items
|
|
}
|
|
bool hasState = false;
|
|
// Falling through each case to test each possibility
|
|
switch (rg) {
|
|
case RG_POCKET_EGG:
|
|
hasState = hasState || HasItem(RG_POCKET_EGG);
|
|
[[fallthrough]];
|
|
case RG_COJIRO:
|
|
hasState = hasState || HasItem(RG_COJIRO);
|
|
[[fallthrough]];
|
|
case RG_ODD_MUSHROOM:
|
|
hasState = hasState || HasItem(RG_ODD_MUSHROOM);
|
|
[[fallthrough]];
|
|
case RG_ODD_POTION:
|
|
hasState = hasState || HasItem(RG_ODD_POTION);
|
|
[[fallthrough]];
|
|
case RG_POACHERS_SAW:
|
|
hasState = hasState || HasItem(RG_POACHERS_SAW);
|
|
[[fallthrough]];
|
|
case RG_BROKEN_SWORD:
|
|
hasState = hasState || HasItem(RG_BROKEN_SWORD);
|
|
[[fallthrough]];
|
|
case RG_PRESCRIPTION:
|
|
hasState = hasState || HasItem(RG_PRESCRIPTION);
|
|
[[fallthrough]];
|
|
case RG_EYEDROPS:
|
|
hasState = hasState || HasItem(RG_EYEDROPS);
|
|
[[fallthrough]];
|
|
case RG_CLAIM_CHECK:
|
|
hasState = hasState || HasItem(RG_CLAIM_CHECK);
|
|
break;
|
|
default:
|
|
SPDLOG_ERROR("TradeQuestStep reached `return false;`. Missing case for RandomizerGet of {}",
|
|
static_cast<uint32_t>(rg));
|
|
assert(false);
|
|
return false;
|
|
}
|
|
return hasState;
|
|
}
|
|
|
|
bool Logic::CanStandingShield() {
|
|
return CanUse(RG_MIRROR_SHIELD) || (IsAdult && HasItem(RG_HYLIAN_SHIELD)) || CanUse(RG_DEKU_SHIELD);
|
|
}
|
|
|
|
bool Logic::CanShield() {
|
|
return CanUse(RG_MIRROR_SHIELD) || HasItem(RG_HYLIAN_SHIELD) || CanUse(RG_DEKU_SHIELD);
|
|
}
|
|
|
|
bool Logic::CanUseProjectile() {
|
|
return HasExplosives() || CanUse(RG_FAIRY_BOW) || CanUse(RG_HOOKSHOT) || CanUse(RG_FAIRY_SLINGSHOT) ||
|
|
CanUse(RG_BOOMERANG);
|
|
}
|
|
|
|
bool Logic::CanBuildRainbowBridge() {
|
|
return ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_ALWAYS_OPEN) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_VANILLA) && HasItem(RG_SHADOW_MEDALLION) &&
|
|
HasItem(RG_SPIRIT_MEDALLION) && CanUse(RG_LIGHT_ARROWS)) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_STONES) &&
|
|
StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).Get()) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_MEDALLIONS) &&
|
|
MedallionCount() +
|
|
(HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Get()) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS) &&
|
|
StoneCount() + MedallionCount() +
|
|
(HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Get()) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS) &&
|
|
DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_BRIDGE_OPTIONS).Is(RO_BRIDGE_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Get()) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) &&
|
|
GetGSCount() >= ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Get()) ||
|
|
(ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG) && HasItem(RG_GREG_RUPEE));
|
|
}
|
|
|
|
bool Logic::CanTriggerLACS() {
|
|
return (ctx->LACSCondition() == RO_LACS_VANILLA && HasItem(RG_SHADOW_MEDALLION) && HasItem(RG_SPIRIT_MEDALLION)) ||
|
|
(ctx->LACSCondition() == RO_LACS_STONES &&
|
|
StoneCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_LACS_STONE_COUNT).Get()) ||
|
|
(ctx->LACSCondition() == RO_LACS_MEDALLIONS &&
|
|
MedallionCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Get()) ||
|
|
(ctx->LACSCondition() == RO_LACS_REWARDS &&
|
|
StoneCount() + MedallionCount() +
|
|
(HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_LACS_REWARD_COUNT).Get()) ||
|
|
(ctx->LACSCondition() == RO_LACS_DUNGEONS &&
|
|
DungeonCount() + (HasItem(RG_GREG_RUPEE) && ctx->GetOption(RSK_LACS_OPTIONS).Is(RO_LACS_GREG_REWARD)) >=
|
|
ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Get()) ||
|
|
(ctx->LACSCondition() == RO_LACS_TOKENS && GetGSCount() >= ctx->GetOption(RSK_LACS_TOKEN_COUNT).Get());
|
|
}
|
|
|
|
bool Logic::SmallKeys(s16 scene, uint8_t requiredAmount) {
|
|
if (HasItem(RG_SKELETON_KEY)) {
|
|
return true;
|
|
}
|
|
return GetSmallKeyCount(scene) >= requiredAmount;
|
|
}
|
|
|
|
std::map<RandomizerGet, uint32_t> Logic::RandoGetToEquipFlag = {
|
|
{ RG_KOKIRI_SWORD, EQUIP_FLAG_SWORD_KOKIRI }, { RG_MASTER_SWORD, EQUIP_FLAG_SWORD_MASTER },
|
|
{ RG_GIANTS_KNIFE, EQUIP_FLAG_SWORD_BGS }, { RG_BIGGORON_SWORD, EQUIP_FLAG_SWORD_BGS },
|
|
{ RG_DEKU_SHIELD, EQUIP_FLAG_SHIELD_DEKU }, { RG_HYLIAN_SHIELD, EQUIP_FLAG_SHIELD_HYLIAN },
|
|
{ RG_MIRROR_SHIELD, EQUIP_FLAG_SHIELD_MIRROR }, { RG_GORON_TUNIC, EQUIP_FLAG_TUNIC_GORON },
|
|
{ RG_ZORA_TUNIC, EQUIP_FLAG_TUNIC_ZORA }, { RG_BUY_DEKU_SHIELD, EQUIP_FLAG_SHIELD_DEKU },
|
|
{ RG_BUY_HYLIAN_SHIELD, EQUIP_FLAG_SHIELD_HYLIAN }, { RG_BUY_GORON_TUNIC, EQUIP_FLAG_TUNIC_GORON },
|
|
{ RG_BUY_ZORA_TUNIC, EQUIP_FLAG_TUNIC_ZORA }, { RG_IRON_BOOTS, EQUIP_FLAG_BOOTS_IRON },
|
|
{ RG_HOVER_BOOTS, EQUIP_FLAG_BOOTS_HOVER }
|
|
};
|
|
|
|
std::map<RandomizerGet, uint32_t> Logic::RandoGetToRandInf = {
|
|
{ RG_ZELDAS_LETTER, RAND_INF_ZELDAS_LETTER },
|
|
{ RG_WEIRD_EGG, RAND_INF_WEIRD_EGG },
|
|
{ RG_RUTOS_LETTER, RAND_INF_OBTAINED_RUTOS_LETTER },
|
|
{ RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL },
|
|
{ RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL },
|
|
{ RG_DESERT_COLOSSUS_BEAN_SOUL, RAND_INF_DESERT_COLOSSUS_BEAN_SOUL },
|
|
{ RG_GERUDO_VALLEY_BEAN_SOUL, RAND_INF_GERUDO_VALLEY_BEAN_SOUL },
|
|
{ RG_GRAVEYARD_BEAN_SOUL, RAND_INF_GRAVEYARD_BEAN_SOUL },
|
|
{ RG_KOKIRI_FOREST_BEAN_SOUL, RAND_INF_KOKIRI_FOREST_BEAN_SOUL },
|
|
{ RG_LAKE_HYLIA_BEAN_SOUL, RAND_INF_LAKE_HYLIA_BEAN_SOUL },
|
|
{ RG_LOST_WOODS_BRIDGE_BEAN_SOUL, RAND_INF_LOST_WOODS_BRIDGE_BEAN_SOUL },
|
|
{ RG_LOST_WOODS_BEAN_SOUL, RAND_INF_LOST_WOODS_BEAN_SOUL },
|
|
{ RG_ZORAS_RIVER_BEAN_SOUL, RAND_INF_ZORAS_RIVER_BEAN_SOUL },
|
|
{ RG_GOHMA_SOUL, RAND_INF_GOHMA_SOUL },
|
|
{ RG_KING_DODONGO_SOUL, RAND_INF_KING_DODONGO_SOUL },
|
|
{ RG_BARINADE_SOUL, RAND_INF_BARINADE_SOUL },
|
|
{ RG_PHANTOM_GANON_SOUL, RAND_INF_PHANTOM_GANON_SOUL },
|
|
{ RG_VOLVAGIA_SOUL, RAND_INF_VOLVAGIA_SOUL },
|
|
{ RG_MORPHA_SOUL, RAND_INF_MORPHA_SOUL },
|
|
{ RG_BONGO_BONGO_SOUL, RAND_INF_BONGO_BONGO_SOUL },
|
|
{ RG_TWINROVA_SOUL, RAND_INF_TWINROVA_SOUL },
|
|
{ RG_GANON_SOUL, RAND_INF_GANON_SOUL },
|
|
{ RG_OCARINA_A_BUTTON, RAND_INF_HAS_OCARINA_A },
|
|
{ RG_OCARINA_C_UP_BUTTON, RAND_INF_HAS_OCARINA_C_UP },
|
|
{ RG_OCARINA_C_DOWN_BUTTON, RAND_INF_HAS_OCARINA_C_DOWN },
|
|
{ RG_OCARINA_C_LEFT_BUTTON, RAND_INF_HAS_OCARINA_C_LEFT },
|
|
{ RG_OCARINA_C_RIGHT_BUTTON, RAND_INF_HAS_OCARINA_C_RIGHT },
|
|
{ RG_KEATON_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_KEATON },
|
|
{ RG_SKULL_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_SKULL },
|
|
{ RG_SPOOKY_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_SPOOKY },
|
|
{ RG_BUNNY_HOOD, RAND_INF_CHILD_TRADES_HAS_MASK_BUNNY },
|
|
{ RG_GORON_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_GORON },
|
|
{ RG_ZORA_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_ZORA },
|
|
{ RG_GERUDO_MASK, RAND_INF_CHILD_TRADES_HAS_MASK_GERUDO },
|
|
{ RG_MASK_OF_TRUTH, RAND_INF_CHILD_TRADES_HAS_MASK_TRUTH },
|
|
{ RG_SKELETON_KEY, RAND_INF_HAS_SKELETON_KEY },
|
|
{ RG_GREG_RUPEE, RAND_INF_GREG_FOUND },
|
|
{ RG_SPEAK_DEKU, RAND_INF_CAN_SPEAK_DEKU },
|
|
{ RG_SPEAK_GERUDO, RAND_INF_CAN_SPEAK_GERUDO },
|
|
{ RG_SPEAK_GORON, RAND_INF_CAN_SPEAK_GORON },
|
|
{ RG_SPEAK_HYLIAN, RAND_INF_CAN_SPEAK_HYLIAN },
|
|
{ RG_SPEAK_KOKIRI, RAND_INF_CAN_SPEAK_KOKIRI },
|
|
{ RG_SPEAK_ZORA, RAND_INF_CAN_SPEAK_ZORA },
|
|
{ RG_FISHING_POLE, RAND_INF_FISHING_POLE_FOUND },
|
|
{ RG_GUARD_HOUSE_KEY, RAND_INF_GUARD_HOUSE_KEY_OBTAINED },
|
|
{ RG_MARKET_BAZAAR_KEY, RAND_INF_MARKET_BAZAAR_KEY_OBTAINED },
|
|
{ RG_MARKET_POTION_SHOP_KEY, RAND_INF_MARKET_POTION_SHOP_KEY_OBTAINED },
|
|
{ RG_MASK_SHOP_KEY, RAND_INF_MASK_SHOP_KEY_OBTAINED },
|
|
{ RG_MARKET_SHOOTING_GALLERY_KEY, RAND_INF_MARKET_SHOOTING_GALLERY_KEY_OBTAINED },
|
|
{ RG_BOMBCHU_BOWLING_KEY, RAND_INF_BOMBCHU_BOWLING_KEY_OBTAINED },
|
|
{ RG_TREASURE_CHEST_GAME_BUILDING_KEY, RAND_INF_TREASURE_CHEST_GAME_BUILDING_KEY_OBTAINED },
|
|
{ RG_BOMBCHU_SHOP_KEY, RAND_INF_BOMBCHU_SHOP_KEY_OBTAINED },
|
|
{ RG_RICHARDS_HOUSE_KEY, RAND_INF_RICHARDS_HOUSE_KEY_OBTAINED },
|
|
{ RG_ALLEY_HOUSE_KEY, RAND_INF_ALLEY_HOUSE_KEY_OBTAINED },
|
|
{ RG_KAK_BAZAAR_KEY, RAND_INF_KAK_BAZAAR_KEY_OBTAINED },
|
|
{ RG_KAK_POTION_SHOP_KEY, RAND_INF_KAK_POTION_SHOP_KEY_OBTAINED },
|
|
{ RG_BOSS_HOUSE_KEY, RAND_INF_BOSS_HOUSE_KEY_OBTAINED },
|
|
{ RG_GRANNYS_POTION_SHOP_KEY, RAND_INF_GRANNYS_POTION_SHOP_KEY_OBTAINED },
|
|
{ RG_SKULLTULA_HOUSE_KEY, RAND_INF_SKULLTULA_HOUSE_KEY_OBTAINED },
|
|
{ RG_IMPAS_HOUSE_KEY, RAND_INF_IMPAS_HOUSE_KEY_OBTAINED },
|
|
{ RG_WINDMILL_KEY, RAND_INF_WINDMILL_KEY_OBTAINED },
|
|
{ RG_KAK_SHOOTING_GALLERY_KEY, RAND_INF_KAK_SHOOTING_GALLERY_KEY_OBTAINED },
|
|
{ RG_DAMPES_HUT_KEY, RAND_INF_DAMPES_HUT_KEY_OBTAINED },
|
|
{ RG_TALONS_HOUSE_KEY, RAND_INF_TALONS_HOUSE_KEY_OBTAINED },
|
|
{ RG_STABLES_KEY, RAND_INF_STABLES_KEY_OBTAINED },
|
|
{ RG_BACK_TOWER_KEY, RAND_INF_BACK_TOWER_KEY_OBTAINED },
|
|
{ RG_HYLIA_LAB_KEY, RAND_INF_HYLIA_LAB_KEY_OBTAINED },
|
|
{ RG_FISHING_HOLE_KEY, RAND_INF_FISHING_HOLE_KEY_OBTAINED },
|
|
};
|
|
|
|
std::map<uint32_t, uint32_t> Logic::RandoGetToDungeonScene = {
|
|
{ RG_FOREST_TEMPLE_SMALL_KEY, SCENE_FOREST_TEMPLE },
|
|
{ RG_FIRE_TEMPLE_SMALL_KEY, SCENE_FIRE_TEMPLE },
|
|
{ RG_WATER_TEMPLE_SMALL_KEY, SCENE_WATER_TEMPLE },
|
|
{ RG_SPIRIT_TEMPLE_SMALL_KEY, SCENE_SPIRIT_TEMPLE },
|
|
{ RG_SHADOW_TEMPLE_SMALL_KEY, SCENE_SHADOW_TEMPLE },
|
|
{ RG_BOTTOM_OF_THE_WELL_SMALL_KEY, SCENE_BOTTOM_OF_THE_WELL },
|
|
{ RG_GERUDO_TRAINING_GROUND_SMALL_KEY, SCENE_GERUDO_TRAINING_GROUND },
|
|
{ RG_GERUDO_FORTRESS_SMALL_KEY, SCENE_THIEVES_HIDEOUT },
|
|
{ RG_GANONS_CASTLE_SMALL_KEY, SCENE_INSIDE_GANONS_CASTLE },
|
|
{ RG_FOREST_TEMPLE_KEY_RING, SCENE_FOREST_TEMPLE },
|
|
{ RG_FIRE_TEMPLE_KEY_RING, SCENE_FIRE_TEMPLE },
|
|
{ RG_WATER_TEMPLE_KEY_RING, SCENE_WATER_TEMPLE },
|
|
{ RG_SPIRIT_TEMPLE_KEY_RING, SCENE_SPIRIT_TEMPLE },
|
|
{ RG_SHADOW_TEMPLE_KEY_RING, SCENE_SHADOW_TEMPLE },
|
|
{ RG_BOTTOM_OF_THE_WELL_KEY_RING, SCENE_BOTTOM_OF_THE_WELL },
|
|
{ RG_GERUDO_TRAINING_GROUND_KEY_RING, SCENE_GERUDO_TRAINING_GROUND },
|
|
{ RG_GERUDO_FORTRESS_KEY_RING, SCENE_THIEVES_HIDEOUT },
|
|
{ RG_GANONS_CASTLE_KEY_RING, SCENE_INSIDE_GANONS_CASTLE },
|
|
{ RG_FOREST_TEMPLE_BOSS_KEY, SCENE_FOREST_TEMPLE },
|
|
{ RG_FIRE_TEMPLE_BOSS_KEY, SCENE_FIRE_TEMPLE },
|
|
{ RG_WATER_TEMPLE_BOSS_KEY, SCENE_WATER_TEMPLE },
|
|
{ RG_SPIRIT_TEMPLE_BOSS_KEY, SCENE_SPIRIT_TEMPLE },
|
|
{ RG_SHADOW_TEMPLE_BOSS_KEY, SCENE_SHADOW_TEMPLE },
|
|
{ RG_GANONS_CASTLE_BOSS_KEY, SCENE_INSIDE_GANONS_CASTLE },
|
|
{ RG_DEKU_TREE_MAP, SCENE_DEKU_TREE },
|
|
{ RG_DODONGOS_CAVERN_MAP, SCENE_DODONGOS_CAVERN },
|
|
{ RG_JABU_JABUS_BELLY_MAP, SCENE_JABU_JABU },
|
|
{ RG_FOREST_TEMPLE_MAP, SCENE_FOREST_TEMPLE },
|
|
{ RG_FIRE_TEMPLE_MAP, SCENE_FIRE_TEMPLE },
|
|
{ RG_WATER_TEMPLE_MAP, SCENE_WATER_TEMPLE },
|
|
{ RG_SPIRIT_TEMPLE_MAP, SCENE_SPIRIT_TEMPLE },
|
|
{ RG_SHADOW_TEMPLE_MAP, SCENE_SHADOW_TEMPLE },
|
|
{ RG_BOTTOM_OF_THE_WELL_MAP, SCENE_BOTTOM_OF_THE_WELL },
|
|
{ RG_ICE_CAVERN_MAP, SCENE_ICE_CAVERN },
|
|
{ RG_DEKU_TREE_COMPASS, SCENE_DEKU_TREE },
|
|
{ RG_DODONGOS_CAVERN_COMPASS, SCENE_DODONGOS_CAVERN },
|
|
{ RG_JABU_JABUS_BELLY_COMPASS, SCENE_JABU_JABU },
|
|
{ RG_FOREST_TEMPLE_COMPASS, SCENE_FOREST_TEMPLE },
|
|
{ RG_FIRE_TEMPLE_COMPASS, SCENE_FIRE_TEMPLE },
|
|
{ RG_WATER_TEMPLE_COMPASS, SCENE_WATER_TEMPLE },
|
|
{ RG_SPIRIT_TEMPLE_COMPASS, SCENE_SPIRIT_TEMPLE },
|
|
{ RG_SHADOW_TEMPLE_COMPASS, SCENE_SHADOW_TEMPLE },
|
|
{ RG_BOTTOM_OF_THE_WELL_COMPASS, SCENE_BOTTOM_OF_THE_WELL },
|
|
{ RG_ICE_CAVERN_COMPASS, SCENE_ICE_CAVERN },
|
|
{ RG_TREASURE_GAME_SMALL_KEY, SCENE_TREASURE_BOX_SHOP }
|
|
};
|
|
|
|
std::map<uint32_t, uint32_t> Logic::RandoGetToQuestItem = {
|
|
{ RG_FOREST_MEDALLION, QUEST_MEDALLION_FOREST },
|
|
{ RG_FIRE_MEDALLION, QUEST_MEDALLION_FIRE },
|
|
{ RG_WATER_MEDALLION, QUEST_MEDALLION_WATER },
|
|
{ RG_SPIRIT_MEDALLION, QUEST_MEDALLION_SPIRIT },
|
|
{ RG_SHADOW_MEDALLION, QUEST_MEDALLION_SHADOW },
|
|
{ RG_LIGHT_MEDALLION, QUEST_MEDALLION_LIGHT },
|
|
{ RG_MINUET_OF_FOREST, QUEST_SONG_MINUET },
|
|
{ RG_BOLERO_OF_FIRE, QUEST_SONG_BOLERO },
|
|
{ RG_SERENADE_OF_WATER, QUEST_SONG_SERENADE },
|
|
{ RG_REQUIEM_OF_SPIRIT, QUEST_SONG_REQUIEM },
|
|
{ RG_NOCTURNE_OF_SHADOW, QUEST_SONG_NOCTURNE },
|
|
{ RG_PRELUDE_OF_LIGHT, QUEST_SONG_PRELUDE },
|
|
{ RG_ZELDAS_LULLABY, QUEST_SONG_LULLABY },
|
|
{ RG_EPONAS_SONG, QUEST_SONG_EPONA },
|
|
{ RG_SARIAS_SONG, QUEST_SONG_SARIA },
|
|
{ RG_SUNS_SONG, QUEST_SONG_SUN },
|
|
{ RG_SONG_OF_TIME, QUEST_SONG_TIME },
|
|
{ RG_SONG_OF_STORMS, QUEST_SONG_STORMS },
|
|
{ RG_KOKIRI_EMERALD, QUEST_KOKIRI_EMERALD },
|
|
{ RG_GORON_RUBY, QUEST_GORON_RUBY },
|
|
{ RG_ZORA_SAPPHIRE, QUEST_ZORA_SAPPHIRE },
|
|
{ RG_STONE_OF_AGONY, QUEST_STONE_OF_AGONY },
|
|
{ RG_GERUDO_MEMBERSHIP_CARD, QUEST_GERUDO_CARD },
|
|
};
|
|
|
|
std::map<uint32_t, uint32_t> BottleRandomizerGetToItemID = {
|
|
{ RG_BOTTLE_WITH_RED_POTION, ITEM_POTION_RED },
|
|
{ RG_BOTTLE_WITH_GREEN_POTION, ITEM_POTION_GREEN },
|
|
{ RG_BOTTLE_WITH_BLUE_POTION, ITEM_POTION_BLUE },
|
|
{ RG_BOTTLE_WITH_FAIRY, ITEM_FAIRY },
|
|
{ RG_BOTTLE_WITH_FISH, ITEM_FISH },
|
|
{ RG_BOTTLE_WITH_BLUE_FIRE, ITEM_BLUE_FIRE },
|
|
{ RG_BOTTLE_WITH_BUGS, ITEM_BUG },
|
|
{ RG_BOTTLE_WITH_POE, ITEM_POE },
|
|
{ RG_BOTTLE_WITH_BIG_POE, ITEM_BIG_POE },
|
|
};
|
|
|
|
uint32_t HookshotLookup[3] = { ITEM_NONE, ITEM_HOOKSHOT, ITEM_LONGSHOT };
|
|
uint32_t OcarinaLookup[3] = { ITEM_NONE, ITEM_OCARINA_FAIRY, ITEM_OCARINA_TIME };
|
|
|
|
void Logic::ApplyItemEffect(Item& item, bool state) {
|
|
auto randoGet = item.GetRandomizerGet();
|
|
if (item.GetGIEntry()->objectId == OBJECT_GI_STICK) {
|
|
SetInventory(ITEM_STICK, (!state ? ITEM_NONE : ITEM_STICK));
|
|
}
|
|
if (item.GetGIEntry()->objectId == OBJECT_GI_NUTS) {
|
|
SetInventory(ITEM_NUT, (!state ? ITEM_NONE : ITEM_NUT));
|
|
}
|
|
switch (item.GetItemType()) {
|
|
case ITEMTYPE_ITEM: {
|
|
switch (randoGet) {
|
|
case RG_STONE_OF_AGONY:
|
|
case RG_GERUDO_MEMBERSHIP_CARD:
|
|
SetQuestItem(RandoGetToQuestItem.at(randoGet), state);
|
|
break;
|
|
case RG_WEIRD_EGG:
|
|
SetRandoInf(RAND_INF_WEIRD_EGG, state);
|
|
break;
|
|
case RG_ZELDAS_LETTER:
|
|
SetRandoInf(RAND_INF_ZELDAS_LETTER, state);
|
|
break;
|
|
case RG_DOUBLE_DEFENSE:
|
|
mSaveContext->isDoubleDefenseAcquired = state;
|
|
break;
|
|
case RG_POCKET_EGG:
|
|
SetRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG, state);
|
|
break;
|
|
case RG_COJIRO:
|
|
case RG_ODD_MUSHROOM:
|
|
case RG_ODD_POTION:
|
|
case RG_POACHERS_SAW:
|
|
case RG_BROKEN_SWORD:
|
|
case RG_PRESCRIPTION:
|
|
case RG_EYEBALL_FROG:
|
|
case RG_EYEDROPS:
|
|
case RG_CLAIM_CHECK:
|
|
SetRandoInf(randoGet - RG_COJIRO + RAND_INF_ADULT_TRADES_HAS_COJIRO, state);
|
|
break;
|
|
case RG_CLIMB:
|
|
SetRandoInf(RAND_INF_CAN_CLIMB, state);
|
|
break;
|
|
case RG_CRAWL:
|
|
SetRandoInf(RAND_INF_CAN_CRAWL, state);
|
|
break;
|
|
case RG_OPEN_CHEST:
|
|
SetRandoInf(RAND_INF_CAN_OPEN_CHEST, state);
|
|
break;
|
|
case RG_PROGRESSIVE_HOOKSHOT: {
|
|
uint8_t i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (CurrentInventory(ITEM_HOOKSHOT) == HookshotLookup[i]) {
|
|
break;
|
|
}
|
|
}
|
|
auto newItem = i + (!state ? -1 : 1);
|
|
if (newItem < 0) {
|
|
newItem = 0;
|
|
} else if (newItem > 2) {
|
|
newItem = 2;
|
|
}
|
|
SetInventory(ITEM_HOOKSHOT, HookshotLookup[newItem]);
|
|
} break;
|
|
case RG_PROGRESSIVE_STRENGTH: {
|
|
auto currentLevel = CurrentUpgrade(UPG_STRENGTH);
|
|
if (!CheckRandoInf(RAND_INF_CAN_GRAB) && state) {
|
|
SetRandoInf(RAND_INF_CAN_GRAB, true);
|
|
} else if (currentLevel == 0 && !state) {
|
|
SetRandoInf(RAND_INF_CAN_GRAB, false);
|
|
} else {
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
SetUpgrade(UPG_STRENGTH, newLevel);
|
|
}
|
|
} break;
|
|
case RG_PROGRESSIVE_BOMB_BAG: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_BOMB_BAG_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_BOMB_BAG, true);
|
|
break;
|
|
}
|
|
auto currentLevel = CurrentUpgrade(UPG_BOMB_BAG);
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
if (currentLevel == 0 && state || currentLevel == 1 && !state) {
|
|
SetInventory(ITEM_BOMB, (!state ? ITEM_NONE : ITEM_BOMB));
|
|
}
|
|
SetUpgrade(UPG_BOMB_BAG, newLevel);
|
|
} break;
|
|
case RG_PROGRESSIVE_BOW: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_QUIVER_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_QUIVER, true);
|
|
break;
|
|
}
|
|
auto currentLevel = CurrentUpgrade(UPG_QUIVER);
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
if (currentLevel == 0 && state || currentLevel == 1 && !state) {
|
|
SetInventory(ITEM_BOW, (!state ? ITEM_NONE : ITEM_BOW));
|
|
}
|
|
SetUpgrade(UPG_QUIVER, newLevel);
|
|
} break;
|
|
case RG_PROGRESSIVE_SLINGSHOT: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_BULLET_BAG_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_BULLET_BAG, true);
|
|
break;
|
|
}
|
|
auto currentLevel = CurrentUpgrade(UPG_BULLET_BAG);
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
if (currentLevel == 0 && state || currentLevel == 1 && !state) {
|
|
SetInventory(ITEM_SLINGSHOT, (!state ? ITEM_NONE : ITEM_SLINGSHOT));
|
|
}
|
|
SetUpgrade(UPG_BULLET_BAG, newLevel);
|
|
} break;
|
|
case RG_PROGRESSIVE_WALLET: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_WALLET_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_MONEY, true);
|
|
break;
|
|
}
|
|
auto currentLevel = CurrentUpgrade(UPG_WALLET);
|
|
if (!CheckRandoInf(RAND_INF_HAS_WALLET) && state) {
|
|
SetRandoInf(RAND_INF_HAS_WALLET, true);
|
|
} else if (currentLevel == 0 && !state) {
|
|
SetRandoInf(RAND_INF_HAS_WALLET, false);
|
|
} else {
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
SetUpgrade(UPG_WALLET, newLevel);
|
|
}
|
|
} break;
|
|
case RG_PROGRESSIVE_SCALE: {
|
|
auto currentLevel = CurrentUpgrade(UPG_SCALE);
|
|
if (!CheckRandoInf(RAND_INF_CAN_SWIM) && state) {
|
|
SetRandoInf(RAND_INF_CAN_SWIM, true);
|
|
} else if (currentLevel == 0 && !state) {
|
|
SetRandoInf(RAND_INF_CAN_SWIM, false);
|
|
} else {
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
SetUpgrade(UPG_SCALE, newLevel);
|
|
}
|
|
} break;
|
|
case RG_PROGRESSIVE_NUT_UPGRADE: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_NUT_UPGRADE_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_NUT_UPGRADE, true);
|
|
break;
|
|
}
|
|
auto currentLevel = CurrentUpgrade(UPG_NUTS);
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
if (currentLevel == 0 && state || currentLevel == 1 && !state) {
|
|
SetInventory(ITEM_NUT, (!state ? ITEM_NONE : ITEM_NUT));
|
|
}
|
|
SetUpgrade(UPG_NUTS, newLevel);
|
|
} break;
|
|
case RG_PROGRESSIVE_STICK_UPGRADE: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_STICK_UPGRADE_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_STICK_UPGRADE, true);
|
|
break;
|
|
}
|
|
auto currentLevel = CurrentUpgrade(UPG_STICKS);
|
|
auto newLevel = currentLevel + (!state ? -1 : 1);
|
|
if (currentLevel == 0 && state || currentLevel == 1 && !state) {
|
|
SetInventory(ITEM_STICK, (!state ? ITEM_NONE : ITEM_STICK));
|
|
}
|
|
SetUpgrade(UPG_STICKS, newLevel);
|
|
} break;
|
|
case RG_PROGRESSIVE_BOMBCHU_BAG: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_BOMBCHU_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_BOMBCHUS, true);
|
|
break;
|
|
}
|
|
SetInventory(ITEM_BOMBCHU, (!state ? ITEM_NONE : ITEM_BOMBCHU));
|
|
} break;
|
|
case RG_PROGRESSIVE_MAGIC_METER: {
|
|
auto realGI = item.GetGIEntry();
|
|
if (realGI->itemId == RG_MAGIC_INF && realGI->modIndex == MOD_RANDOMIZER) {
|
|
SetRandoInf(RAND_INF_HAS_INFINITE_MAGIC_METER, true);
|
|
break;
|
|
}
|
|
mSaveContext->magicLevel += (!state ? -1 : 1);
|
|
} break;
|
|
case RG_PROGRESSIVE_OCARINA: {
|
|
uint8_t i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (CurrentInventory(ITEM_OCARINA_FAIRY) == OcarinaLookup[i]) {
|
|
break;
|
|
}
|
|
}
|
|
i += (!state ? -1 : 1);
|
|
if (i < 0) {
|
|
i = 0;
|
|
} else if (i > 2) {
|
|
i = 2;
|
|
}
|
|
SetInventory(ITEM_OCARINA_FAIRY, OcarinaLookup[i]);
|
|
} break;
|
|
case RG_HEART_CONTAINER:
|
|
mSaveContext->healthCapacity += (!state ? -16 : 16);
|
|
break;
|
|
case RG_PIECE_OF_HEART:
|
|
mSaveContext->healthCapacity += (!state ? -4 : 4);
|
|
break;
|
|
case RG_BOOMERANG:
|
|
case RG_LENS_OF_TRUTH:
|
|
case RG_MEGATON_HAMMER:
|
|
case RG_DINS_FIRE:
|
|
case RG_FARORES_WIND:
|
|
case RG_NAYRUS_LOVE:
|
|
case RG_FIRE_ARROWS:
|
|
case RG_ICE_ARROWS:
|
|
case RG_LIGHT_ARROWS:
|
|
SetInventory(item.GetGIEntry()->itemId, (!state ? ITEM_NONE : item.GetGIEntry()->itemId));
|
|
break;
|
|
case RG_MAGIC_BEAN:
|
|
case RG_MAGIC_BEAN_PACK: {
|
|
auto change = (item.GetRandomizerGet() == RG_MAGIC_BEAN ? 1 : 10);
|
|
auto current = GetAmmo(ITEM_BEAN);
|
|
SetAmmo(ITEM_BEAN, current + (!state ? -change : change));
|
|
} break;
|
|
case RG_EMPTY_BOTTLE:
|
|
case RG_BOTTLE_WITH_MILK:
|
|
case RG_BOTTLE_WITH_RED_POTION:
|
|
case RG_BOTTLE_WITH_GREEN_POTION:
|
|
case RG_BOTTLE_WITH_BLUE_POTION:
|
|
case RG_BOTTLE_WITH_FAIRY:
|
|
case RG_BOTTLE_WITH_FISH:
|
|
case RG_BOTTLE_WITH_BLUE_FIRE:
|
|
case RG_BOTTLE_WITH_BUGS:
|
|
case RG_BOTTLE_WITH_POE:
|
|
case RG_BOTTLE_WITH_BIG_POE: {
|
|
uint8_t slot = SLOT_BOTTLE_1;
|
|
while (slot != SLOT_BOTTLE_4) {
|
|
if (mSaveContext->inventory.items[slot] == ITEM_NONE) {
|
|
break;
|
|
}
|
|
slot++;
|
|
}
|
|
uint16_t itemId = item.GetGIEntry()->itemId;
|
|
if (BottleRandomizerGetToItemID.contains(randoGet)) {
|
|
itemId = BottleRandomizerGetToItemID[randoGet];
|
|
}
|
|
if (randoGet == RG_BOTTLE_WITH_BIG_POE) {
|
|
BigPoes++;
|
|
}
|
|
mSaveContext->inventory.items[slot] = static_cast<uint8_t>(itemId);
|
|
} break;
|
|
case RG_RUTOS_LETTER:
|
|
SetRandoInf(RAND_INF_OBTAINED_RUTOS_LETTER, state);
|
|
break;
|
|
case RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL:
|
|
case RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL:
|
|
case RG_DESERT_COLOSSUS_BEAN_SOUL:
|
|
case RG_GERUDO_VALLEY_BEAN_SOUL:
|
|
case RG_GRAVEYARD_BEAN_SOUL:
|
|
case RG_KOKIRI_FOREST_BEAN_SOUL:
|
|
case RG_LAKE_HYLIA_BEAN_SOUL:
|
|
case RG_LOST_WOODS_BRIDGE_BEAN_SOUL:
|
|
case RG_LOST_WOODS_BEAN_SOUL:
|
|
case RG_ZORAS_RIVER_BEAN_SOUL:
|
|
case RG_GOHMA_SOUL:
|
|
case RG_KING_DODONGO_SOUL:
|
|
case RG_BARINADE_SOUL:
|
|
case RG_PHANTOM_GANON_SOUL:
|
|
case RG_VOLVAGIA_SOUL:
|
|
case RG_MORPHA_SOUL:
|
|
case RG_BONGO_BONGO_SOUL:
|
|
case RG_TWINROVA_SOUL:
|
|
case RG_GANON_SOUL:
|
|
case RG_OCARINA_A_BUTTON:
|
|
case RG_OCARINA_C_UP_BUTTON:
|
|
case RG_OCARINA_C_DOWN_BUTTON:
|
|
case RG_OCARINA_C_LEFT_BUTTON:
|
|
case RG_OCARINA_C_RIGHT_BUTTON:
|
|
case RG_KEATON_MASK:
|
|
case RG_SKULL_MASK:
|
|
case RG_SPOOKY_MASK:
|
|
case RG_BUNNY_HOOD:
|
|
case RG_GORON_MASK:
|
|
case RG_ZORA_MASK:
|
|
case RG_GERUDO_MASK:
|
|
case RG_MASK_OF_TRUTH:
|
|
case RG_GREG_RUPEE:
|
|
case RG_SPEAK_DEKU:
|
|
case RG_SPEAK_GERUDO:
|
|
case RG_SPEAK_GORON:
|
|
case RG_SPEAK_HYLIAN:
|
|
case RG_SPEAK_KOKIRI:
|
|
case RG_SPEAK_ZORA:
|
|
case RG_FISHING_POLE:
|
|
case RG_GUARD_HOUSE_KEY:
|
|
case RG_MARKET_BAZAAR_KEY:
|
|
case RG_MARKET_POTION_SHOP_KEY:
|
|
case RG_MASK_SHOP_KEY:
|
|
case RG_MARKET_SHOOTING_GALLERY_KEY:
|
|
case RG_BOMBCHU_BOWLING_KEY:
|
|
case RG_TREASURE_CHEST_GAME_BUILDING_KEY:
|
|
case RG_BOMBCHU_SHOP_KEY:
|
|
case RG_RICHARDS_HOUSE_KEY:
|
|
case RG_ALLEY_HOUSE_KEY:
|
|
case RG_KAK_BAZAAR_KEY:
|
|
case RG_KAK_POTION_SHOP_KEY:
|
|
case RG_BOSS_HOUSE_KEY:
|
|
case RG_GRANNYS_POTION_SHOP_KEY:
|
|
case RG_SKULLTULA_HOUSE_KEY:
|
|
case RG_IMPAS_HOUSE_KEY:
|
|
case RG_WINDMILL_KEY:
|
|
case RG_KAK_SHOOTING_GALLERY_KEY:
|
|
case RG_DAMPES_HUT_KEY:
|
|
case RG_TALONS_HOUSE_KEY:
|
|
case RG_STABLES_KEY:
|
|
case RG_BACK_TOWER_KEY:
|
|
case RG_HYLIA_LAB_KEY:
|
|
case RG_FISHING_HOLE_KEY:
|
|
SetRandoInf(RandoGetToRandInf.at(randoGet), state);
|
|
break;
|
|
case RG_TRIFORCE_PIECE:
|
|
mSaveContext->ship.quest.data.randomizer.triforcePiecesCollected += (!state ? -1 : 1);
|
|
break;
|
|
case RG_BOMBCHU_5:
|
|
case RG_BOMBCHU_10:
|
|
case RG_BOMBCHU_20:
|
|
SetInventory(ITEM_BOMBCHU, (!state ? ITEM_NONE : ITEM_BOMBCHU));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} break;
|
|
case ITEMTYPE_EQUIP: {
|
|
RandomizerGet itemRG = item.GetRandomizerGet();
|
|
if (itemRG == RG_DEKU_SHIELD || itemRG == RG_HYLIAN_SHIELD) {
|
|
return;
|
|
}
|
|
uint32_t equipId = RandoGetToEquipFlag.find(itemRG)->second;
|
|
if (!state) {
|
|
mSaveContext->inventory.equipment &= ~equipId;
|
|
if (equipId == EQUIP_FLAG_SWORD_BGS && itemRG != RG_GIANTS_KNIFE) {
|
|
mSaveContext->bgsFlag = false;
|
|
}
|
|
} else {
|
|
mSaveContext->inventory.equipment |= equipId;
|
|
if (equipId == EQUIP_FLAG_SWORD_BGS && itemRG != RG_GIANTS_KNIFE) {
|
|
mSaveContext->bgsFlag = true;
|
|
}
|
|
}
|
|
} break;
|
|
case ITEMTYPE_DUNGEONREWARD:
|
|
case ITEMTYPE_SONG:
|
|
SetQuestItem(RandoGetToQuestItem.find(item.GetRandomizerGet())->second, state);
|
|
break;
|
|
case ITEMTYPE_MAP:
|
|
SetDungeonItem(DUNGEON_MAP, RandoGetToDungeonScene.find(item.GetRandomizerGet())->second, state);
|
|
break;
|
|
case ITEMTYPE_COMPASS:
|
|
SetDungeonItem(DUNGEON_COMPASS, RandoGetToDungeonScene.find(item.GetRandomizerGet())->second, state);
|
|
break;
|
|
case ITEMTYPE_BOSSKEY:
|
|
SetDungeonItem(DUNGEON_KEY_BOSS, RandoGetToDungeonScene.find(item.GetRandomizerGet())->second, state);
|
|
break;
|
|
case ITEMTYPE_FORTRESS_SMALLKEY:
|
|
case ITEMTYPE_SMALLKEY: {
|
|
auto randoGet = item.GetRandomizerGet();
|
|
auto keyring = randoGet >= RG_FOREST_TEMPLE_KEY_RING && randoGet <= RG_GANONS_CASTLE_KEY_RING;
|
|
auto dungeonIndex = RandoGetToDungeonScene.find(randoGet)->second;
|
|
auto count = GetSmallKeyCount(dungeonIndex);
|
|
if (!state) {
|
|
if (keyring) {
|
|
count = 0;
|
|
} else {
|
|
count -= 1;
|
|
}
|
|
} else {
|
|
if (keyring) {
|
|
count = 10;
|
|
} else {
|
|
count += 1;
|
|
}
|
|
}
|
|
SetSmallKeyCount(dungeonIndex, count);
|
|
} break;
|
|
case ITEMTYPE_TOKEN:
|
|
mSaveContext->inventory.gsTokens += (!state ? -1 : 1);
|
|
break;
|
|
case ITEMTYPE_EVENT:
|
|
break;
|
|
case ITEMTYPE_DROP:
|
|
case ITEMTYPE_REFILL:
|
|
case ITEMTYPE_SHOP: {
|
|
RandomizerGet itemRG = item.GetRandomizerGet();
|
|
if (itemRG == RG_BUY_HYLIAN_SHIELD || itemRG == RG_BUY_DEKU_SHIELD || itemRG == RG_BUY_GORON_TUNIC ||
|
|
itemRG == RG_BUY_ZORA_TUNIC) {
|
|
uint32_t equipId = RandoGetToEquipFlag.find(itemRG)->second;
|
|
if (!state) {
|
|
mSaveContext->inventory.equipment &= ~equipId;
|
|
} else {
|
|
mSaveContext->inventory.equipment |= equipId;
|
|
}
|
|
}
|
|
switch (itemRG) {
|
|
case RG_DEKU_NUTS_5:
|
|
case RG_DEKU_NUTS_10:
|
|
case RG_BUY_DEKU_NUTS_5:
|
|
case RG_BUY_DEKU_NUTS_10:
|
|
SetInventory(ITEM_NUT, (!state ? ITEM_NONE : ITEM_NUT));
|
|
break;
|
|
case RG_DEKU_STICK_1:
|
|
case RG_BUY_DEKU_STICK_1:
|
|
case RG_STICKS:
|
|
SetInventory(ITEM_STICK, (!state ? ITEM_NONE : ITEM_STICK));
|
|
break;
|
|
case RG_BOMBCHU_5:
|
|
case RG_BOMBCHU_10:
|
|
case RG_BOMBCHU_20:
|
|
SetInventory(ITEM_BOMBCHU, (!state ? ITEM_NONE : ITEM_BOMBCHU));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
SaveContext* Logic::GetSaveContext() {
|
|
if (mSaveContext == nullptr) {
|
|
NewSaveContext();
|
|
}
|
|
return mSaveContext;
|
|
}
|
|
|
|
void Logic::SetSaveContext(SaveContext* context) {
|
|
mSaveContext = context;
|
|
}
|
|
|
|
void Logic::InitSaveContext() {
|
|
mSaveContext->totalDays = 0;
|
|
mSaveContext->bgsDayCount = 0;
|
|
|
|
mSaveContext->deaths = 0;
|
|
for (int i = 0; i < ARRAY_COUNT(mSaveContext->playerName); i++) {
|
|
mSaveContext->playerName[i] = 0x3E;
|
|
}
|
|
mSaveContext->n64ddFlag = 0;
|
|
mSaveContext->healthCapacity = 0x30;
|
|
mSaveContext->health = 0x30;
|
|
mSaveContext->magicLevel = 0;
|
|
mSaveContext->magic = 0x30;
|
|
mSaveContext->rupees = 0;
|
|
mSaveContext->swordHealth = 0;
|
|
mSaveContext->naviTimer = 0;
|
|
mSaveContext->isMagicAcquired = 0;
|
|
mSaveContext->isDoubleMagicAcquired = 0;
|
|
mSaveContext->isDoubleDefenseAcquired = 0;
|
|
mSaveContext->bgsFlag = 0;
|
|
mSaveContext->ocarinaGameRoundNum = 0;
|
|
for (int button = 0; button < ARRAY_COUNT(mSaveContext->childEquips.buttonItems); button++) {
|
|
mSaveContext->childEquips.buttonItems[button] = ITEM_NONE;
|
|
}
|
|
for (int button = 0; button < ARRAY_COUNT(mSaveContext->childEquips.cButtonSlots); button++) {
|
|
mSaveContext->childEquips.cButtonSlots[button] = SLOT_NONE;
|
|
}
|
|
mSaveContext->childEquips.equipment = 0;
|
|
for (int button = 0; button < ARRAY_COUNT(mSaveContext->adultEquips.buttonItems); button++) {
|
|
mSaveContext->adultEquips.buttonItems[button] = ITEM_NONE;
|
|
}
|
|
for (int button = 0; button < ARRAY_COUNT(mSaveContext->adultEquips.cButtonSlots); button++) {
|
|
mSaveContext->adultEquips.cButtonSlots[button] = SLOT_NONE;
|
|
}
|
|
mSaveContext->adultEquips.equipment = 0;
|
|
mSaveContext->unk_54 = 0;
|
|
mSaveContext->savedSceneNum = SCENE_LINKS_HOUSE;
|
|
|
|
// Equipment
|
|
for (int button = 0; button < ARRAY_COUNT(mSaveContext->equips.buttonItems); button++) {
|
|
mSaveContext->equips.buttonItems[button] = ITEM_NONE;
|
|
}
|
|
for (int button = 0; button < ARRAY_COUNT(mSaveContext->equips.cButtonSlots); button++) {
|
|
mSaveContext->equips.cButtonSlots[button] = SLOT_NONE;
|
|
}
|
|
mSaveContext->equips.equipment = 0;
|
|
|
|
// Inventory
|
|
for (int item = 0; item < ARRAY_COUNT(mSaveContext->inventory.items); item++) {
|
|
mSaveContext->inventory.items[item] = ITEM_NONE;
|
|
}
|
|
for (int ammo = 0; ammo < ARRAY_COUNT(mSaveContext->inventory.ammo); ammo++) {
|
|
mSaveContext->inventory.ammo[ammo] = 0;
|
|
}
|
|
mSaveContext->inventory.equipment = 0;
|
|
mSaveContext->inventory.upgrades = 0;
|
|
mSaveContext->inventory.questItems = 0;
|
|
for (int dungeon = 0; dungeon < ARRAY_COUNT(mSaveContext->inventory.dungeonItems); dungeon++) {
|
|
mSaveContext->inventory.dungeonItems[dungeon] = 0;
|
|
}
|
|
for (int dungeon = 0; dungeon < ARRAY_COUNT(mSaveContext->inventory.dungeonKeys); dungeon++) {
|
|
mSaveContext->inventory.dungeonKeys[dungeon] = 0x0;
|
|
}
|
|
mSaveContext->inventory.defenseHearts = 0;
|
|
mSaveContext->inventory.gsTokens = 0;
|
|
for (int scene = 0; scene < ARRAY_COUNT(mSaveContext->sceneFlags); scene++) {
|
|
mSaveContext->sceneFlags[scene].chest = 0;
|
|
mSaveContext->sceneFlags[scene].swch = 0;
|
|
mSaveContext->sceneFlags[scene].clear = 0;
|
|
mSaveContext->sceneFlags[scene].collect = 0;
|
|
mSaveContext->sceneFlags[scene].unk = 0;
|
|
mSaveContext->sceneFlags[scene].rooms = 0;
|
|
mSaveContext->sceneFlags[scene].floors = 0;
|
|
}
|
|
mSaveContext->fw.pos.x = 0;
|
|
mSaveContext->fw.pos.y = 0;
|
|
mSaveContext->fw.pos.z = 0;
|
|
mSaveContext->fw.yaw = 0;
|
|
mSaveContext->fw.playerParams = 0;
|
|
mSaveContext->fw.entranceIndex = 0;
|
|
mSaveContext->fw.roomIndex = 0;
|
|
mSaveContext->fw.set = 0;
|
|
mSaveContext->fw.tempSwchFlags = 0;
|
|
mSaveContext->fw.tempCollectFlags = 0;
|
|
for (int flag = 0; flag < ARRAY_COUNT(mSaveContext->gsFlags); flag++) {
|
|
mSaveContext->gsFlags[flag] = 0;
|
|
}
|
|
for (int highscore = 0; highscore < ARRAY_COUNT(mSaveContext->highScores); highscore++) {
|
|
mSaveContext->highScores[highscore] = 0;
|
|
}
|
|
for (int flag = 0; flag < ARRAY_COUNT(mSaveContext->eventChkInf); flag++) {
|
|
mSaveContext->eventChkInf[flag] = 0;
|
|
}
|
|
for (int flag = 0; flag < ARRAY_COUNT(mSaveContext->itemGetInf); flag++) {
|
|
mSaveContext->itemGetInf[flag] = 0;
|
|
}
|
|
for (int flag = 0; flag < ARRAY_COUNT(mSaveContext->infTable); flag++) {
|
|
mSaveContext->infTable[flag] = 0;
|
|
}
|
|
mSaveContext->worldMapAreaData = 0;
|
|
mSaveContext->scarecrowLongSongSet = 0;
|
|
for (int i = 0; i < ARRAY_COUNT(mSaveContext->scarecrowLongSong); i++) {
|
|
mSaveContext->scarecrowLongSong[i].noteIdx = 0;
|
|
mSaveContext->scarecrowLongSong[i].unk_01 = 0;
|
|
mSaveContext->scarecrowLongSong[i].unk_02 = 0;
|
|
mSaveContext->scarecrowLongSong[i].volume = 0;
|
|
mSaveContext->scarecrowLongSong[i].vibrato = 0;
|
|
mSaveContext->scarecrowLongSong[i].tone = 0;
|
|
mSaveContext->scarecrowLongSong[i].semitone = 0;
|
|
}
|
|
mSaveContext->scarecrowSpawnSongSet = 0;
|
|
for (int i = 0; i < ARRAY_COUNT(mSaveContext->scarecrowSpawnSong); i++) {
|
|
mSaveContext->scarecrowSpawnSong[i].noteIdx = 0;
|
|
mSaveContext->scarecrowSpawnSong[i].unk_01 = 0;
|
|
mSaveContext->scarecrowSpawnSong[i].unk_02 = 0;
|
|
mSaveContext->scarecrowSpawnSong[i].volume = 0;
|
|
mSaveContext->scarecrowSpawnSong[i].vibrato = 0;
|
|
mSaveContext->scarecrowSpawnSong[i].tone = 0;
|
|
mSaveContext->scarecrowSpawnSong[i].semitone = 0;
|
|
}
|
|
|
|
mSaveContext->horseData.scene = SCENE_HYRULE_FIELD;
|
|
mSaveContext->horseData.pos.x = -1840;
|
|
mSaveContext->horseData.pos.y = 72;
|
|
mSaveContext->horseData.pos.z = 5497;
|
|
mSaveContext->horseData.angle = -0x6AD9;
|
|
mSaveContext->magicLevel = 0;
|
|
mSaveContext->infTable[29] = 1;
|
|
mSaveContext->sceneFlags[5].swch = 0x40000000;
|
|
|
|
// SoH specific
|
|
mSaveContext->ship.backupFW = mSaveContext->fw;
|
|
mSaveContext->ship.pendingSale = ITEM_NONE;
|
|
mSaveContext->ship.pendingSaleMod = MOD_NONE;
|
|
mSaveContext->ship.pendingIceTrapCount = 0;
|
|
|
|
// Init with normal quest unless only an MQ rom is provided
|
|
mSaveContext->ship.quest.id = OTRGlobals::Instance->HasOriginal() ? QUEST_NORMAL : QUEST_MASTER;
|
|
|
|
// RANDOTODO (ADD ITEMLOCATIONS TO GSAVECONTEXT)
|
|
}
|
|
|
|
void Logic::NewSaveContext() {
|
|
if (mSaveContext != nullptr && mSaveContext != &gSaveContext) {
|
|
free(mSaveContext);
|
|
}
|
|
mSaveContext = new SaveContext();
|
|
InitSaveContext();
|
|
}
|
|
|
|
uint8_t Logic::InventorySlot(uint32_t item) {
|
|
return gItemSlots[item];
|
|
}
|
|
|
|
uint32_t Logic::CurrentUpgrade(uint32_t upgrade) {
|
|
return (mSaveContext->inventory.upgrades & gUpgradeMasks[upgrade]) >> gUpgradeShifts[upgrade];
|
|
}
|
|
|
|
uint32_t Logic::CurrentInventory(uint32_t item) {
|
|
return mSaveContext->inventory.items[InventorySlot(item)];
|
|
}
|
|
|
|
void Logic::SetUpgrade(uint32_t upgrade, uint8_t level) {
|
|
mSaveContext->inventory.upgrades &= gUpgradeNegMasks[upgrade];
|
|
mSaveContext->inventory.upgrades |= level << gUpgradeShifts[upgrade];
|
|
}
|
|
|
|
bool Logic::CheckInventory(uint32_t item, bool exact) {
|
|
auto current = mSaveContext->inventory.items[InventorySlot(item)];
|
|
return exact ? (current == item) : (current != ITEM_NONE);
|
|
}
|
|
|
|
void Logic::SetInventory(uint32_t itemSlot, uint32_t item) {
|
|
mSaveContext->inventory.items[InventorySlot(itemSlot)] = item;
|
|
}
|
|
|
|
bool Logic::CheckEquipment(uint32_t equipFlag) {
|
|
return (equipFlag & mSaveContext->inventory.equipment);
|
|
}
|
|
|
|
bool Logic::CheckQuestItem(uint32_t item) {
|
|
return ((1 << item) & mSaveContext->inventory.questItems);
|
|
}
|
|
|
|
void Logic::SetQuestItem(uint32_t item, bool state) {
|
|
if (!state) {
|
|
mSaveContext->inventory.questItems &= ~(1 << item);
|
|
} else {
|
|
mSaveContext->inventory.questItems |= (1 << item);
|
|
}
|
|
}
|
|
|
|
const std::vector<uint8_t>& GetThievesHideoutSmallKeyDoors() {
|
|
// Retrieved from scenes/shared/gerudoway_scene/gerudoway_room_%d
|
|
// SOH::SceneCommandID::SetActorList, actor.id == ACTOR_DOOR_GERUDO, actor.params & 0x3F
|
|
static const std::vector<uint8_t> normalSmallKeyDoors{ 1, 2, 3, 4 };
|
|
static const std::vector<uint8_t> fastSmallKeyDoors{ 1 };
|
|
static const std::vector<uint8_t> freeSmallKeyDoors{};
|
|
|
|
if (RAND_GET_OPTION(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_NORMAL)) {
|
|
return normalSmallKeyDoors;
|
|
} else if (RAND_GET_OPTION(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FAST)) {
|
|
return fastSmallKeyDoors;
|
|
}
|
|
return freeSmallKeyDoors;
|
|
}
|
|
|
|
// Get the swch bit positions for the dungeon
|
|
const std::vector<uint8_t>& GetDungeonSmallKeyDoors(SceneID sceneId) {
|
|
static const std::vector<uint8_t> emptyVector;
|
|
|
|
auto dungeonInfo = Rando::Context::GetInstance()->GetDungeons()->GetDungeonFromScene(sceneId);
|
|
if (dungeonInfo == nullptr) {
|
|
return emptyVector;
|
|
}
|
|
|
|
bool masterQuest = dungeonInfo->IsMQ();
|
|
|
|
// Create a unique key for the dungeon and master quest
|
|
uint8_t key = sceneId | (masterQuest << 7);
|
|
|
|
static std::unordered_map<uint8_t, std::vector<uint8_t>> dungeonSmallKeyDoors;
|
|
auto foundEntry = dungeonSmallKeyDoors.find(key);
|
|
if (foundEntry != dungeonSmallKeyDoors.end()) {
|
|
return foundEntry->second;
|
|
}
|
|
dungeonSmallKeyDoors[key] = {};
|
|
|
|
// Get the scene path
|
|
SceneTableEntry* sceneTableEntry = &gSceneTable[sceneId];
|
|
std::string scenePath =
|
|
StringHelper::Sprintf("scenes/%s/%s/%s", masterQuest ? "mq" : "nonmq", sceneTableEntry->sceneFile.fileName,
|
|
sceneTableEntry->sceneFile.fileName);
|
|
|
|
// Load the scene
|
|
std::shared_ptr<SOH::Scene> scene = std::dynamic_pointer_cast<SOH::Scene>(
|
|
Ship::Context::GetInstance()->GetResourceManager()->LoadResource(scenePath));
|
|
if (scene == nullptr) {
|
|
return emptyVector;
|
|
}
|
|
|
|
// Find the SetTransitionActorList command
|
|
std::shared_ptr<SOH::SetTransitionActorList> transitionActorListCommand = nullptr;
|
|
for (auto& command : scene->commands) {
|
|
if (command->cmdId == SOH::SceneCommandID::SetTransitionActorList) {
|
|
transitionActorListCommand = std::dynamic_pointer_cast<SOH::SetTransitionActorList>(command);
|
|
break;
|
|
}
|
|
}
|
|
if (transitionActorListCommand == nullptr) {
|
|
return emptyVector;
|
|
}
|
|
|
|
// Find the bit position for the small key doors
|
|
for (auto& transitionActor : transitionActorListCommand->transitionActorList) {
|
|
if (transitionActor.id == ACTOR_EN_DOOR) {
|
|
uint8_t doorType = (transitionActor.params >> 7) & 7;
|
|
if (doorType == DOOR_LOCKED) {
|
|
dungeonSmallKeyDoors[key].emplace_back(transitionActor.params & 0x3F);
|
|
}
|
|
} else if (transitionActor.id == ACTOR_DOOR_SHUTTER) {
|
|
uint8_t doorType = (transitionActor.params >> 6) & 15;
|
|
if (doorType == SHUTTER_KEY_LOCKED) {
|
|
dungeonSmallKeyDoors[key].emplace_back(transitionActor.params & 0x3F);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dungeonSmallKeyDoors[key];
|
|
}
|
|
|
|
int8_t Logic::GetUsedSmallKeyCount(SceneID sceneId) {
|
|
const auto& smallKeyDoors =
|
|
(sceneId == SCENE_THIEVES_HIDEOUT) ? GetThievesHideoutSmallKeyDoors() : GetDungeonSmallKeyDoors(sceneId);
|
|
|
|
// Get the swch value for the scene
|
|
uint32_t swch;
|
|
if (gPlayState != nullptr && gPlayState->sceneNum == sceneId) {
|
|
swch = gPlayState->actorCtx.flags.swch;
|
|
} else {
|
|
swch = mSaveContext->sceneFlags[sceneId].swch;
|
|
}
|
|
|
|
// Count the number of small keys doors unlocked
|
|
int8_t unlockedSmallKeyDoors = 0;
|
|
for (auto& smallKeyDoor : smallKeyDoors) {
|
|
unlockedSmallKeyDoors += swch >> smallKeyDoor & 1;
|
|
}
|
|
|
|
// RANDOTODO: Account for MQ Water trick that causes the basement lock to unlock when the player clears the stalfos
|
|
// pit.
|
|
return unlockedSmallKeyDoors;
|
|
}
|
|
|
|
uint8_t Logic::GetSmallKeyCount(uint32_t dungeonIndex) {
|
|
int8_t dungeonKeys = mSaveContext->inventory.dungeonKeys[dungeonIndex];
|
|
if (dungeonKeys == -1) {
|
|
// never got keys, so can't have used keys
|
|
return 0;
|
|
}
|
|
return dungeonKeys + GetUsedSmallKeyCount(SceneID(dungeonIndex));
|
|
}
|
|
|
|
void Logic::SetSmallKeyCount(uint32_t dungeonIndex, uint8_t count) {
|
|
mSaveContext->inventory.dungeonKeys[dungeonIndex] = count;
|
|
}
|
|
|
|
bool Logic::CheckDungeonItem(uint32_t item, uint32_t dungeonIndex) {
|
|
return mSaveContext->inventory.dungeonItems[dungeonIndex] & gBitFlags[item];
|
|
}
|
|
|
|
void Logic::SetDungeonItem(uint32_t item, uint32_t dungeonIndex, bool state) {
|
|
if (!state) {
|
|
mSaveContext->inventory.dungeonItems[dungeonIndex] &= ~gBitFlags[item];
|
|
} else {
|
|
mSaveContext->inventory.dungeonItems[dungeonIndex] |= gBitFlags[item];
|
|
}
|
|
}
|
|
|
|
bool Logic::CheckRandoInf(uint32_t flag) {
|
|
return mSaveContext->ship.randomizerInf[flag >> 4] & (1 << (flag & 0xF));
|
|
}
|
|
|
|
void Logic::SetRandoInf(uint32_t flag, bool state) {
|
|
if (!state) {
|
|
mSaveContext->ship.randomizerInf[flag >> 4] &= ~(1 << (flag & 0xF));
|
|
} else {
|
|
mSaveContext->ship.randomizerInf[flag >> 4] |= (1 << (flag & 0xF));
|
|
}
|
|
}
|
|
|
|
bool Logic::CheckEventChkInf(int32_t flag) {
|
|
return mSaveContext->eventChkInf[flag >> 4] & (1 << (flag & 0xF));
|
|
}
|
|
|
|
void Logic::SetEventChkInf(int32_t flag, bool state) {
|
|
if (!state) {
|
|
mSaveContext->eventChkInf[flag >> 4] &= ~(1 << (flag & 0xF));
|
|
} else {
|
|
mSaveContext->eventChkInf[flag >> 4] |= (1 << (flag & 0xF));
|
|
}
|
|
}
|
|
|
|
uint8_t Logic::GetGSCount() {
|
|
return static_cast<uint8_t>(mSaveContext->inventory.gsTokens);
|
|
}
|
|
|
|
uint8_t Logic::GetAmmo(uint32_t item) {
|
|
return mSaveContext->inventory.ammo[gItemSlots[item]];
|
|
}
|
|
|
|
void Logic::SetAmmo(uint32_t item, uint8_t count) {
|
|
mSaveContext->inventory.ammo[gItemSlots[item]] = count;
|
|
}
|
|
|
|
void Logic::SetContext(std::shared_ptr<Context> _ctx) {
|
|
ctx = _ctx;
|
|
}
|
|
|
|
bool Logic::Get(LogicVal logicVal) {
|
|
return inLogic[logicVal];
|
|
}
|
|
|
|
void Logic::Set(LogicVal logicVal, bool value) {
|
|
inLogic[logicVal] = value;
|
|
}
|
|
|
|
bool Logic::IsFireLoopLocked() {
|
|
return ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANYWHERE) ||
|
|
ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OVERWORLD) ||
|
|
ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON);
|
|
}
|
|
|
|
bool Logic::ReachScarecrow() {
|
|
return ScarecrowsSong() && CanUse(RG_HOOKSHOT);
|
|
}
|
|
|
|
bool Logic::ReachDistantScarecrow() {
|
|
return ScarecrowsSong() && CanUse(RG_LONGSHOT);
|
|
}
|
|
|
|
bool Logic::CanClimbLadder() {
|
|
return HasItem(RG_CLIMB) || (ctx->GetTrickOption(RT_HOOKSHOT_LADDERS) && CanUse(RG_HOOKSHOT));
|
|
}
|
|
|
|
bool Logic::CanClimbHighLadder() {
|
|
return HasItem(RG_CLIMB) || (ctx->GetTrickOption(RT_HOOKSHOT_LADDERS) && CanUse(RG_LONGSHOT));
|
|
}
|
|
|
|
bool Logic::SummonEpona() {
|
|
return IsAdult && Get(LOGIC_FREED_EPONA) && CanUse(RG_EPONAS_SONG);
|
|
}
|
|
|
|
bool Logic::IsReverseAccessPossible() {
|
|
// If we ever allow dungeon entrances to connect to boss rooms directly in dungeon chains, or for 1 boss door to
|
|
// lead to another dungeons boss door, add RSK_MIX_DUNGEON_ENTRANCES to the final condition
|
|
// RANDOTODO Check for Age-Locked Boss entrances + decoupled + Ganon's tower when it is shuffled
|
|
return !ctx->GetOption(RSK_SHUFFLE_BOSS_ENTRANCES).Is(RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) &&
|
|
((ctx->GetOption(RSK_DECOUPLED_ENTRANCES) &&
|
|
ctx->GetOption(RSK_SHUFFLE_BOSS_ENTRANCES).Is(RO_BOSS_ROOM_ENTRANCE_SHUFFLE_FULL)) ||
|
|
(ctx->GetOption(RSK_MIX_BOSS_ENTRANCES) &&
|
|
(ctx->GetOption(RSK_MIX_OVERWORLD_ENTRANCES) || ctx->GetOption(RSK_MIX_INTERIOR_ENTRANCES))));
|
|
}
|
|
|
|
bool Logic::DMCUpperToPots() {
|
|
return CanUse(RG_HOVER_BOOTS) || (IsAdult && ((Get(LOGIC_DMC_BOULDER)) ||
|
|
(ctx->GetTrickOption(RT_DMC_BOULDER_SKIP) /* && CanUse(RG_ROLL)*/)));
|
|
}
|
|
|
|
bool Logic::DMCPotsToPad() {
|
|
return (CanUse(RG_HOVER_BOOTS) || CanUse(RG_HOOKSHOT) ||
|
|
(IsAdult && CanShield() && ctx->GetTrickOption(RT_DMC_BOLERO_JUMP) && CanUse(RG_POWER_BRACELET)));
|
|
}
|
|
|
|
bool Logic::DMCPadToPots() {
|
|
return ((CanUse(RG_HOVER_BOOTS) && (IsAdult || (HasItem(RG_CLIMB) /*&& CanUse(RG_ROLL)*/))) || CanUse(RG_HOOKSHOT));
|
|
}
|
|
|
|
bool Logic::SpiritExplosiveKeyLogic() {
|
|
return SmallKeys(SCENE_SPIRIT_TEMPLE, HasExplosives() ? 1 : 2);
|
|
}
|
|
|
|
bool Logic::SpiritWestToSkull() {
|
|
return (IsAdult && ctx->GetTrickOption(RT_SPIRIT_STATUE_JUMP)) || CanUse(RG_HOVER_BOOTS) || ReachScarecrow();
|
|
}
|
|
|
|
bool Logic::SpiritSunBlockSouthLedge() {
|
|
// also possible to do a backwalk hover + backflip if you equip hovers as you start backwalk to accelerate faster
|
|
return HasItem(RG_POWER_BRACELET) || IsAdult || CanKillEnemy(RE_BEAMOS) /*|| BunnyHovers()*/ ||
|
|
(CanUse(RG_HOOKSHOT) &&
|
|
(HasFireSource() ||
|
|
(Get(LOGIC_SPIRIT_SUN_BLOCK_TORCH) &&
|
|
(CanUse(RG_STICKS) || (ctx->GetTrickOption(RT_SPIRIT_SUN_CHEST) && CanUse(RG_FAIRY_BOW))))));
|
|
}
|
|
|
|
bool Logic::SpiritEastToSwitch() {
|
|
return (IsAdult && ctx->GetTrickOption(RT_SPIRIT_STATUE_JUMP)) || CanUse(RG_HOVER_BOOTS) ||
|
|
(CanUse(RG_ZELDAS_LULLABY) && CanUse(RG_HOOKSHOT));
|
|
}
|
|
|
|
// Combines crossing the ledge directly and the jump from the hand
|
|
bool Logic::MQSpiritWestToPots() {
|
|
return (IsAdult && ctx->GetTrickOption(RT_SPIRIT_STATUE_JUMP)) || CanUse(RG_HOVER_BOOTS) || CanUse(RG_SONG_OF_TIME);
|
|
}
|
|
|
|
bool Logic::MQSpiritStatueToSunBlock() {
|
|
return (IsAdult || ctx->GetTrickOption(RT_SPIRIT_MQ_SUN_BLOCK_SOT) ||
|
|
CanUse(RG_SONG_OF_TIME) /* || CanBunnyJump()*/) &&
|
|
HasItem(RG_POWER_BRACELET);
|
|
}
|
|
|
|
bool Logic::MQSpiritStatueSouthDoor() {
|
|
return HasFireSource() || (ctx->GetTrickOption(RT_SPIRIT_MQ_FROZEN_EYE) && CanUse(RG_FAIRY_BOW) &&
|
|
CanUse(RG_SONG_OF_TIME) && (HasItem(RG_CLIMB) || CanUse(RG_HOOKSHOT)));
|
|
}
|
|
|
|
bool Logic::MQSpirit4KeyColossus() {
|
|
// !QUANTUM LOGIC!
|
|
// We only need 4 keys and the ability to reach both hands for adult to logically be able to drop down onto Desert
|
|
// Colossus This is because there are only 3 keys that can be wasted without opening up either this lock to East
|
|
// hand, or the West Hand lock through Sun Block Room and both directions allow you to drop onto colossus
|
|
// logic->CanKillEnemy(RE_FLOORMASTER) is implied
|
|
return CanAvoidEnemy(RE_BEAMOS, true, 4) && CanUse(RG_SONG_OF_TIME) && CanJumpslash() &&
|
|
(HasItem(RG_POWER_BRACELET) || SunlightArrows()) &&
|
|
(ctx->GetTrickOption(RT_LENS_SPIRIT_MQ) || CanUse(RG_LENS_OF_TRUTH)) && CanKillEnemy(RE_IRON_KNUCKLE) &&
|
|
CanUse(RG_HOOKSHOT);
|
|
}
|
|
|
|
bool Logic::MQSpirit4KeyWestHand() {
|
|
// !QUANTUM LOGIC!
|
|
// Continuing from MQSpirit4KeyColossus, if we also have a longshot, we can go from the East hand to the West hand,
|
|
// meaning we always have access to East Hand
|
|
return CanUse(RG_LONGSHOT) && MQSpirit4KeyColossus();
|
|
}
|
|
// This version of the function handles Shared Access for child, based on what adult could do if they existed
|
|
bool Logic::CouldMQSpirit4KeyWestHand() {
|
|
return CanAvoidEnemy(RE_BEAMOS, true, 4) && CanUse(RG_SONG_OF_TIME) &&
|
|
(HasItem(RG_MASTER_SWORD) || HasItem(RG_BIGGORON_SWORD) || HasItem(RG_MEGATON_HAMMER)) &&
|
|
(HasItem(RG_POWER_BRACELET) || SunlightArrows()) &&
|
|
(ctx->GetTrickOption(RT_LENS_SPIRIT_MQ) || CanUse(RG_LENS_OF_TRUTH)) && HasItem(RG_LONGSHOT);
|
|
}
|
|
|
|
// !QUANTUM LOGIC!
|
|
// With 3 keys, you cannot lock adult out of leaving spirit onto the hands and jumping down, as you would have to
|
|
// open the west hand door and then adult could climb through sun block room to jump down from there
|
|
// This requires that adult can complete both routes
|
|
// If we have the longshot, we can also guarantee access to the outer west hand as you can longshot from the east hand
|
|
// to the west Implies CanKillEnemy(RE_IRON_KNUCKLE)
|
|
bool Logic::OuterWestHandLogic() {
|
|
return HasExplosives() && (HasItem(RG_CLIMB) || CanUse(RG_LONGSHOT)) && HasItem(RG_POWER_BRACELET) &&
|
|
SmallKeys(SCENE_SPIRIT_TEMPLE, HasItem(RG_LONGSHOT) ? 3 : 5);
|
|
}
|
|
|
|
bool Logic::OuterWestHandMQLogic() {
|
|
return MQSpiritStatueToSunBlock() && SmallKeys(SCENE_SPIRIT_TEMPLE, CouldMQSpirit4KeyWestHand() ? 4 : 7);
|
|
}
|
|
|
|
bool Logic::StatueRoomMQKeyLogic() {
|
|
// !QUANTUM LOGIC!
|
|
// If child enters in reverse, then they have access to Certain Access to Broken Wall room in 6 keys,
|
|
// the ability to hit switches and the ability to climb because only child can reach the initial child lock
|
|
// without opening the Statue room to Broken Wall Room lock first
|
|
// if adult can ever cross crawlspaces this becomes more complicated.
|
|
return SmallKeys(SCENE_SPIRIT_TEMPLE, IsChild && Get(LOGIC_REVERSE_SPIRIT_CHILD) && CanHitSwitch() &&
|
|
(HasItem(RG_CLIMB) || CanUse(RG_LONGSHOT))
|
|
? 6
|
|
: 7);
|
|
}
|
|
|
|
void Logic::Reset(bool resetSaveContext /*= true*/) {
|
|
if (resetSaveContext) {
|
|
NewSaveContext();
|
|
}
|
|
StartPerformanceTimer(PT_LOGIC_RESET);
|
|
memset(inLogic, false, sizeof(inLogic));
|
|
|
|
if (resetSaveContext) {
|
|
// Ocarina C Buttons
|
|
bool ocBtnShuffle = ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS).Is(true);
|
|
SetRandoInf(RAND_INF_HAS_OCARINA_A, !ocBtnShuffle);
|
|
SetRandoInf(RAND_INF_HAS_OCARINA_C_UP, !ocBtnShuffle);
|
|
SetRandoInf(RAND_INF_HAS_OCARINA_C_DOWN, !ocBtnShuffle);
|
|
SetRandoInf(RAND_INF_HAS_OCARINA_C_LEFT, !ocBtnShuffle);
|
|
SetRandoInf(RAND_INF_HAS_OCARINA_C_RIGHT, !ocBtnShuffle);
|
|
|
|
// Progressive Items
|
|
SetUpgrade(UPG_STICKS, ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG).Is(true) ? 0 : 1);
|
|
SetUpgrade(UPG_NUTS, ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG).Is(true) ? 0 : 1);
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_SWIM).Is(false)) {
|
|
SetRandoInf(RAND_INF_CAN_SWIM, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_GRAB).Is(false)) {
|
|
SetRandoInf(RAND_INF_CAN_GRAB, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_CLIMB).Is(false)) {
|
|
SetRandoInf(RAND_INF_CAN_CLIMB, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_CRAWL).Is(false)) {
|
|
SetRandoInf(RAND_INF_CAN_CRAWL, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_OPEN_CHEST).Is(false)) {
|
|
SetRandoInf(RAND_INF_CAN_OPEN_CHEST, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_SPEAK).Is(false)) {
|
|
SetRandoInf(RAND_INF_CAN_SPEAK_DEKU, true);
|
|
SetRandoInf(RAND_INF_CAN_SPEAK_GERUDO, true);
|
|
SetRandoInf(RAND_INF_CAN_SPEAK_GORON, true);
|
|
SetRandoInf(RAND_INF_CAN_SPEAK_HYLIAN, true);
|
|
SetRandoInf(RAND_INF_CAN_SPEAK_KOKIRI, true);
|
|
SetRandoInf(RAND_INF_CAN_SPEAK_ZORA, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_CHILD_WALLET).Is(false)) {
|
|
SetRandoInf(RAND_INF_HAS_WALLET, true);
|
|
}
|
|
|
|
// If we're not shuffling fishing pole, we start with it
|
|
if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE).Is(false)) {
|
|
SetRandoInf(RAND_INF_FISHING_POLE_FOUND, true);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_SHUFFLE_BEAN_SOULS).Is(false)) {
|
|
SetRandoInf(RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_DESERT_COLOSSUS_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_GERUDO_VALLEY_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_GRAVEYARD_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_KOKIRI_FOREST_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_LAKE_HYLIA_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_LOST_WOODS_BRIDGE_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_LOST_WOODS_BEAN_SOUL, true);
|
|
SetRandoInf(RAND_INF_ZORAS_RIVER_BEAN_SOUL, true);
|
|
}
|
|
|
|
// If not keysanity, start with 1 logical key to account for automatically unlocking the basement door in
|
|
// vanilla FiT
|
|
if (!IsFireLoopLocked() && ctx->GetDungeon(Rando::FIRE_TEMPLE)->IsVanilla()) {
|
|
SetSmallKeyCount(SCENE_FIRE_TEMPLE, 1);
|
|
}
|
|
}
|
|
|
|
Bottles = 0;
|
|
NumBottles = 0;
|
|
PieceOfHeart = 0;
|
|
HeartContainer = 0;
|
|
|
|
IsChild = false;
|
|
IsAdult = false;
|
|
BigPoes = 0;
|
|
|
|
BaseHearts = ctx->GetOption(RSK_STARTING_HEARTS).Get() + 1;
|
|
|
|
AtDay = false;
|
|
AtNight = false;
|
|
if (resetSaveContext) {
|
|
GetSaveContext()->linkAge = !ctx->GetOption(RSK_SELECTED_STARTING_AGE).Get();
|
|
}
|
|
|
|
CalculatingAvailableChecks = false;
|
|
|
|
StopPerformanceTimer(PT_LOGIC_RESET);
|
|
}
|
|
} // namespace Rando
|