mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-01-30 23:10:14 -05:00
Support hook unregistration (#3538)
This commit is contained in:
parent
bdfcf39e56
commit
a84227cbbb
@ -163,11 +163,30 @@ public:
|
||||
static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect);
|
||||
|
||||
// Game Hooks
|
||||
template <typename H> struct RegisteredGameHooks { inline static std::vector<typename H::fn> functions; };
|
||||
template <typename H> void RegisterGameHook(typename H::fn h) { RegisteredGameHooks<H>::functions.push_back(h); }
|
||||
uint32_t nextHookId = 1;
|
||||
template <typename H> struct RegisteredGameHooks { inline static std::unordered_map<uint32_t, typename H::fn> functions; };
|
||||
template <typename H> struct HooksToUnregister { inline static std::vector<uint32_t> hooks; };
|
||||
template <typename H> uint32_t RegisterGameHook(typename H::fn h) {
|
||||
// Ensure hook id is unique and not 0, which is reserved for invalid hooks
|
||||
if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1;
|
||||
while (RegisteredGameHooks<H>::functions.find(this->nextHookId) != RegisteredGameHooks<H>::functions.end()) {
|
||||
this->nextHookId++;
|
||||
}
|
||||
|
||||
RegisteredGameHooks<H>::functions[this->nextHookId] = h;
|
||||
return this->nextHookId++;
|
||||
}
|
||||
template <typename H> void UnregisterGameHook(uint32_t id) {
|
||||
HooksToUnregister<H>::hooks.push_back(id);
|
||||
}
|
||||
|
||||
template <typename H, typename... Args> void ExecuteHooks(Args&&... args) {
|
||||
for (auto& fn : RegisteredGameHooks<H>::functions) {
|
||||
fn(std::forward<Args>(args)...);
|
||||
for (auto& hookId : HooksToUnregister<H>::hooks) {
|
||||
RegisteredGameHooks<H>::functions.erase(hookId);
|
||||
}
|
||||
HooksToUnregister<H>::hooks.clear();
|
||||
for (auto& hook : RegisteredGameHooks<H>::functions) {
|
||||
hook.second(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,70 +519,93 @@ void RegisterDaytimeGoldSkultullas() {
|
||||
});
|
||||
}
|
||||
|
||||
void RegisterHyperBosses() {
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* refActor) {
|
||||
// Run the update function a second time to make bosses move and act twice as fast.
|
||||
bool IsHyperBossesActive() {
|
||||
return CVarGetInteger("gHyperBosses", 0) ||
|
||||
(IS_BOSS_RUSH && gSaveContext.bossRushOptions[BR_OPTIONS_HYPERBOSSES] == BR_CHOICE_HYPERBOSSES_YES);
|
||||
}
|
||||
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
Actor* actor = static_cast<Actor*>(refActor);
|
||||
void UpdateHyperBossesState() {
|
||||
static uint32_t actorUpdateHookId = 0;
|
||||
if (actorUpdateHookId != 0) {
|
||||
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(actorUpdateHookId);
|
||||
actorUpdateHookId = 0;
|
||||
}
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
uint8_t hyperBossesActive =
|
||||
CVarGetInteger("gHyperBosses", 0) ||
|
||||
(IS_BOSS_RUSH &&
|
||||
gSaveContext.bossRushOptions[BR_OPTIONS_HYPERBOSSES] == BR_CHOICE_HYPERBOSSES_YES);
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
Actor* actor = static_cast<Actor*>(refActor);
|
||||
|
||||
// Don't apply during cutscenes because it causes weird behaviour and/or crashes on some bosses.
|
||||
if (hyperBossesActive && 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;
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
GameInteractor::RawAction::UpdateActor(actor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterHyperBosses() {
|
||||
UpdateHyperBossesState();
|
||||
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>([](int16_t fileNum) {
|
||||
UpdateHyperBossesState();
|
||||
});
|
||||
}
|
||||
|
||||
void RegisterHyperEnemies() {
|
||||
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.
|
||||
void UpdateHyperEnemiesState() {
|
||||
static uint32_t actorUpdateHookId = 0;
|
||||
if (actorUpdateHookId != 0) {
|
||||
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorUpdate>(actorUpdateHookId);
|
||||
actorUpdateHookId = 0;
|
||||
}
|
||||
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
Actor* actor = static_cast<Actor*>(refActor);
|
||||
if (CVarGetInteger("gHyperEnemies", 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.
|
||||
|
||||
// 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;
|
||||
Player* player = GET_PLAYER(gPlayState);
|
||||
Actor* actor = static_cast<Actor*>(refActor);
|
||||
|
||||
// Don't apply during cutscenes because it causes weird behaviour and/or crashes on some cutscenes.
|
||||
if (CVarGetInteger("gHyperEnemies", 0) && isEnemy && !isExcludedEnemy &&
|
||||
!Player_InBlockingCsMode(gPlayState, player)) {
|
||||
GameInteractor::RawAction::UpdateActor(actor);
|
||||
}
|
||||
});
|
||||
// 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("gHyperEnemies", 0) && isEnemy && !isExcludedEnemy &&
|
||||
!Player_InBlockingCsMode(gPlayState, player)) {
|
||||
GameInteractor::RawAction::UpdateActor(actor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterBonkDamage() {
|
||||
@ -1371,7 +1394,7 @@ void InitMods() {
|
||||
RegisterPermanentHeartLoss();
|
||||
RegisterDeleteFileOnDeath();
|
||||
RegisterHyperBosses();
|
||||
RegisterHyperEnemies();
|
||||
UpdateHyperEnemiesState();
|
||||
RegisterBonkDamage();
|
||||
RegisterMenuPathFix();
|
||||
RegisterMirrorModeHandler();
|
||||
|
@ -12,6 +12,8 @@ void UpdateMirrorModeState(int32_t sceneNum);
|
||||
void UpdateHurtContainerModeState(bool newState);
|
||||
void PatchToTMedallions();
|
||||
void UpdatePermanentHeartLossState();
|
||||
void UpdateHyperEnemiesState();
|
||||
void UpdateHyperBossesState();
|
||||
void InitMods();
|
||||
void UpdatePatchHand();
|
||||
|
||||
|
@ -959,9 +959,13 @@ void DrawEnhancementsMenu() {
|
||||
UIWidgets::Tooltip("Bonking into trees will have a chance to drop up to 3 sticks. Must already have obtained sticks.");
|
||||
UIWidgets::PaddedEnhancementCheckbox("No Heart Drops", "gNoHeartDrops", true, false);
|
||||
UIWidgets::Tooltip("Disables heart drops, but not heart placements, like from a Deku Scrub running off\nThis simulates Hero Mode from other games in the series");
|
||||
UIWidgets::PaddedEnhancementCheckbox("Hyper Bosses", "gHyperBosses", true, false);
|
||||
if (UIWidgets::PaddedEnhancementCheckbox("Hyper Bosses", "gHyperBosses", true, false)) {
|
||||
UpdateHyperBossesState();
|
||||
}
|
||||
UIWidgets::Tooltip("All major bosses move and act twice as fast.");
|
||||
UIWidgets::PaddedEnhancementCheckbox("Hyper Enemies", "gHyperEnemies", true, false);
|
||||
if (UIWidgets::PaddedEnhancementCheckbox("Hyper Enemies", "gHyperEnemies", true, false)) {
|
||||
UpdateHyperEnemiesState();
|
||||
}
|
||||
UIWidgets::Tooltip("All regular enemies and mini-bosses move and act twice as fast.");
|
||||
UIWidgets::PaddedEnhancementCheckbox("Always Win Goron Pot", "gGoronPot", true, false);
|
||||
UIWidgets::Tooltip("Always get the heart piece/purple rupee from the spinning Goron pot");
|
||||
|
Loading…
Reference in New Issue
Block a user