mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-12-18 06:12:20 -05:00
1446 lines
66 KiB
C++
1446 lines
66 KiB
C++
#include "mods.h"
|
|
#include <libultraship/bridge.h>
|
|
#include "game-interactor/GameInteractor.h"
|
|
#include "tts/tts.h"
|
|
#include "soh/OTRGlobals.h"
|
|
#include "soh/SaveManager.h"
|
|
#include "soh/ResourceManagerHelpers.h"
|
|
#include "soh/Enhancements/boss-rush/BossRushTypes.h"
|
|
#include "soh/Enhancements/boss-rush/BossRush.h"
|
|
#include "soh/Enhancements/enhancementTypes.h"
|
|
#include "soh/Enhancements/randomizer/3drando/random.hpp"
|
|
#include "soh/Enhancements/cosmetics/authenticGfxPatches.h"
|
|
#include <soh/Enhancements/item-tables/ItemTableManager.h>
|
|
#include "soh/Enhancements/nametag.h"
|
|
#include "soh/Enhancements/timesaver_hook_handlers.h"
|
|
#include "soh/Enhancements/TimeSavers/TimeSavers.h"
|
|
#include "soh/Enhancements/cheat_hook_handlers.h"
|
|
#include "soh/Enhancements/randomizer/hook_handlers.h"
|
|
#include "objects/object_gi_compass/object_gi_compass.h"
|
|
|
|
#include "src/overlays/actors/ovl_En_Bb/z_en_bb.h"
|
|
#include "src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.h"
|
|
#include "src/overlays/actors/ovl_En_Mb/z_en_mb.h"
|
|
#include "src/overlays/actors/ovl_En_Tite/z_en_tite.h"
|
|
#include "src/overlays/actors/ovl_En_Zf/z_en_zf.h"
|
|
#include "src/overlays/actors/ovl_En_Wf/z_en_wf.h"
|
|
#include "src/overlays/actors/ovl_En_Reeba/z_en_reeba.h"
|
|
#include "src/overlays/actors/ovl_En_Peehat/z_en_peehat.h"
|
|
#include "src/overlays/actors/ovl_En_Po_Field/z_en_po_field.h"
|
|
#include "src/overlays/actors/ovl_En_Poh/z_en_poh.h"
|
|
#include "src/overlays/actors/ovl_En_Tp/z_en_tp.h"
|
|
#include "src/overlays/actors/ovl_En_Firefly/z_en_firefly.h"
|
|
#include "src/overlays/actors/ovl_En_Xc/z_en_xc.h"
|
|
#include "src/overlays/actors/ovl_Fishing/z_fishing.h"
|
|
#include "src/overlays/actors/ovl_Obj_Switch/z_obj_switch.h"
|
|
#include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h"
|
|
#include "src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.h"
|
|
#include "src/overlays/actors/ovl_En_Door/z_en_door.h"
|
|
#include "objects/object_link_boy/object_link_boy.h"
|
|
#include "objects/object_link_child/object_link_child.h"
|
|
#include "kaleido.h"
|
|
|
|
extern "C" {
|
|
#include <z64.h>
|
|
#include "align_asset_macro.h"
|
|
#include "macros.h"
|
|
#include "soh/cvar_prefixes.h"
|
|
#include "functions.h"
|
|
#include "variables.h"
|
|
#include "functions.h"
|
|
#include "src/overlays/actors/ovl_En_Door/z_en_door.h"
|
|
|
|
extern SaveContext gSaveContext;
|
|
extern PlayState* gPlayState;
|
|
extern void Overlay_DisplayText(float duration, const char* text);
|
|
}
|
|
|
|
// GreyScaleEndDlist
|
|
#define dgEndGrayscaleAndEndDlistDL "__OTR__helpers/cosmetics/gEndGrayscaleAndEndDlistDL"
|
|
static const ALIGN_ASSET(2) char gEndGrayscaleAndEndDlistDL[] = dgEndGrayscaleAndEndDlistDL;
|
|
|
|
// This is used for the Temple of Time Medalions' color
|
|
#define dtokinoma_room_0DL_007A70 "__OTR__scenes/shared/tokinoma_scene/tokinoma_room_0DL_007A70"
|
|
static const ALIGN_ASSET(2) char tokinoma_room_0DL_007A70[] = dtokinoma_room_0DL_007A70;
|
|
#define dtokinoma_room_0DL_007FD0 "__OTR__scenes/shared/tokinoma_scene/tokinoma_room_0DL_007FD0"
|
|
static const ALIGN_ASSET(2) char tokinoma_room_0DL_007FD0[] = dtokinoma_room_0DL_007FD0;
|
|
|
|
void RegisterInfiniteMoney() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
if (CVarGetInteger(CVAR_CHEAT("InfiniteMoney"), 0) != 0 && (!IS_RANDO || Flags_GetRandomizerInf(RAND_INF_HAS_WALLET))) {
|
|
if (gSaveContext.rupees < CUR_CAPACITY(UPG_WALLET)) {
|
|
gSaveContext.rupees = CUR_CAPACITY(UPG_WALLET);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterInfiniteHealth() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
if (CVarGetInteger(CVAR_CHEAT("InfiniteHealth"), 0) != 0) {
|
|
if (gSaveContext.health < gSaveContext.healthCapacity) {
|
|
gSaveContext.health = gSaveContext.healthCapacity;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterInfiniteAmmo() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
if (CVarGetInteger(CVAR_CHEAT("InfiniteAmmo"), 0) != 0) {
|
|
// Deku Sticks
|
|
if (AMMO(ITEM_STICK) < CUR_CAPACITY(UPG_STICKS)) {
|
|
AMMO(ITEM_STICK) = CUR_CAPACITY(UPG_STICKS);
|
|
}
|
|
|
|
// Deku Nuts
|
|
if (AMMO(ITEM_NUT) < CUR_CAPACITY(UPG_NUTS)) {
|
|
AMMO(ITEM_NUT) = CUR_CAPACITY(UPG_NUTS);
|
|
}
|
|
|
|
// Bombs
|
|
if (AMMO(ITEM_BOMB) < CUR_CAPACITY(UPG_BOMB_BAG)) {
|
|
AMMO(ITEM_BOMB) = CUR_CAPACITY(UPG_BOMB_BAG);
|
|
}
|
|
|
|
// Fairy Bow (Ammo)
|
|
if (AMMO(ITEM_BOW) < CUR_CAPACITY(UPG_QUIVER)) {
|
|
AMMO(ITEM_BOW) = CUR_CAPACITY(UPG_QUIVER);
|
|
}
|
|
|
|
// Fairy Slingshot (Ammo)
|
|
if (AMMO(ITEM_SLINGSHOT) < CUR_CAPACITY(UPG_BULLET_BAG)) {
|
|
AMMO(ITEM_SLINGSHOT) = CUR_CAPACITY(UPG_BULLET_BAG);
|
|
}
|
|
|
|
// Bombchus (max: 50, no upgrades)
|
|
if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU && AMMO(ITEM_BOMBCHU) < 50) {
|
|
AMMO(ITEM_BOMBCHU) = 50;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterInfiniteMagic() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
if (CVarGetInteger(CVAR_CHEAT("InfiniteMagic"), 0) != 0) {
|
|
if (gSaveContext.isMagicAcquired && gSaveContext.magic != (gSaveContext.isDoubleMagicAcquired + 1) * 0x30) {
|
|
gSaveContext.magic = (gSaveContext.isDoubleMagicAcquired + 1) * 0x30;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterInfiniteNayrusLove() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
if (CVarGetInteger(CVAR_CHEAT("InfiniteNayru"), 0) != 0) {
|
|
gSaveContext.nayrusLoveTimer = 0x44B;
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterMoonJumpOnL() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
|
|
if (CVarGetInteger(CVAR_CHEAT("MoonJumpOnL"), 0) != 0) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
|
|
if (CHECK_BTN_ANY(gPlayState->state.input[0].cur.button, BTN_L)) {
|
|
player->actor.velocity.y = 6.34375f;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void RegisterInfiniteISG() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
|
|
if (CVarGetInteger(CVAR_CHEAT("EasyISG"), 0) != 0) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
player->meleeWeaponState = 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
//Permanent quick put away (QPA) glitched damage value
|
|
void RegisterEzQPA() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
|
|
if (CVarGetInteger(CVAR_CHEAT("EasyQPA"), 0) != 0) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
player->meleeWeaponQuads[0].info.toucher.dmgFlags = 0x16171617;
|
|
player->meleeWeaponQuads[1].info.toucher.dmgFlags = 0x16171617;
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterUnrestrictedItems() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true)) return;
|
|
|
|
if (CVarGetInteger(CVAR_CHEAT("NoRestrictItems"), 0) != 0) {
|
|
u8 sunsBackup = gPlayState->interfaceCtx.restrictions.sunsSong;
|
|
memset(&gPlayState->interfaceCtx.restrictions, 0, sizeof(gPlayState->interfaceCtx.restrictions));
|
|
gPlayState->interfaceCtx.restrictions.sunsSong = sunsBackup;
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterFreezeTime() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (CVarGetInteger(CVAR_CHEAT("FreezeTime"), 0) != 0) {
|
|
if (CVarGetInteger(CVAR_GENERAL("PrevTime"), -1) == -1) {
|
|
CVarSetInteger(CVAR_GENERAL("PrevTime"), gSaveContext.dayTime);
|
|
}
|
|
|
|
int32_t prevTime = CVarGetInteger(CVAR_GENERAL("PrevTime"), gSaveContext.dayTime);
|
|
gSaveContext.dayTime = prevTime;
|
|
} else {
|
|
CVarClear(CVAR_GENERAL("PrevTime"));
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Switches Link's age and respawns him at the last entrance he entered.
|
|
void SwitchAge() {
|
|
if (gPlayState == NULL) return;
|
|
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
|
|
// Hyrule Castle: Very likely to fall through floor, so we force a specific entrance
|
|
if (gPlayState->sceneNum == SCENE_HYRULE_CASTLE || gPlayState->sceneNum == SCENE_OUTSIDE_GANONS_CASTLE) {
|
|
gPlayState->nextEntranceIndex = ENTR_CASTLE_GROUNDS_SOUTH_EXIT;
|
|
} else {
|
|
gSaveContext.respawnFlag = 1;
|
|
gPlayState->nextEntranceIndex = gSaveContext.entranceIndex;
|
|
|
|
// Preserve the player's position and orientation
|
|
gSaveContext.respawn[RESPAWN_MODE_DOWN].entranceIndex = gPlayState->nextEntranceIndex;
|
|
gSaveContext.respawn[RESPAWN_MODE_DOWN].roomIndex = gPlayState->roomCtx.curRoom.num;
|
|
gSaveContext.respawn[RESPAWN_MODE_DOWN].pos = player->actor.world.pos;
|
|
gSaveContext.respawn[RESPAWN_MODE_DOWN].yaw = player->actor.shape.rot.y;
|
|
|
|
if (gPlayState->roomCtx.curRoom.behaviorType2 < 4) {
|
|
gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0x0DFF;
|
|
} else {
|
|
// Scenes with static backgrounds use a special camera we need to preserve
|
|
Camera* camera = GET_ACTIVE_CAM(gPlayState);
|
|
s16 camId = camera->camDataIdx;
|
|
gSaveContext.respawn[RESPAWN_MODE_DOWN].playerParams = 0x0D00 | camId;
|
|
}
|
|
}
|
|
|
|
gPlayState->transitionTrigger = TRANS_TRIGGER_START;
|
|
gPlayState->transitionType = TRANS_TYPE_INSTANT;
|
|
gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK_FAST;
|
|
gPlayState->linkAgeOnLoad ^= 1;
|
|
|
|
static HOOK_ID hookId = 0;
|
|
hookId = REGISTER_VB_SHOULD(VB_INFLICT_VOID_DAMAGE, {
|
|
*should = false;
|
|
GameInteractor::Instance->UnregisterGameHookForID<GameInteractor::OnVanillaBehavior>(hookId);
|
|
});
|
|
}
|
|
|
|
/// Switches Link's age and respawns him at the last entrance he entered.
|
|
void RegisterOcarinaTimeTravel() {
|
|
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnOcarinaSongAction>([]() {
|
|
if (!GameInteractor::IsSaveLoaded(true) || !CVarGetInteger(CVAR_ENHANCEMENT("TimeTravel"), 0)) {
|
|
return;
|
|
}
|
|
|
|
Actor* player = &GET_PLAYER(gPlayState)->actor;
|
|
Actor* nearbyTimeBlockEmpty = Actor_FindNearby(gPlayState, player, ACTOR_OBJ_WARP2BLOCK, ACTORCAT_ITEMACTION, 300.0f);
|
|
Actor* nearbyTimeBlock = Actor_FindNearby(gPlayState, player, ACTOR_OBJ_TIMEBLOCK, ACTORCAT_ITEMACTION, 300.0f);
|
|
Actor* nearbyOcarinaSpot = Actor_FindNearby(gPlayState, player, ACTOR_EN_OKARINA_TAG, ACTORCAT_PROP, 120.0f);
|
|
Actor* nearbyDoorOfTime = Actor_FindNearby(gPlayState, player, ACTOR_DOOR_TOKI, ACTORCAT_BG, 500.0f);
|
|
Actor* nearbyFrogs = Actor_FindNearby(gPlayState, player, ACTOR_EN_FR, ACTORCAT_NPC, 300.0f);
|
|
bool justPlayedSoT = gPlayState->msgCtx.lastPlayedSong == OCARINA_SONG_TIME;
|
|
bool notNearAnySource = !nearbyTimeBlockEmpty && !nearbyTimeBlock && !nearbyOcarinaSpot && !nearbyDoorOfTime && !nearbyFrogs;
|
|
bool hasOcarinaOfTime = (INV_CONTENT(ITEM_OCARINA_TIME) == ITEM_OCARINA_TIME);
|
|
bool doesntNeedOcarinaOfTime = CVarGetInteger(CVAR_ENHANCEMENT("TimeTravel"), 0) == 2;
|
|
bool hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER);
|
|
// TODO: Once Swordless Adult is fixed: Remove the Master Sword check
|
|
if (justPlayedSoT && notNearAnySource && (hasOcarinaOfTime || doesntNeedOcarinaOfTime) && hasMasterSword) {
|
|
SwitchAge();
|
|
}
|
|
});
|
|
}
|
|
|
|
void AutoSave(GetItemEntry itemEntry) {
|
|
u8 item = itemEntry.itemId;
|
|
bool performSave = false;
|
|
// Don't autosave immediately after buying items from shops to prevent getting them for free!
|
|
// Don't autosave in the Chamber of Sages since resuming from that map breaks the game
|
|
// Don't autosave during the Ganon fight when picking up the Master Sword
|
|
if ((CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) != AUTOSAVE_OFF) && (gPlayState != NULL) && (gSaveContext.pendingSale == ITEM_NONE) &&
|
|
(gPlayState->gameplayFrames > 60 && gSaveContext.cutsceneIndex < 0xFFF0) && (gPlayState->sceneNum != SCENE_GANON_BOSS) && (gPlayState->sceneNum != SCENE_CHAMBER_OF_THE_SAGES)) {
|
|
if (((CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS) || (CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_ALL_ITEMS)) && (item != ITEM_NONE)) {
|
|
// Autosave for all items
|
|
performSave = true;
|
|
|
|
} else if (((CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_MAJOR_ITEMS) || (CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_MAJOR_ITEMS)) && (item != ITEM_NONE)) {
|
|
// Autosave for major items
|
|
if (itemEntry.modIndex == 0) {
|
|
switch (item) {
|
|
case ITEM_STICK:
|
|
case ITEM_NUT:
|
|
case ITEM_BOMB:
|
|
case ITEM_BOW:
|
|
case ITEM_SEEDS:
|
|
case ITEM_FISHING_POLE:
|
|
case ITEM_MAGIC_SMALL:
|
|
case ITEM_MAGIC_LARGE:
|
|
case ITEM_INVALID_4:
|
|
case ITEM_INVALID_5:
|
|
case ITEM_INVALID_6:
|
|
case ITEM_INVALID_7:
|
|
case ITEM_HEART:
|
|
case ITEM_RUPEE_GREEN:
|
|
case ITEM_RUPEE_BLUE:
|
|
case ITEM_RUPEE_RED:
|
|
case ITEM_RUPEE_PURPLE:
|
|
case ITEM_RUPEE_GOLD:
|
|
case ITEM_INVALID_8:
|
|
case ITEM_STICKS_5:
|
|
case ITEM_STICKS_10:
|
|
case ITEM_NUTS_5:
|
|
case ITEM_NUTS_10:
|
|
case ITEM_BOMBS_5:
|
|
case ITEM_BOMBS_10:
|
|
case ITEM_BOMBS_20:
|
|
case ITEM_BOMBS_30:
|
|
case ITEM_ARROWS_SMALL:
|
|
case ITEM_ARROWS_MEDIUM:
|
|
case ITEM_ARROWS_LARGE:
|
|
case ITEM_SEEDS_30:
|
|
case ITEM_NONE:
|
|
break;
|
|
case ITEM_BOMBCHU:
|
|
case ITEM_BOMBCHUS_5:
|
|
case ITEM_BOMBCHUS_20:
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("EnableBombchuDrops"), 0)) {
|
|
performSave = true;
|
|
}
|
|
break;
|
|
default:
|
|
performSave = true;
|
|
break;
|
|
}
|
|
} else if (itemEntry.modIndex == 1 && item != RG_ICE_TRAP) {
|
|
performSave = true;
|
|
}
|
|
} else if (CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_MAJOR_ITEMS ||
|
|
CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_LOCATION_AND_ALL_ITEMS ||
|
|
CVarGetInteger(CVAR_ENHANCEMENT("Autosave"), AUTOSAVE_OFF) == AUTOSAVE_LOCATION) {
|
|
performSave = true;
|
|
}
|
|
if (performSave) {
|
|
Play_PerformSave(gPlayState);
|
|
performSave = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RegisterAutoSave() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) { AutoSave(itemEntry); });
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSaleEnd>([](GetItemEntry itemEntry) { AutoSave(itemEntry); });
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([](int32_t sceneNum) { AutoSave(GET_ITEM_NONE); });
|
|
}
|
|
|
|
void RegisterRupeeDash() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("RupeeDash"), 0)) {
|
|
return;
|
|
}
|
|
|
|
// Initialize Timer
|
|
static uint16_t rupeeDashTimer = 0;
|
|
uint16_t rdmTime = CVarGetInteger(CVAR_ENHANCEMENT("RupeeDashInterval"), 5) * 20;
|
|
|
|
// Did time change by DashInterval?
|
|
if (rupeeDashTimer >= rdmTime) {
|
|
rupeeDashTimer = 0;
|
|
if (gSaveContext.rupees > 0) {
|
|
uint16_t walletSize = (CUR_UPG_VALUE(UPG_WALLET) + 1) * -1;
|
|
Rupees_ChangeBy(walletSize);
|
|
} else {
|
|
Health_ChangeBy(gPlayState, -16);
|
|
}
|
|
} else {
|
|
rupeeDashTimer++;
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterShadowTag() {
|
|
static bool shouldSpawn = false;
|
|
static uint16_t delayTimer = 60;
|
|
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("ShadowTag"), 0)) {
|
|
return;
|
|
}
|
|
if (gPlayState->sceneNum == SCENE_FOREST_TEMPLE && // Forest Temple Scene
|
|
gPlayState->roomCtx.curRoom.num == 16 || // Green Poe Room
|
|
gPlayState->roomCtx.curRoom.num == 13 || // Blue Poe Room
|
|
gPlayState->roomCtx.curRoom.num == 12) { // Red Poe Room
|
|
return;
|
|
} else {
|
|
if (shouldSpawn && (delayTimer <= 0)) {
|
|
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_WALLMAS, 0, 0, 0, 0, 0, 0, 3, false);
|
|
shouldSpawn = false;
|
|
} else {
|
|
delayTimer--;
|
|
}
|
|
}
|
|
});
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneSpawnActors>([]() {
|
|
shouldSpawn = true;
|
|
delayTimer = 60;
|
|
});
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) {
|
|
shouldSpawn = true;
|
|
delayTimer = 60;
|
|
});
|
|
}
|
|
|
|
static bool hasAffectedHealth = false;
|
|
void UpdatePermanentHeartLossState() {
|
|
if (!GameInteractor::IsSaveLoaded()) return;
|
|
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("PermanentHeartLoss"), 0) && hasAffectedHealth) {
|
|
uint8_t heartContainers = gSaveContext.sohStats.heartContainers; // each worth 16 health
|
|
uint8_t heartPieces = gSaveContext.sohStats.heartPieces; // each worth 4 health, but only in groups of 4
|
|
uint8_t startingHealth = 16 * (IS_RANDO ? (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_HEARTS) + 1) : 3);
|
|
|
|
|
|
uint8_t newCapacity = startingHealth + (heartContainers * 16) + ((heartPieces - (heartPieces % 4)) * 4);
|
|
gSaveContext.healthCapacity = MAX(newCapacity, gSaveContext.healthCapacity);
|
|
gSaveContext.health = MIN(gSaveContext.health, gSaveContext.healthCapacity);
|
|
hasAffectedHealth = false;
|
|
}
|
|
}
|
|
|
|
void RegisterPermanentHeartLoss() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int16_t fileNum) {
|
|
hasAffectedHealth = false;
|
|
UpdatePermanentHeartLossState();
|
|
});
|
|
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("PermanentHeartLoss"), 0) || !GameInteractor::IsSaveLoaded()) return;
|
|
|
|
if (gSaveContext.healthCapacity > 16 && gSaveContext.healthCapacity - gSaveContext.health >= 16) {
|
|
gSaveContext.healthCapacity -= 16;
|
|
gSaveContext.health = MIN(gSaveContext.health, gSaveContext.healthCapacity);
|
|
hasAffectedHealth = true;
|
|
}
|
|
});
|
|
};
|
|
|
|
void RegisterDeleteFileOnDeath() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("DeleteFileOnDeath"), 0) || !GameInteractor::IsSaveLoaded() || gPlayState == NULL) return;
|
|
|
|
if (gPlayState->gameOverCtx.state == GAMEOVER_DEATH_MENU && gPlayState->pauseCtx.state == 9) {
|
|
SaveManager::Instance->DeleteZeldaFile(gSaveContext.fileNum);
|
|
hasAffectedHealth = false;
|
|
std::reinterpret_pointer_cast<Ship::ConsoleWindow>(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"))->Dispatch("reset");
|
|
}
|
|
});
|
|
}
|
|
|
|
struct DayTimeGoldSkulltulas {
|
|
uint16_t scene;
|
|
uint16_t room;
|
|
bool forChild;
|
|
std::vector<ActorEntry> actorEntries;
|
|
};
|
|
|
|
using DayTimeGoldSkulltulasList = std::vector<DayTimeGoldSkulltulas>;
|
|
|
|
void RegisterDaytimeGoldSkultullas() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneSpawnActors>([]() {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("NightGSAlwaysSpawn"), 0)) {
|
|
return;
|
|
}
|
|
|
|
// Gold Skulltulas that are not part of the scene actor list during the day
|
|
// Actor values copied from the night time scene actor list
|
|
static const DayTimeGoldSkulltulasList dayTimeGoldSkulltulas = {
|
|
// Graveyard
|
|
{ SCENE_GRAVEYARD, 1, true, { { ACTOR_EN_SW, { 156, 315, 795 }, { 16384, -32768, 0 }, -20096 } } },
|
|
// ZF
|
|
{ SCENE_ZORAS_FOUNTAIN, 0, true, { { ACTOR_EN_SW, { -1891, 187, 1911 }, { 16384, 18022, 0 }, -19964 } } },
|
|
// GF
|
|
{ SCENE_GERUDOS_FORTRESS, 0, false, { { ACTOR_EN_SW, { 1598, 999, -2008 }, { 16384, -16384, 0 }, -19198 } } },
|
|
{ SCENE_GERUDOS_FORTRESS, 1, false, { { ACTOR_EN_SW, { 3377, 1734, -4935 }, { 16384, 0, 0 }, -19199 } } },
|
|
// Kak
|
|
{ SCENE_KAKARIKO_VILLAGE, 0, false, { { ACTOR_EN_SW, { -18, 540, 1800 }, { 0, -32768, 0 }, -20160 } } },
|
|
{ SCENE_KAKARIKO_VILLAGE,
|
|
0,
|
|
true,
|
|
{ { ACTOR_EN_SW, { -465, 377, -888 }, { 0, 28217, 0 }, -20222 },
|
|
{ ACTOR_EN_SW, { 5, 686, -171 }, { 0, -32768, 0 }, -20220 },
|
|
{ ACTOR_EN_SW, { 324, 270, 905 }, { 16384, 0, 0 }, -20216 },
|
|
{ ACTOR_EN_SW, { -602, 120, 1120 }, { 16384, 0, 0 }, -20208 } } },
|
|
// LLR
|
|
{ SCENE_LON_LON_RANCH,
|
|
0,
|
|
true,
|
|
{ { ACTOR_EN_SW, { -2344, 180, 672 }, { 16384, 22938, 0 }, -29695 },
|
|
{ ACTOR_EN_SW, { 808, 48, 326 }, { 16384, 0, 0 }, -29694 },
|
|
{ ACTOR_EN_SW, { 997, 286, -2698 }, { 16384, -16384, 0 }, -29692 } } },
|
|
};
|
|
|
|
for (const auto& dayTimeGS : dayTimeGoldSkulltulas) {
|
|
if (IS_DAY && dayTimeGS.forChild == LINK_IS_CHILD && dayTimeGS.scene == gPlayState->sceneNum &&
|
|
dayTimeGS.room == gPlayState->roomCtx.curRoom.num) {
|
|
for (const auto& actorEntry : dayTimeGS.actorEntries) {
|
|
Actor_Spawn(&gPlayState->actorCtx, gPlayState, actorEntry.id, actorEntry.pos.x, actorEntry.pos.y,
|
|
actorEntry.pos.z, actorEntry.rot.x, actorEntry.rot.y, actorEntry.rot.z,
|
|
actorEntry.params, false);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
bool IsHyperBossesActive() {
|
|
return CVarGetInteger(CVAR_ENHANCEMENT("HyperBosses"), 0) ||
|
|
(IS_BOSS_RUSH && gSaveContext.bossRushOptions[BR_OPTIONS_HYPERBOSSES] == BR_CHOICE_HYPERBOSSES_YES);
|
|
}
|
|
|
|
void UpdateHyperBossesState() {
|
|
static uint32_t actorUpdateHookId = 0;
|
|
if (actorUpdateHookId != 0) {
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(actorUpdateHookId);
|
|
actorUpdateHookId = 0;
|
|
}
|
|
|
|
if (IsHyperBossesActive()) {
|
|
actorUpdateHookId = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* refActor) {
|
|
// Run the update function a second time to make bosses move and act twice as fast.
|
|
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
|
|
uint8_t isBossActor =
|
|
actor->id == ACTOR_BOSS_GOMA || // Gohma
|
|
actor->id == ACTOR_BOSS_DODONGO || // King Dodongo
|
|
actor->id == ACTOR_EN_BDFIRE || // King Dodongo Fire Breath
|
|
actor->id == ACTOR_BOSS_VA || // Barinade
|
|
actor->id == ACTOR_BOSS_GANONDROF || // Phantom Ganon
|
|
actor->id == ACTOR_EN_FHG_FIRE || // Phantom Ganon/Ganondorf Energy Ball/Thunder
|
|
actor->id == ACTOR_EN_FHG || // Phantom Ganon's Horse
|
|
actor->id == ACTOR_BOSS_FD || actor->id == ACTOR_BOSS_FD2 || // Volvagia (grounded/flying)
|
|
actor->id == ACTOR_EN_VB_BALL || // Volvagia Rocks
|
|
actor->id == ACTOR_BOSS_MO || // Morpha
|
|
actor->id == ACTOR_BOSS_SST || // Bongo Bongo
|
|
actor->id == ACTOR_BOSS_TW || // Twinrova
|
|
actor->id == ACTOR_BOSS_GANON || // Ganondorf
|
|
actor->id == ACTOR_BOSS_GANON2; // Ganon
|
|
|
|
// Don't apply during cutscenes because it causes weird behaviour and/or crashes on some bosses.
|
|
if (IsHyperBossesActive() && isBossActor && !Player_InBlockingCsMode(gPlayState, player)) {
|
|
// Barinade needs to be updated in sequence to avoid unintended behaviour.
|
|
if (actor->id == ACTOR_BOSS_VA) {
|
|
// params -1 is BOSSVA_BODY
|
|
if (actor->params == -1) {
|
|
Actor* actorList = gPlayState->actorCtx.actorLists[ACTORCAT_BOSS].head;
|
|
while (actorList != NULL) {
|
|
GameInteractor::RawAction::UpdateActor(actorList);
|
|
actorList = actorList->next;
|
|
}
|
|
}
|
|
} else {
|
|
GameInteractor::RawAction::UpdateActor(actor);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void RegisterHyperBosses() {
|
|
UpdateHyperBossesState();
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int16_t fileNum) {
|
|
UpdateHyperBossesState();
|
|
});
|
|
}
|
|
|
|
void UpdateHyperEnemiesState() {
|
|
static uint32_t actorUpdateHookId = 0;
|
|
if (actorUpdateHookId != 0) {
|
|
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(actorUpdateHookId);
|
|
actorUpdateHookId = 0;
|
|
}
|
|
|
|
if (CVarGetInteger(CVAR_ENHANCEMENT("HyperEnemies"), 0)) {
|
|
actorUpdateHookId = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* refActor) {
|
|
// Run the update function a second time to make enemies and minibosses move and act twice as fast.
|
|
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
|
|
// Some enemies are not in the ACTORCAT_ENEMY category, and some are that aren't really enemies.
|
|
bool isEnemy = actor->category == ACTORCAT_ENEMY || actor->id == ACTOR_EN_TORCH2;
|
|
bool isExcludedEnemy = actor->id == ACTOR_EN_FIRE_ROCK || actor->id == ACTOR_EN_ENCOUNT2;
|
|
|
|
// Don't apply during cutscenes because it causes weird behaviour and/or crashes on some cutscenes.
|
|
if (CVarGetInteger(CVAR_ENHANCEMENT("HyperEnemies"), 0) && isEnemy && !isExcludedEnemy &&
|
|
!Player_InBlockingCsMode(gPlayState, player)) {
|
|
GameInteractor::RawAction::UpdateActor(actor);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void RegisterBonkDamage() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerBonk>([]() {
|
|
uint8_t bonkOption = CVarGetInteger(CVAR_ENHANCEMENT("BonkDamageMult"), BONK_DAMAGE_NONE);
|
|
if (bonkOption == BONK_DAMAGE_NONE) {
|
|
return;
|
|
}
|
|
|
|
if (bonkOption == BONK_DAMAGE_OHKO) {
|
|
gSaveContext.health = 0;
|
|
return;
|
|
}
|
|
|
|
uint16_t bonkDamage = 0;
|
|
switch (bonkOption) {
|
|
case BONK_DAMAGE_QUARTER_HEART:
|
|
bonkDamage = 4;
|
|
break;
|
|
case BONK_DAMAGE_HALF_HEART:
|
|
bonkDamage = 8;
|
|
break;
|
|
case BONK_DAMAGE_1_HEART:
|
|
bonkDamage = 16;
|
|
break;
|
|
case BONK_DAMAGE_2_HEARTS:
|
|
bonkDamage = 32;
|
|
break;
|
|
case BONK_DAMAGE_4_HEARTS:
|
|
bonkDamage = 64;
|
|
break;
|
|
case BONK_DAMAGE_8_HEARTS:
|
|
bonkDamage = 128;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Health_ChangeBy(gPlayState, -bonkDamage);
|
|
// Set invincibility to make Link flash red as a visual damage indicator.
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
player->invincibilityTimer = 28;
|
|
});
|
|
}
|
|
|
|
void UpdateDirtPathFixState(int32_t sceneNum) {
|
|
switch (sceneNum) {
|
|
case SCENE_HYRULE_FIELD:
|
|
case SCENE_KOKIRI_FOREST:
|
|
case SCENE_HYRULE_CASTLE:
|
|
CVarSetInteger(CVAR_Z_FIGHTING_MODE, CVarGetInteger(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix"), ZFIGHT_FIX_DISABLED));
|
|
return;
|
|
default:
|
|
CVarClear(CVAR_Z_FIGHTING_MODE);
|
|
}
|
|
}
|
|
|
|
void RegisterMenuPathFix() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnTransitionEnd>([](int32_t sceneNum) {
|
|
UpdateDirtPathFixState(sceneNum);
|
|
});
|
|
}
|
|
|
|
void UpdateMirrorModeState(int32_t sceneNum) {
|
|
static bool prevMirroredWorld = false;
|
|
bool nextMirroredWorld = false;
|
|
|
|
int16_t mirroredMode = CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorldMode"), MIRRORED_WORLD_OFF);
|
|
int16_t inDungeon = (sceneNum >= SCENE_DEKU_TREE && sceneNum <= SCENE_INSIDE_GANONS_CASTLE_COLLAPSE && sceneNum != SCENE_THIEVES_HIDEOUT) ||
|
|
(sceneNum >= SCENE_DEKU_TREE_BOSS && sceneNum <= SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR) ||
|
|
(sceneNum == SCENE_GANON_BOSS);
|
|
|
|
if (mirroredMode == MIRRORED_WORLD_RANDOM_SEEDED || mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED) {
|
|
uint32_t seed = sceneNum + (IS_RANDO ? Rando::Context::GetInstance()->GetSettings()->GetSeed()
|
|
: gSaveContext.sohStats.fileCreatedAt);
|
|
Random_Init(seed);
|
|
}
|
|
|
|
bool randomMirror = Random(0, 2) == 1;
|
|
|
|
if (
|
|
mirroredMode == MIRRORED_WORLD_ALWAYS ||
|
|
((mirroredMode == MIRRORED_WORLD_RANDOM || mirroredMode == MIRRORED_WORLD_RANDOM_SEEDED) && randomMirror) ||
|
|
// Dungeon modes
|
|
(inDungeon && (mirroredMode == MIRRORED_WORLD_DUNGEONS_All ||
|
|
(mirroredMode == MIRRORED_WORLD_DUNGEONS_VANILLA && !ResourceMgr_IsSceneMasterQuest(sceneNum)) ||
|
|
(mirroredMode == MIRRORED_WORLD_DUNGEONS_MQ && ResourceMgr_IsSceneMasterQuest(sceneNum)) ||
|
|
((mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM || mirroredMode == MIRRORED_WORLD_DUNGEONS_RANDOM_SEEDED) && randomMirror)))
|
|
) {
|
|
nextMirroredWorld = true;
|
|
CVarSetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 1);
|
|
} else {
|
|
nextMirroredWorld = false;
|
|
CVarClear(CVAR_ENHANCEMENT("MirroredWorld"));
|
|
}
|
|
|
|
if (prevMirroredWorld != nextMirroredWorld) {
|
|
prevMirroredWorld = nextMirroredWorld;
|
|
ApplyMirrorWorldGfxPatches();
|
|
}
|
|
}
|
|
|
|
void RegisterMirrorModeHandler() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int32_t sceneNum) {
|
|
UpdateMirrorModeState(sceneNum);
|
|
});
|
|
}
|
|
|
|
void UpdatePatchHand() {
|
|
if ((CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && LINK_IS_CHILD) {
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1", 92, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2", 93, gsSPEndDisplayList());
|
|
ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1", 84, gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2", 85, gsSPEndDisplayList());
|
|
ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1", 51, gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2", 52, gsSPEndDisplayList());
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1", 104, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2", 105, gsSPEndDisplayList());
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1", 79, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2", 80, gsSPEndDisplayList());
|
|
ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1", 76, gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2", 77, gsSPEndDisplayList());
|
|
|
|
} else {
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2");
|
|
}
|
|
if ((CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && LINK_IS_ADULT) {
|
|
ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword", 13, gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot", 13, gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang", 50, gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield", 49, gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL));
|
|
} else {
|
|
ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword");
|
|
ResourceMgr_UnpatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot");
|
|
ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang");
|
|
ResourceMgr_UnpatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield");
|
|
}
|
|
if (CVarGetInteger("gEnhancements.FixHammerHand", 0) && LINK_IS_ADULT) {
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand1", 92, gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL));
|
|
ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand2", 93, gsSPEndDisplayList());
|
|
} else {
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand1");
|
|
ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand2");
|
|
}
|
|
}
|
|
|
|
void RegisterPatchHandHandler() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int32_t sceneNum) {
|
|
UpdatePatchHand();
|
|
});
|
|
}
|
|
|
|
void RegisterResetNaviTimer() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int32_t sceneNum) {
|
|
if (CVarGetInteger(CVAR_ENHANCEMENT("ResetNaviTimer"), 0)) {
|
|
gSaveContext.naviTimer = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
//this map is used for enemies that can be uniquely identified by their id
|
|
//and that are always counted
|
|
//enemies that can't be uniquely identified by their id
|
|
//or only sometimes count (like ACTOR_EN_TP)
|
|
//have to be manually handled in RegisterEnemyDefeatCounts
|
|
static std::unordered_map<u16, u16> uniqueEnemyIdToStatCount = {
|
|
{ ACTOR_EN_ANUBICE, COUNT_ENEMIES_DEFEATED_ANUBIS },
|
|
{ ACTOR_EN_AM, COUNT_ENEMIES_DEFEATED_ARMOS },
|
|
{ ACTOR_EN_CLEAR_TAG, COUNT_ENEMIES_DEFEATED_ARWING },
|
|
{ ACTOR_EN_VALI, COUNT_ENEMIES_DEFEATED_BARI },
|
|
{ ACTOR_EN_VM, COUNT_ENEMIES_DEFEATED_BEAMOS },
|
|
{ ACTOR_EN_BIGOKUTA, COUNT_ENEMIES_DEFEATED_BIG_OCTO },
|
|
{ ACTOR_EN_BILI, COUNT_ENEMIES_DEFEATED_BIRI },
|
|
{ ACTOR_EN_DNS, COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB },
|
|
{ ACTOR_EN_TORCH, COUNT_ENEMIES_DEFEATED_DARK_LINK },
|
|
{ ACTOR_EN_DH, COUNT_ENEMIES_DEFEATED_DEAD_HAND },
|
|
{ ACTOR_EN_HINTNUTS, COUNT_ENEMIES_DEFEATED_DEKU_SCRUB },
|
|
{ ACTOR_EN_DODONGO, COUNT_ENEMIES_DEFEATED_DODONGO },
|
|
{ ACTOR_EN_DODOJR, COUNT_ENEMIES_DEFEATED_DODONGO_BABY },
|
|
{ ACTOR_DOOR_KILLER, COUNT_ENEMIES_DEFEATED_DOOR_TRAP },
|
|
{ ACTOR_EN_FD, COUNT_ENEMIES_DEFEATED_FLARE_DANCER },
|
|
{ ACTOR_EN_FLOORMAS, COUNT_ENEMIES_DEFEATED_FLOORMASTER },
|
|
{ ACTOR_EN_TUBO_TRAP, COUNT_ENEMIES_DEFEATED_FLYING_POT },
|
|
{ ACTOR_EN_YUKABYUN, COUNT_ENEMIES_DEFEATED_FLOOR_TILE },
|
|
{ ACTOR_EN_FZ, COUNT_ENEMIES_DEFEATED_FREEZARD },
|
|
{ ACTOR_EN_GELDB, COUNT_ENEMIES_DEFEATED_GERUDO_THIEF },
|
|
{ ACTOR_EN_GOMA, COUNT_ENEMIES_DEFEATED_GOHMA_LARVA },
|
|
{ ACTOR_EN_CROW, COUNT_ENEMIES_DEFEATED_GUAY },
|
|
{ ACTOR_EN_RR, COUNT_ENEMIES_DEFEATED_LIKE_LIKE },
|
|
{ ACTOR_EN_DEKUNUTS, COUNT_ENEMIES_DEFEATED_MAD_SCRUB },
|
|
{ ACTOR_EN_OKUTA, COUNT_ENEMIES_DEFEATED_OCTOROK },
|
|
{ ACTOR_EN_BA, COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE },
|
|
{ ACTOR_EN_PO_SISTERS, COUNT_ENEMIES_DEFEATED_POE_SISTERS },
|
|
{ ACTOR_EN_BUBBLE, COUNT_ENEMIES_DEFEATED_SHABOM },
|
|
{ ACTOR_EN_SB, COUNT_ENEMIES_DEFEATED_SHELLBLADE },
|
|
{ ACTOR_EN_SKJ, COUNT_ENEMIES_DEFEATED_SKULL_KID },
|
|
{ ACTOR_EN_NY, COUNT_ENEMIES_DEFEATED_SPIKE },
|
|
{ ACTOR_EN_SKB, COUNT_ENEMIES_DEFEATED_STALCHILD },
|
|
{ ACTOR_EN_TEST, COUNT_ENEMIES_DEFEATED_STALFOS },
|
|
{ ACTOR_EN_WEIYER, COUNT_ENEMIES_DEFEATED_STINGER },
|
|
{ ACTOR_EN_BW, COUNT_ENEMIES_DEFEATED_TORCH_SLUG },
|
|
{ ACTOR_EN_WALLMAS, COUNT_ENEMIES_DEFEATED_WALLMASTER },
|
|
{ ACTOR_EN_KAREBABA, COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA },
|
|
};
|
|
|
|
void RegisterEnemyDefeatCounts() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnEnemyDefeat>([](void* refActor) {
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
if (uniqueEnemyIdToStatCount.contains(actor->id)) {
|
|
gSaveContext.sohStats.count[uniqueEnemyIdToStatCount[actor->id]]++;
|
|
} else {
|
|
switch (actor->id) {
|
|
case ACTOR_EN_BB:
|
|
if (actor->params == ENBB_GREEN || actor->params == ENBB_GREEN_BIG) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN]++;
|
|
} else if (actor->params == ENBB_BLUE) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE]++;
|
|
} else if (actor->params == ENBB_WHITE) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE]++;
|
|
} else if (actor->params == ENBB_RED) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_RED]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_DEKUBABA:
|
|
if (actor->params == DEKUBABA_BIG) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_ZF:
|
|
if (actor->params == ENZF_TYPE_DINOLFOS) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DINOLFOS]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIZALFOS]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_RD:
|
|
if (actor->params >= -1) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_REDEAD]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GIBDO]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_IK:
|
|
if (actor->params == 0) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_FIREFLY:
|
|
if (actor->params == KEESE_NORMAL_FLY || actor->params == KEESE_NORMAL_PERCH) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE]++;
|
|
} else if (actor->params == KEESE_FIRE_FLY || actor->params == KEESE_FIRE_PERCH) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_FIRE]++;
|
|
} else if (actor->params == KEESE_ICE_FLY) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_ICE]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_REEBA:
|
|
{
|
|
EnReeba* reeba = (EnReeba*)actor;
|
|
if (reeba->isBig) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER_BIG]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER]++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_MB:
|
|
if (actor->params == 0) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_PEEHAT:
|
|
if (actor->params == PEAHAT_TYPE_LARVA) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_POH:
|
|
if (actor->params == EN_POH_FLAT || actor->params == EN_POH_SHARP) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_COMPOSER]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_PO_FIELD:
|
|
if (actor->params == EN_PO_FIELD_BIG) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_BIG]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_ST:
|
|
if (actor->params == 1) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_SW:
|
|
if (((actor->params & 0xE000) >> 0xD) != 0) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLWALLTULA]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_TP:
|
|
if (actor->params == TAILPASARAN_HEAD) { // Only count the head, otherwise each body segment will increment
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TAILPASARAN]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_TITE:
|
|
if (actor->params == TEKTITE_BLUE) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]++;
|
|
}
|
|
break;
|
|
|
|
case ACTOR_EN_WF:
|
|
if (actor->params == WOLFOS_WHITE) {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE]++;
|
|
} else {
|
|
gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS]++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterBossDefeatTimestamps() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnBossDefeat>([](void* refActor) {
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
switch (actor->id) {
|
|
case ACTOR_BOSS_DODONGO:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_KING_DODONGO] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_FD2:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_VOLVAGIA] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_GANON:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANONDORF] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_GANON2:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
gSaveContext.sohStats.gameComplete = true;
|
|
break;
|
|
case ACTOR_BOSS_GANONDROF:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_GOMA:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GOHMA] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_MO:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_MORPHA] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_SST:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BONGO_BONGO] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_TW:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_TWINROVA] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
case ACTOR_BOSS_VA:
|
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BARINADE] = GAMEPLAYSTAT_TOTAL_TIME;
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
typedef enum {
|
|
ADD_ICE_TRAP,
|
|
ADD_BURN_TRAP,
|
|
ADD_SHOCK_TRAP,
|
|
ADD_KNOCK_TRAP,
|
|
ADD_SPEED_TRAP,
|
|
ADD_BOMB_TRAP,
|
|
ADD_VOID_TRAP,
|
|
ADD_AMMO_TRAP,
|
|
ADD_KILL_TRAP,
|
|
ADD_TELEPORT_TRAP,
|
|
ADD_TRAP_MAX
|
|
} AltTrapType;
|
|
|
|
const char* altTrapTypeCvars[] = {
|
|
CVAR_ENHANCEMENT("ExtraTraps.Ice"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Burn"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Shock"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Knockback"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Speed"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Bomb"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Void"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Ammo"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Kill"),
|
|
CVAR_ENHANCEMENT("ExtraTraps.Teleport")
|
|
};
|
|
|
|
std::vector<AltTrapType> getEnabledAddTraps () {
|
|
std::vector<AltTrapType> enabledAddTraps;
|
|
for (int i = 0; i < ADD_TRAP_MAX; i++) {
|
|
if (CVarGetInteger(altTrapTypeCvars[i], 0)) {
|
|
if (gSaveContext.equips.buttonItems[0] == ITEM_FISHING_POLE && (i == ADD_VOID_TRAP || i == ADD_TELEPORT_TRAP)) {
|
|
continue; // don't add void or teleport if you're holding the fishing pole, as this causes issues
|
|
}
|
|
enabledAddTraps.push_back(static_cast<AltTrapType>(i));
|
|
}
|
|
}
|
|
if (enabledAddTraps.size() == 0) {
|
|
enabledAddTraps.push_back(ADD_ICE_TRAP);
|
|
}
|
|
return enabledAddTraps;
|
|
};
|
|
|
|
void RegisterAltTrapTypes() {
|
|
static AltTrapType roll = ADD_TRAP_MAX;
|
|
static int statusTimer = -1;
|
|
static int eventTimer = -1;
|
|
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry itemEntry) {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) || itemEntry.modIndex != MOD_RANDOMIZER || itemEntry.getItemId != RG_ICE_TRAP) {
|
|
return;
|
|
}
|
|
roll = RandomElement(getEnabledAddTraps());
|
|
switch (roll) {
|
|
case ADD_ICE_TRAP:
|
|
GameInteractor::RawAction::FreezePlayer();
|
|
break;
|
|
case ADD_BURN_TRAP:
|
|
GameInteractor::RawAction::BurnPlayer();
|
|
break;
|
|
case ADD_SHOCK_TRAP:
|
|
GameInteractor::RawAction::ElectrocutePlayer();
|
|
break;
|
|
case ADD_KNOCK_TRAP:
|
|
eventTimer = 3;
|
|
break;
|
|
case ADD_SPEED_TRAP:
|
|
Audio_PlaySoundGeneral(NA_SE_VO_KZ_MOVE, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
|
GameInteractor::State::RunSpeedModifier = -2;
|
|
statusTimer = 200;
|
|
Overlay_DisplayText(10, "Speed Decreased!");
|
|
break;
|
|
case ADD_BOMB_TRAP:
|
|
eventTimer = 3;
|
|
break;
|
|
case ADD_VOID_TRAP:
|
|
Audio_PlaySoundGeneral(NA_SE_EN_GANON_LAUGH, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
|
eventTimer = 3;
|
|
break;
|
|
case ADD_AMMO_TRAP:
|
|
eventTimer = 3;
|
|
Overlay_DisplayText(5, "Ammo Halved!");
|
|
break;
|
|
case ADD_KILL_TRAP:
|
|
GameInteractor::RawAction::SetPlayerHealth(0);
|
|
break;
|
|
case ADD_TELEPORT_TRAP:
|
|
eventTimer = 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>([]() {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
if (statusTimer == 0) {
|
|
GameInteractor::State::RunSpeedModifier = 0;
|
|
}
|
|
if (eventTimer == 0) {
|
|
switch (roll) {
|
|
case ADD_KNOCK_TRAP:
|
|
GameInteractor::RawAction::KnockbackPlayer(1);
|
|
break;
|
|
case ADD_BOMB_TRAP:
|
|
GameInteractor::RawAction::SpawnActor(ACTOR_EN_BOM, 1);
|
|
break;
|
|
case ADD_VOID_TRAP:
|
|
Play_TriggerRespawn(gPlayState);
|
|
break;
|
|
case ADD_AMMO_TRAP:
|
|
AMMO(ITEM_STICK) = AMMO(ITEM_STICK) * 0.5;
|
|
AMMO(ITEM_NUT) = AMMO(ITEM_NUT) * 0.5;
|
|
AMMO(ITEM_SLINGSHOT) = AMMO(ITEM_SLINGSHOT) * 0.5;
|
|
AMMO(ITEM_BOW) = AMMO(ITEM_BOW) * 0.5;
|
|
AMMO(ITEM_BOMB) = AMMO(ITEM_BOMB) * 0.5;
|
|
AMMO(ITEM_BOMBCHU) = AMMO(ITEM_BOMBCHU) * 0.5;
|
|
Audio_PlaySoundGeneral(NA_SE_VO_FR_SMILE_0, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
|
|
break;
|
|
case ADD_TELEPORT_TRAP: {
|
|
int entrance;
|
|
int index = 1 + rand() % 10;
|
|
switch (index) {
|
|
case 1:
|
|
entrance = GI_TP_DEST_SERENADE;
|
|
break;
|
|
case 2:
|
|
entrance = GI_TP_DEST_REQUIEM;
|
|
break;
|
|
case 3:
|
|
entrance = GI_TP_DEST_BOLERO;
|
|
break;
|
|
case 4:
|
|
entrance = GI_TP_DEST_MINUET;
|
|
break;
|
|
case 5:
|
|
entrance = GI_TP_DEST_NOCTURNE;
|
|
break;
|
|
case 6:
|
|
entrance = GI_TP_DEST_PRELUDE;
|
|
break;
|
|
default:
|
|
entrance = GI_TP_DEST_LINKSHOUSE;
|
|
break;
|
|
}
|
|
GameInteractor::RawAction::TeleportPlayer(entrance);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
statusTimer--;
|
|
eventTimer--;
|
|
});
|
|
}
|
|
|
|
void UpdateHurtContainerModeState(bool newState) {
|
|
static bool hurtEnabled = false;
|
|
if (hurtEnabled == newState) {
|
|
return;
|
|
}
|
|
|
|
hurtEnabled = newState;
|
|
uint16_t getHeartPieces = gSaveContext.sohStats.heartPieces / 4;
|
|
uint16_t getHeartContainers = gSaveContext.sohStats.heartContainers;
|
|
|
|
if (hurtEnabled) {
|
|
gSaveContext.healthCapacity = 320 - ((getHeartPieces + getHeartContainers) * 16);
|
|
} else {
|
|
gSaveContext.healthCapacity = 48 + ((getHeartPieces + getHeartContainers) * 16);
|
|
}
|
|
}
|
|
|
|
void RegisterHurtContainerModeHandler() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int32_t fileNum) {
|
|
UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0));
|
|
});
|
|
}
|
|
|
|
void RegisterRandomizedEnemySizes() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
|
|
// Randomized Enemy Sizes
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
|
|
// Exclude wobbly platforms in Jabu because they need to act like platforms.
|
|
// Exclude Dead Hand hands and Bongo Bongo main body because they make the fights (near) impossible.
|
|
uint8_t excludedEnemy = actor->id == ACTOR_EN_BROB || actor->id == ACTOR_EN_DHA || (actor->id == ACTOR_BOSS_SST && actor->params == -1);
|
|
|
|
// Dodongo, Volvagia and Dead Hand are always smaller because they're impossible when bigger.
|
|
uint8_t smallOnlyEnemy = actor->id == ACTOR_BOSS_DODONGO || actor->id == ACTOR_BOSS_FD ||
|
|
actor->id == ACTOR_BOSS_FD2 || actor->id == ACTOR_EN_DH;
|
|
|
|
// Only apply to enemies and bosses.
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemySizes"), 0) || (actor->category != ACTORCAT_ENEMY && actor->category != ACTORCAT_BOSS) || excludedEnemy) {
|
|
return;
|
|
}
|
|
|
|
float randomNumber;
|
|
float randomScale;
|
|
|
|
uint8_t bigActor = rand() % 2;
|
|
|
|
// Big actor
|
|
if (bigActor && !smallOnlyEnemy) {
|
|
randomNumber = rand() % 200;
|
|
// Between 100% and 300% size.
|
|
randomScale = 1.0f + (randomNumber / 100);
|
|
// Small actor
|
|
} else {
|
|
randomNumber = rand() % 90;
|
|
// Between 10% and 100% size.
|
|
randomScale = 0.1f + (randomNumber / 100);
|
|
}
|
|
|
|
Actor_SetScale(actor, actor->scale.z * randomScale);
|
|
|
|
if (CVarGetInteger(CVAR_ENHANCEMENT("EnemySizeScalesHealth"), 0) && (actor->category == ACTORCAT_ENEMY)) {
|
|
// Scale the health based on a smaller factor than randomScale
|
|
float healthScalingFactor = 0.8f; // Adjust this factor as needed
|
|
float scaledHealth = actor->colChkInfo.health * (randomScale * healthScalingFactor);
|
|
|
|
// Ensure the scaled health doesn't go below zero
|
|
actor->colChkInfo.health = fmax(scaledHealth, 1.0f);
|
|
} else {
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterOpenAllHours() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
|
|
if (CVarGetInteger(CVAR_ENHANCEMENT("OpenAllHours"), 0) && (actor->id == ACTOR_EN_DOOR)) {
|
|
switch (actor->params) {
|
|
case 4753: // Night Market Bazaar
|
|
case 1678: // Night Potion Shop
|
|
case 2689: // Day Bombchu Shop
|
|
case 2703: // Night Slingshot Game
|
|
case 653: // Day Chest Game
|
|
case 6801: // Night Kak Bazaar
|
|
case 7822: // Night Kak Potion Shop
|
|
case 4751: // Night Kak Archery Game
|
|
case 3728: // Night Mask Shop
|
|
{
|
|
actor->params = (actor->params & 0xFC00) | (DOOR_SCENEEXIT << 7) | 0x3F;
|
|
EnDoor* enDoor = static_cast<EnDoor*>(refActor);
|
|
EnDoor_SetupType(enDoor, gPlayState);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void PatchToTMedallions() {
|
|
// TODO: Refactor the DemoEffect_UpdateJewelAdult and DemoEffect_UpdateJewelChild from z_demo_effect
|
|
// effects to take effect in there
|
|
if (CVarGetInteger(CVAR_ENHANCEMENT("ToTMedallionsColors"), 0)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_StartGrayscale", 7, gsSPGrayscale(true));
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_StartGrayscale", 7, gsSPGrayscale(true));
|
|
|
|
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_WATER)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeBlue", 16, gsDPSetGrayscaleColor(0, 161, 255, 255));
|
|
} else {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeBlue", 16, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
}
|
|
|
|
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SPIRIT)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeOrange", 45, gsDPSetGrayscaleColor(255, 135, 0, 255));
|
|
} else {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeOrange", 45, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
}
|
|
|
|
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_LIGHT)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeYellow", 69, gsDPSetGrayscaleColor(255, 255, 0, 255));
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakeYellow", 16, gsDPSetGrayscaleColor(255, 255, 0, 255));
|
|
} else {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeYellow", 69, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakeYellow", 16, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
}
|
|
|
|
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_FOREST)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeGreen", 94, gsDPSetGrayscaleColor(0, 255, 0, 255));
|
|
} else {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeGreen", 94, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
}
|
|
|
|
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_FIRE)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeRed", 118, gsDPSetGrayscaleColor(255, 0, 0, 255));
|
|
} else {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeRed", 118, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
}
|
|
|
|
if (CHECK_QUEST_ITEM(QUEST_MEDALLION_SHADOW)) {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakePurple", 142, gsDPSetGrayscaleColor(212, 0, 255, 255));
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakePurple", 27, gsDPSetGrayscaleColor(212, 0, 255, 255));
|
|
} else {
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakePurple", 142, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakePurple", 27, gsDPSetGrayscaleColor(255, 255, 255, 255));
|
|
}
|
|
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_EndGrayscaleAndEndDlist", 160, gsSPBranchListOTRFilePath(gEndGrayscaleAndEndDlistDL));
|
|
ResourceMgr_PatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_EndGrayscaleAndEndDlist", 51, gsSPBranchListOTRFilePath(gEndGrayscaleAndEndDlistDL));
|
|
} else {
|
|
// Unpatch everything
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_StartGrayscale");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_StartGrayscale");
|
|
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeBlue");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeOrange");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeYellow");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakeYellow");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakeRed");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_MakePurple");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_MakePurple");
|
|
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007A70, "ToTMedallions_EndGrayscaleAndEndDlist");
|
|
ResourceMgr_UnpatchGfxByName(tokinoma_room_0DL_007FD0, "ToTMedallions_2_EndGrayscaleAndEndDlist");
|
|
}
|
|
}
|
|
|
|
void RegisterToTMedallions() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnItemReceive>([](GetItemEntry _unused) {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("ToTMedallionsColors"), 0) || !gPlayState || gPlayState->sceneNum != SCENE_TEMPLE_OF_TIME) {
|
|
return;
|
|
}
|
|
PatchToTMedallions();
|
|
});
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) {
|
|
if (!CVarGetInteger(CVAR_ENHANCEMENT("ToTMedallionsColors"), 0) || sceneNum != SCENE_TEMPLE_OF_TIME) {
|
|
return;
|
|
}
|
|
PatchToTMedallions();
|
|
});
|
|
}
|
|
|
|
|
|
void RegisterFloorSwitchesHook() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
|
|
Actor* actor = static_cast<Actor*>(refActor);
|
|
if (actor->id != ACTOR_OBJ_SWITCH || !CVarGetInteger(CVAR_ENHANCEMENT("FixFloorSwitches"), 0)) {
|
|
return;
|
|
}
|
|
|
|
ObjSwitch* switchActor = reinterpret_cast<ObjSwitch*>(actor);
|
|
s32 type = (switchActor->dyna.actor.params & 7);
|
|
|
|
if (switchActor->dyna.actor.params == 0x1200 || switchActor->dyna.actor.params == 0x3A00) {
|
|
switchActor->dyna.actor.world.pos.y -= 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
void RegisterPauseMenuHooks() {
|
|
static bool pauseWarpHooksRegistered = false;
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([&]() {
|
|
if (!GameInteractor::IsSaveLoaded() || !CVarGetInteger(CVAR_ENHANCEMENT("PauseWarp"), 0)) {
|
|
pauseWarpHooksRegistered = false;
|
|
return;
|
|
}
|
|
if (!pauseWarpHooksRegistered) {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnKaleidoUpdate>([]() {PauseWarp_HandleSelection();});
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>([]() {
|
|
PauseWarp_Execute();
|
|
});
|
|
pauseWarpHooksRegistered = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
extern "C" u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey);
|
|
|
|
void PatchCompasses() {
|
|
s8 compassesCanBeOutsideDungeon = IS_RANDO && DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS);
|
|
s8 isColoredCompassesEnabled = compassesCanBeOutsideDungeon && CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MatchCompassColors"), 1);
|
|
if (isColoredCompassesEnabled) {
|
|
ResourceMgr_PatchGfxByName(gGiCompassDL, "Compass_PrimColor", 5, gsDPNoOp());
|
|
ResourceMgr_PatchGfxByName(gGiCompassDL, "Compass_EnvColor", 6, gsDPNoOp());
|
|
} else {
|
|
ResourceMgr_UnpatchGfxByName(gGiCompassDL, "Compass_PrimColor");
|
|
ResourceMgr_UnpatchGfxByName(gGiCompassDL, "Compass_EnvColor");
|
|
}
|
|
}
|
|
|
|
void RegisterRandomizerCompasses() {
|
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadFile>([](int32_t _unused) {
|
|
PatchCompasses();
|
|
});
|
|
}
|
|
|
|
void InitMods() {
|
|
BossRush_RegisterHooks();
|
|
RandomizerRegisterHooks();
|
|
TimeSaverRegisterHooks();
|
|
CheatsRegisterHooks();
|
|
TimeSavers_Register();
|
|
RegisterTTS();
|
|
RegisterInfiniteMoney();
|
|
RegisterInfiniteHealth();
|
|
RegisterInfiniteAmmo();
|
|
RegisterInfiniteMagic();
|
|
RegisterInfiniteNayrusLove();
|
|
RegisterMoonJumpOnL();
|
|
RegisterInfiniteISG();
|
|
RegisterEzQPA();
|
|
RegisterUnrestrictedItems();
|
|
RegisterFreezeTime();
|
|
RegisterOcarinaTimeTravel();
|
|
RegisterAutoSave();
|
|
RegisterDaytimeGoldSkultullas();
|
|
RegisterRupeeDash();
|
|
RegisterShadowTag();
|
|
RegisterPermanentHeartLoss();
|
|
RegisterDeleteFileOnDeath();
|
|
RegisterHyperBosses();
|
|
UpdateHyperEnemiesState();
|
|
RegisterBonkDamage();
|
|
RegisterMenuPathFix();
|
|
RegisterMirrorModeHandler();
|
|
RegisterResetNaviTimer();
|
|
RegisterEnemyDefeatCounts();
|
|
RegisterBossDefeatTimestamps();
|
|
RegisterAltTrapTypes();
|
|
RegisterRandomizedEnemySizes();
|
|
RegisterOpenAllHours();
|
|
RegisterToTMedallions();
|
|
RegisterRandomizerCompasses();
|
|
NameTag_RegisterHooks();
|
|
RegisterFloorSwitchesHook();
|
|
RegisterPatchHandHandler();
|
|
RegisterHurtContainerModeHandler();
|
|
RegisterPauseMenuHooks();
|
|
RandoKaleido_RegisterHooks();
|
|
}
|