Files
dusklight/src/dusk/randomizer/game/tools.cpp
T

593 lines
20 KiB
C++

#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<int, std::vector<int>> 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<int, std::string> 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<int, std::string> 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);
}