#include "tools.h" #include "d/actor/d_a_alink.h" #include "d/d_com_inf_game.h" #include "d/d_item.h" #include "d/d_item_data.h" #include "dusk/logging.h" #include "f_op/f_op_actor_mng.h" #include "stages.h" #include "verify_item_functions.h" bool playerIsInRoomStage(s32 room, const char* stage) { // Only check room if it is valid // Room numbers are normally stored as int8_t, so the highest positive value is 127 if ((room < 0) || (room > 127)) { return false; } if (room != dStage_roomControl_c::mStayNo) { return false; } // Only check stage if it is valid if (!stage) { return false; } return daAlink_c::checkStageName(stage); } void checkTransformFromWolf() { if (dComIfGs_getTransformStatus()) { daAlink_getAlinkActorClass()->procCoMetamorphoseInit(); } } u8 setNextWarashibeItem() { static const u8 questItemsList[] = { dItemNo_Randomizer_LETTER_e, dItemNo_Randomizer_BILL_e, dItemNo_Randomizer_WOOD_STATUE_e, dItemNo_Randomizer_IRIAS_PENDANT_e, dItemNo_Randomizer_HORSE_FLUTE_e }; u32 listLength = sizeof(questItemsList) / sizeof(questItemsList[0]); u8 newItem = 0xFF; // null by default for (u32 i = 0; i < listLength; i++) { const u32 item = questItemsList[i]; const u8 slotItem = dComIfGs_getItem(21, 0); if (item == slotItem) { newItem = item; u32 j = i; do { j = (j + 1) % listLength; // Move to next index, wrapping around if needed. if (checkItemGet(questItemsList[j], 1)) { newItem = questItemsList[j]; break; } } while (j != i); // If the item to switch to is the same as the current item and we don't have the item anymore, null the slot if ((newItem == item) && !checkItemGet(item, 1)) { newItem = 0xFF; } dComIfGs_setItem(21, newItem); break; } } return newItem; } void offWarashibeItem(u8 item) { g_dComIfG_gameInfo.info.getSavedata().getPlayer().getGetItem().offFirstBit(item); setNextWarashibeItem(); } int initCreatePlayerItem(u32 item, u32 flag, const cXyz* pos, int roomNo, const csXyz* angle, const cXyz* scale) { u32 params = 0xFF0000 | ((flag & 0xFF) << 0x8) | (item & 0xFF); return fopAcM_create(539, params, pos, roomNo, angle, scale, -1); } int getStageID(const char* stage) { int loopCount = sizeof(allStages) / sizeof(allStages[0]); for (int i = 0; i < loopCount; i++) { // If stage is NULL, check for current stage if (stage == NULL) { if (daAlink_c::checkStageName(allStages[i])) return i; } else if (strcmp(stage, allStages[i]) == 0) { return i; } } // Didn't find the current stage for some reason return -1; } bool playerIsOnTitleScreen() { // Player is either on title screen movie stage (S_MV000) or on title screen map layer 10 return strcmp(dComIfGp_getStartStageName(), "S_MV000") == 0 || (strcmp(dComIfGp_getStartStageName(), "F_SP102") == 0 && dComIfG_play_c::getLayerNo(0) == 10); } u16 getItemMessageID(u8 itemId) { // If heart piece, choose from the different heart piece messages if (itemId == dItemNo_Randomizer_KAKERA_HEART_e) { static u32 const heartPieceMessage[5] = {0x86, 0x9C, 0x9D, 0x9E, 0x9F}; return heartPieceMessage[dComIfGs_getMaxLife() % 5]; } return itemId + 0x65; } int numCompletedDungeons() { int numCompleted{0}; // Loop through dungeon area node ids for (int i = 0x10; i < 0x18; ++i) { numCompleted += dComIfGs_isStageBossEnemy(i); } return numCompleted; } int numFusedShadows() { int numFusedShadows{0}; for (int i = 0; i < 3; ++i) { numFusedShadows += dComIfGs_isCollectCrystal(i); } return numFusedShadows; } int numMirrorShards() { int numMirrorShards{0}; for (int i = 0; i < 4; ++i) { numMirrorShards += dComIfGs_isCollectMirror(i); } return numMirrorShards; } int getTempleKeysFound(int stage) { static std::unordered_map> keyDoorFlags = { {0xA, {0x0}}, {0x10, {0x7, 0xB, 0x2B, 0x3E}}, {0x11, {0x33, 0x3D, 0x3F}}, {0x12, {0x23, 0x24, 0x34}}, {0x13, {0x27, 0x46, 0x4D, 0x5A, 0x5B}}, {0x14, {0x2B, 0x2C, 0x2F, 0x30}}, {0x15, {0x1B, 0x1C, 0x1D}}, {0x16, {0x6}}, {0x17, {0x6, 0x7, 0x8, 0xB, 0x23, 0x24, 0x25}}, {0x18, {0x4C, 0x6F, 0x7C}} }; int count = dComIfGs_getKeyNum(stage); // Add number of unlocked key doors for this dungeon to current key count for (auto flag : keyDoorFlags[stage]) { if (dComIfGs_isSwitch(stage, flag)) { count += 1; } } return count; } bool isTempleBigKeyFound(int stage) { // The boss key never gets taken away unlike small keys return dComIfGs_isDungeonItemBossKey(stage); } randomizer::logic::item_pool::ItemPool getSaveItemPool(randomizer::logic::world::World* world) { randomizer::logic::item_pool::ItemPool pool{}; // Item wheel items for (int i = 0; i < MAX_ITEM_SLOTS; ++i) { switch (dComIfGs_getItem(i, false)) { case dItemNo_Randomizer_HAWK_EYE_e: pool.push_back(world->GetItem("Hawkeye", true)); break; case dItemNo_Randomizer_BOOMERANG_e: pool.push_back(world->GetItem("Gale Boomerang", true)); break; case dItemNo_Randomizer_SPINNER_e: pool.push_back(world->GetItem("Spinner", true)); break; case dItemNo_Randomizer_IRONBALL_e: pool.push_back(world->GetItem("Ball and Chain", true)); break; case dItemNo_Randomizer_BOW_e: pool.push_back(world->GetItem("Progressive Bow", true)); break; case dItemNo_Randomizer_W_HOOKSHOT_e: pool.push_back(world->GetItem("Progressive Clawshot", true)); [[fallthrough]]; case dItemNo_Randomizer_HOOKSHOT_e: pool.push_back(world->GetItem("Progressive Clawshot", true)); break; case dItemNo_Randomizer_HVY_BOOTS_e: pool.push_back(world->GetItem("Iron Boots", true)); break; case dItemNo_Randomizer_COPY_ROD_e: pool.push_back(world->GetItem("Progressive Dominion Rod", true)); // Powered up dominion rod if (dComIfGs_isEventBit(0x2580)) { pool.push_back(world->GetItem("Progressive Dominion Rod", true)); } break; case dItemNo_Randomizer_KANTERA_e: pool.push_back(world->GetItem("Lantern", true)); break; case dItemNo_Randomizer_JEWEL_ROD_e: case dItemNo_Randomizer_JEWEL_BEE_ROD_e: case dItemNo_Randomizer_JEWEL_WORM_ROD_e: pool.push_back(world->GetItem("Progressive Fishing Rod", true)); [[fallthrough]]; case dItemNo_Randomizer_FISHING_ROD_1_e: case dItemNo_Randomizer_BEE_ROD_e: case dItemNo_Randomizer_WORM_ROD_e: pool.push_back(world->GetItem("Progressive Fishing Rod", true)); break; case dItemNo_Randomizer_PACHINKO_e: pool.push_back(world->GetItem("Slingshot", true)); break; case dItemNo_Randomizer_BOMB_BAG_LV1_e: case dItemNo_Randomizer_NORMAL_BOMB_e: case dItemNo_Randomizer_WATER_BOMB_e: case dItemNo_Randomizer_POKE_BOMB_e: pool.push_back(world->GetItem("Bomb Bag", true)); break; case dItemNo_Randomizer_RAFRELS_MEMO_e: pool.push_back(world->GetItem("Aurus Memo", true)); break; case dItemNo_Randomizer_ASHS_SCRIBBLING_e: pool.push_back(world->GetItem("Asheis Sketch", true)); break; case dItemNo_Randomizer_EMPTY_BOTTLE_e: case dItemNo_Randomizer_RED_BOTTLE_e: case dItemNo_Randomizer_GREEN_BOTTLE_e: case dItemNo_Randomizer_BLUE_BOTTLE_e: case dItemNo_Randomizer_MILK_BOTTLE_e: case dItemNo_Randomizer_HALF_MILK_BOTTLE_e: case dItemNo_Randomizer_OIL_BOTTLE_e: case dItemNo_Randomizer_WATER_BOTTLE_e: case dItemNo_Randomizer_OIL_BOTTLE_2_e: case dItemNo_Randomizer_RED_BOTTLE_2_e: case dItemNo_Randomizer_UGLY_SOUP_e: case dItemNo_Randomizer_HOT_SPRING_e: case dItemNo_Randomizer_FAIRY_e: case dItemNo_Randomizer_HOT_SPRING_2_e: case dItemNo_Randomizer_OIL2_e: case dItemNo_Randomizer_OIL_e: case dItemNo_Randomizer_FAIRY_DROP_e: case dItemNo_Randomizer_DROP_BOTTLE_e: case dItemNo_Randomizer_BEE_CHILD_e: case dItemNo_Randomizer_CHUCHU_RARE_e: case dItemNo_Randomizer_CHUCHU_RED_e: case dItemNo_Randomizer_CHUCHU_BLUE_e: case dItemNo_Randomizer_CHUCHU_GREEN_e: case dItemNo_Randomizer_CHUCHU_YELLOW_e: case dItemNo_Randomizer_CHUCHU_PURPLE_e: case dItemNo_Randomizer_LV1_SOUP_e: case dItemNo_Randomizer_LV2_SOUP_e: case dItemNo_Randomizer_LV3_SOUP_e: case dItemNo_Randomizer_OIL_BOTTLE3_e: case dItemNo_Randomizer_CHUCHU_BLACK_e: pool.push_back(world->GetItem("Empty Bottle", true)); break; default: break; } } // Shadow Crystal if (dComIfGs_isEventBit(0xD04)) { pool.push_back(world->GetItem("Shadow Crystal", true)); } // Fused Shadows for (int i = 0; i < numFusedShadows(); ++i) { pool.push_back(world->GetItem("Progressive Fused Shadow", true)); } // Mirror Shards for (int i = 0; i < numMirrorShards(); ++i) { pool.push_back(world->GetItem("Progressive Mirror Shard", true)); } // Poe Souls for (int i = 0; i < dComIfGs_getPohSpiritNum(); ++i) { pool.push_back(world->GetItem("Poe Soul", true)); } // Hearts for (int i = 0; i < dComIfGs_getMaxLife(); ++i) { pool.push_back(world->GetItem("Piece of Heart", true)); } // Sky Book characters for (int i = 0; i < dComIfGs_getAncientDocumentNum(); ++i) { pool.push_back(world->GetItem("Progressive Sky Book", true)); } // Small Keys static std::unordered_map keyRegionItemNameMap = { {0xA, "Gerudo Desert Bulblin Camp Key"}, {0x10, "Forest Temple Small Key"}, {0x11, "Goron Mines Small Key"}, {0x12, "Lakebed Temple Small Key"}, {0x13, "Arbiters Grounds Small Key"}, {0x14, "Snowpeak Ruins Small Key"}, {0x15, "Temple of Time Small Key"}, {0x16, "City in the Sky Small Key"}, {0x17, "Palace of Twilight Small Key"}, {0x18, "Hyrule Castle Small Key"}, }; for (auto& [stage, keyName] : keyRegionItemNameMap) { for (int i = 0; i < getTempleKeysFound(stage); ++i) { pool.push_back(world->GetItem(keyName, true)); } } // Gate Keys if (haveItem(dItemNo_Randomizer_BOSSRIDER_KEY_e)) { pool.push_back(world->GetItem(dItemNo_Randomizer_BOSSRIDER_KEY_e, true)); } // Big Keys static std::unordered_map bigKeyRegionItemNameMap = { {0x10, "Forest Temple Big Key"}, {0x12, "Lakebed Temple Big Key"}, {0x13, "Arbiters Grounds Big Key"}, {0x14, "Snowpeak Ruins Bedroom Key"}, {0x15, "Temple of Time Big Key"}, {0x16, "City in the Sky Big Key"}, {0x17, "Palace of Twilight Big Key"}, {0x18, "Hyrule Castle Big Key"}, }; for (auto& [stage, keyName] : bigKeyRegionItemNameMap) { if (isTempleBigKeyFound(stage)) { pool.push_back(world->GetItem(keyName, true)); } } // Goron Mines Key Shards for (int i = dItemNo_Randomizer_L2_KEY_PIECES1_e; i < dItemNo_Randomizer_L2_KEY_PIECES3_e; ++i) { if (haveItem(i)) { pool.push_back(world->GetItem("Goron Mines Key Shard", true)); } } // Ordon Pumpkin if (haveItem(dItemNo_Randomizer_TOMATO_PUREE_e)) { pool.push_back(world->GetItem(dItemNo_Randomizer_TOMATO_PUREE_e, true)); } // Ordon Cheese if (haveItem(dItemNo_Randomizer_TASTE_e)) { pool.push_back(world->GetItem(dItemNo_Randomizer_TASTE_e, true)); } // Golden Bugs for (int i = dItemNo_Randomizer_M_BEETLE_e; i < dItemNo_Randomizer_F_MAYFLY_e; ++i) { if (haveItem(i)) { pool.push_back(world->GetItem(i, true)); } } // Ilia quest items for (int i = dItemNo_Randomizer_LETTER_e; i < dItemNo_Randomizer_HORSE_FLUTE_e; ++i) { if (haveItem(i)) { pool.push_back(world->GetItem(i, true)); } } // Warp Portals // Item ids are scattered so we have to explicitly list them all static const int portals[] = { dItemNo_Randomizer_ORDON_PORTAL_e, dItemNo_Randomizer_SOUTH_FARON_PORTAL_e, dItemNo_Randomizer_NORTH_FARON_PORTAL_e, dItemNo_Randomizer_SACRED_GROVE_PORTAL_e, dItemNo_Randomizer_KAKARIKO_GORGE_PORTAL_e, dItemNo_Randomizer_KAKARIKO_VILLAGE_PORTAL_e, dItemNo_Randomizer_DEATH_MOUNTAIN_PORTAL_e, dItemNo_Randomizer_ELDIN_BRIDGE_PORTAL_e, dItemNo_Randomizer_CASTLE_TOWN_PORTAL_e, dItemNo_Randomizer_UPPER_ZORAS_RIVER_PORTAL_e, dItemNo_Randomizer_ZORAS_DOMAIN_PORTAL_e, dItemNo_Randomizer_SNOWPEAK_PORTAL_e, dItemNo_Randomizer_GERUDO_DESERT_PORTAL_e, dItemNo_Randomizer_MIRROR_CHAMBER_PORTAL_e, }; for (auto portal : portals) { if (haveItem(portal)) { pool.push_back(world->GetItem(portal, true)); } } // Swords static const int swords[] = { dItemNo_Randomizer_WOOD_STICK_e, dItemNo_Randomizer_SWORD_e, dItemNo_Randomizer_MASTER_SWORD_e, dItemNo_Randomizer_LIGHT_SWORD_e, }; for (auto sword : swords) { if (haveItem(sword)) { pool.push_back(world->GetItem("Progressive Sword", true)); } } // Other Equipment static const int equipment[] = { dItemNo_Randomizer_WOOD_SHIELD_e, dItemNo_Randomizer_HYLIA_SHIELD_e, dItemNo_Randomizer_WEAR_ZORA_e, dItemNo_Randomizer_ARMOR_e, }; for (auto item : equipment) { if (haveItem(item)) { pool.push_back(world->GetItem(item, true)); } } // Hidden Skills static const int hiddenSkills[] = { dItemNo_Randomizer_ENDING_BLOW_e, dItemNo_Randomizer_SHIELD_ATTACK_e, dItemNo_Randomizer_BACK_SLICE_e, dItemNo_Randomizer_HELM_SPLITTER_e, dItemNo_Randomizer_MORTAL_DRAW_e, dItemNo_Randomizer_JUMP_STRIKE_e, dItemNo_Randomizer_GREAT_SPIN_e, }; for (auto skill : hiddenSkills) { if (haveItem(skill)) { pool.push_back(world->GetItem("Progressive Hidden Skill", true)); } } // Wallets switch (dComIfGs_getWalletSize()) { case GIANT_WALLET: pool.push_back(world->GetItem("Progressive Wallet", true)); [[fallthrough]]; case BIG_WALLET: pool.push_back(world->GetItem("Progressive Wallet", true)); [[fallthrough]]; default: break; } return pool; } int getStageSaveId(int id) { switch (id) { case 41: // F_SP00 (Ordon Ranch) case 43: // F_SP103 (Ordon Village / Outside Link's House) case 44: // F_SP104 (Ordon Spring) case 65: // R_SP01 (Ordon House Interiors) return 0x0; case 66: // R_SP107 (Castle Town Sewers) return 0x1; case 40: // D_SB10 (Faron Woods Cave) case 45: // F_SP108 (Faron Woods) case 67: // R_SP108 (Coro's House) return 0x2; case 46: // F_SP109 (Kakariko Village) case 47: // F_SP110 (Death Mountain Trail) case 48: // F_SP111 (Kakariko Graveyard) case 63: // F_SP128 (Hidden Village) case 68: // R_SP109 (Kakariko Interiors) case 69: // R_SP110 (Goron Elder's Hall) case 72: // R_SP128 (Impaz's House) case 75: // R_SP209 (Sanctuary Basement) return 0x3; case 49: // F_SP112 (Zora's River) case 50: // F_SP113 (Zora's Domain) case 52: // F_SP115 (Lake Hylia) case 61: // F_SP126 (Upper Zora's River) case 71: // R_SP127 (Hena's Cabin) return 0x4; case 56: // F_SP121 (Hyrule Field) case 57: // F_SP122 (Outside Castle Town) case 58: // F_SP123 (King Bulblin 2) case 64: // F_SP200 (Wolf Howling Cutscene Map) return 0x6; case 54: // F_SP117 (Lost Woods) return 0x7; case 51: // F_SP114 (Snowpeak Mountain) return 0x8; case 53: // F_SP116 (Castle Town) case 70: // R_SP116 (Telma's Bar / Secret Passage) case 73: // R_SP160 (Hyrule Castle Town Interiors) case 74: // R_SP161 (STAR Tent) return 0x9; case 55: // F_SP118 (Bulblin Camp) case 59: // F_SP124 (Gerudo Desert) case 60: // F_SP125 (Mirror Chamber) return 0xA; case 62: // F_SP127 (Fishing Pond) return 0xB; case 6: // D_MN05 (Forest Temple) case 7: // D_MN05A (Diababa Arena) case 8: // D_MN05B (Ook Arena) return 0x10; case 3: // D_MN04 (Goron Mines) case 4: // D_MN04A (Fyrus Arena) case 5: // D_MN04B (Dangoro Arena) return 0x11; case 0: // D_MN01 (Lakebed Temple) case 1: // D_MN01A (Morpheel Arena) case 2: // D_MN01B (Deku Toad Arena) return 0x12; case 24: // D_MN10 (Arbiter's Grounds) case 25: // D_MN10A (Stallord Arena) case 26: // D_MN10B (Death Sword Arena) return 0x13; case 27: // D_MN11 (Snowpeak Ruins) case 28: // D_MN11A (Blizzeta Arena) case 29: // D_MN11B (Darkhammer Arena) return 0x14; case 9: // D_MN06 (Temple of Time) case 10: // D_MN06A (Armogohma Arena) case 11: // D_MN06B (Darknut Arena) return 0x15; case 12: // D_MN07 (City in the Sky) case 13: // D_MN07A (Argorok Arena) case 14: // D_MN07B (Aeralfos Arena) return 0x16; case 15: // D_MN08 (Palace of Twilight) case 16: // D_MN08A (Palace of Twilight Throne Room) case 17: // D_MN08B (Phantom Zant Arena 1) case 18: // D_MN08C (Phantom Zant Arena 2) case 19: // D_MN08D (Zant Arenas) return 0x17; case 20: // D_MN09 (Hyrule Castle) case 21: // D_MN09A (Hyrule Castle Throne Room) case 22: // D_MN09B (Horseback Ganondorf Arena) case 23: // D_MN09C (Dark Lord Ganondorf Arena) return 0x18; case 30: // D_SB00 (Ice Cavern) case 31: // D_SB01 (Cave Of Ordeals) case 32: // D_SB02 (Kakariko Gorge Cavern) return 0x19; case 33: // D_SB03 (Lake Hylia Cavern) case 34: // D_SB04 (Goron Stockcave) return 0x1A; case 35: // D_SB05 (Grotto 1) case 36: // D_SB06 (Grotto 2) case 37: // D_SB07 (Grotto 3) case 38: // D_SB08 (Grotto 4) case 39: // D_SB09 (Grotto 5) return 0x1B; case 42: // F_SP102 (Title Screen / King Bulblin 1) return 0xFF; default: DuskLog.warn("Failed to find Save Id for ID: {}" , id); return -1; } } int getStageSaveId(const char* stage) { int id = getStageID(stage); return getStageSaveId(id); } bool tracker_isEventBit(u16 flag) { return g_randomizerState.mTrackerTempEventFlag == flag || dComIfGs_isEventBit(flag); } bool tracker_isStageSwitch(int stage, int flag) { return dComIfGs_isStageSwitch(stage, flag) || (g_randomizerState.mTrackerTempSwitchFlag.flag == flag && g_randomizerState.mTrackerTempSwitchFlag.stage == stage); }