summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPepe20129 <72659707+Pepe20129@users.noreply.github.com>2024-11-21 23:08:49 +0100
committerGitHub <noreply@github.com>2024-11-21 16:08:49 -0600
commit0f48970576bcad6f237fb2f576aaf2c59fc0b6d6 (patch)
treec4e97b02288792dcb8134a2b62187248eceeeb2f
parentd459043644f604be278938a3120e583b8ea3bbcc (diff)
Shiny enemies (#4570)
-rw-r--r--soh/include/z64actor.h1
-rw-r--r--soh/soh/Enhancements/Holiday/Pablo.cpp199
-rw-r--r--soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h1
-rw-r--r--soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp7
-rw-r--r--soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h1
-rw-r--r--soh/src/code/z_actor.c1
6 files changed, 210 insertions, 0 deletions
diff --git a/soh/include/z64actor.h b/soh/include/z64actor.h
index 156f1d679..08ca0032f 100644
--- a/soh/include/z64actor.h
+++ b/soh/include/z64actor.h
@@ -182,6 +182,7 @@ typedef struct Actor {
/* 0x13C */ char dbgPad[0x10]; // Padding that only exists in the debug rom
// #region SOH [General]
/* */ u8 maximumHealth; // Max health value for use with health bars, set on actor init
+ /* */ u8 isShiny;
// #endregion
} Actor; // size = 0x14C
diff --git a/soh/soh/Enhancements/Holiday/Pablo.cpp b/soh/soh/Enhancements/Holiday/Pablo.cpp
new file mode 100644
index 000000000..08173df86
--- /dev/null
+++ b/soh/soh/Enhancements/Holiday/Pablo.cpp
@@ -0,0 +1,199 @@
+#include "Holiday.hpp"
+#include "z64.h"
+#include "macros.h"
+#include "functions.h"
+#include "src/overlays/effects/ovl_Effect_Ss_En_Ice/z_eff_ss_en_ice.h"
+
+extern "C" {
+ extern PlayState* gPlayState;
+}
+
+#define AUTHOR "Pablo"
+#define CVAR(v) "gHoliday." AUTHOR "." v
+
+#pragma region Shiny
+
+static Vec3f shineSpots[12] = {
+ { 20.0f, 20.0f, 0.0f }, { 10.0f, 40.0f, 10.0f }, { -10.0f, 40.0f, 10.0f }, { -20.0f, 20.0f, 0.0f },
+ { 10.0f, 40.0f, -10.0f }, { -10.0f, 40.0f, -10.0f }, { 0.0f, 20.0f, -20.0f }, { 10.0f, 0.0f, 10.0f },
+ { 10.0f, 0.0f, -10.0f }, { 0.0f, 20.0f, 20.0f }, { -10.0f, 0.0f, 10.0f }, { -10.0f, 0.0f, -10.0f },
+};
+
+static u8 shinyableActorIds[51] = {
+ ACTOR_EN_WALLMAS,
+ ACTOR_EN_ZF,
+ ACTOR_EN_YUKABYUN,
+ ACTOR_EN_WF,
+ ACTOR_EN_WEIYER,
+ ACTOR_EN_VM,
+ ACTOR_EN_TUBO_TRAP,
+ ACTOR_EN_VALI,
+ ACTOR_EN_TP,
+ ACTOR_EN_TORCH2,
+ ACTOR_EN_TITE,
+ ACTOR_EN_TEST,
+ ACTOR_EN_SW,
+ ACTOR_EN_ST,
+ ACTOR_EN_SKB,
+ ACTOR_EN_SKJ,
+ ACTOR_EN_PEEHAT,
+ ACTOR_EN_SB,
+ ACTOR_EN_RR,
+ ACTOR_EN_REEBA,
+ ACTOR_EN_RD,
+ ACTOR_EN_PO_SISTERS,
+ ACTOR_EN_PO_FIELD,
+ ACTOR_EN_POH,
+ ACTOR_EN_KAREBABA,
+ ACTOR_EN_OKUTA,
+ ACTOR_EN_NY,
+ ACTOR_EN_MB,
+ ACTOR_EN_IK,
+ ACTOR_EN_GOMA,
+ ACTOR_EN_GELDB,
+ ACTOR_EN_FZ,
+ ACTOR_EN_FLOORMAS,
+ ACTOR_EN_FIREFLY,
+ ACTOR_EN_FD,
+ ACTOR_EN_EIYER,
+ ACTOR_EN_DODONGO,
+ ACTOR_EN_DODOJR,
+ ACTOR_EN_DH,
+ ACTOR_EN_DEKUBABA,
+ ACTOR_EN_CROW,
+ ACTOR_EN_CLEAR_TAG,
+ ACTOR_EN_BW,
+ ACTOR_EN_BUBBLE,
+ ACTOR_EN_AM,
+ ACTOR_EN_BILI,
+ ACTOR_EN_BIGOKUTA,
+ ACTOR_EN_BB,
+ ACTOR_EN_BA,
+ ACTOR_EN_ANUBICE,
+ ACTOR_DOOR_KILLER
+};
+
+u8 CanBeShiny(Actor* actor) {
+ for (u8 i = 0; i < ARRAY_COUNT(shinyableActorIds); i += 1) {
+ if (shinyableActorIds[i] == actor->id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ApplyShinyness(Actor* actor) {
+ if (!CanBeShiny(actor)) {
+ assert(false);
+ return;
+ }
+
+ actor->isShiny = true;
+ actor->colChkInfo.health *= 4;
+ actor->maximumHealth *= 4;
+ actor->scale.x *= 1.25f;
+ actor->scale.y *= 1.25f;
+ actor->scale.z *= 1.25f;
+}
+
+void RenderShines(Actor* actor) {
+ if (!CanBeShiny(actor) || !actor->isShiny) {
+ assert(false);
+ return;
+ }
+
+ if (gSaveContext.gameMode != GAMEMODE_NORMAL || gPlayState->pauseCtx.state != 0) {
+ return;
+ }
+
+ if (Rand_ZeroOne() < 0.1f) {
+ Vec3f shinePos;
+ s32 i = (s32)(Rand_ZeroOne() * ARRAY_COUNT(shineSpots));
+
+ shinePos.x = actor->world.pos.x + shineSpots[i].x;
+ shinePos.y = actor->world.pos.y + shineSpots[i].y;
+ shinePos.z = actor->world.pos.z + shineSpots[i].z;
+
+ EffectSsEnIceInitParams initParams;
+
+ initParams.actor = actor;
+ initParams.pos = shinePos;
+ initParams.type = 0;
+
+ // 50/50 chance of red or green
+ if (Rand_Next() % 2 == 0) {
+ initParams.primColor.r = 255;
+ initParams.primColor.g = 0;
+ initParams.envColor.r = 255;
+ initParams.envColor.g = 0;
+ } else {
+ initParams.primColor.r = 0;
+ initParams.primColor.g = 255;
+ initParams.envColor.r = 0;
+ initParams.envColor.g = 255;
+ }
+
+ initParams.primColor.b = 0;
+ initParams.primColor.a = 250;
+ initParams.envColor.b = 0;
+ initParams.scale = 0.5f;
+
+ EffectSs_Spawn(gPlayState, EFFECT_SS_EN_ICE, 80, &initParams);
+ }
+}
+
+void SpawnShinyReward(Actor* actor) {
+ if (!CanBeShiny(actor)) {
+ assert(false);
+ return;
+ }
+
+ for (u8 i = 0; i < 10; i += 1) {
+ Item_DropCollectible(gPlayState, &actor->world.pos, ITEM00_RUPEE_RED);
+ Item_DropCollectible(gPlayState, &actor->world.pos, ITEM00_RUPEE_GREEN);
+ }
+}
+
+void RegisterShiny() {
+ GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
+ Actor* actor = static_cast<Actor*>(refActor);
+ if (CVarGetInteger(CVAR("Shiny.Enabled"), 0) && CanBeShiny(actor) && Rand_ZeroOne() < (1.0f / (s32)CVarGetInteger(CVAR("Shiny.Chance"), 8192))) {
+ ApplyShinyness(actor);
+ }
+ });
+
+ GameInteractor::Instance->RegisterGameHook<GameInteractor::OnEnemyDefeat>([](void* refActor) {
+ Actor* actor = static_cast<Actor*>(refActor);
+ if (CVarGetInteger(CVAR("Shiny.Enabled"), 0) && actor->isShiny) {
+ SpawnShinyReward(actor);
+ }
+ });
+
+ GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorDraw>([](void* refActor) {
+ Actor* actor = static_cast<Actor*>(refActor);
+ if (CVarGetInteger(CVAR("Shiny.Enabled"), 0) && actor->isShiny) {
+ RenderShines(actor);
+ }
+ });
+}
+
+void ShinyDrawImGui() {
+ UIWidgets::PaddedEnhancementCheckbox("Enable Shiny Enemies", CVAR("Shiny.Enabled"), true, false);
+ UIWidgets::Tooltip("Allows enemies to be shiny.\nShiny enemies are 25% bigger and have 4 times the health but drop the equivalent of a gold rupee upon death");
+
+ UIWidgets::PaddedEnhancementSliderInt("Shiny Chance: %d", "##ShinyChance", CVAR("Shiny.Chance"), 1, 8192, "", 8192, true, true, false, false, "");
+ UIWidgets::Tooltip("The chance for an enemy to be shiny is 1 / Shiny Chance");
+}
+
+#pragma endregion
+
+static void DrawMenu() {
+ ImGui::SeparatorText(AUTHOR);
+ ShinyDrawImGui();
+}
+
+static void RegisterMod() {
+ RegisterShiny();
+}
+
+static Holiday holiday(DrawMenu, RegisterMod); \ No newline at end of file
diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h
index 06ef7cc21..989a56c75 100644
--- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h
+++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h
@@ -23,6 +23,7 @@ DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price));
DEFINE_HOOK(ShouldActorInit, (void* actor, bool* result));
DEFINE_HOOK(OnActorInit, (void* actor));
DEFINE_HOOK(OnActorUpdate, (void* actor));
+DEFINE_HOOK(OnActorDraw, (void* actor));
DEFINE_HOOK(OnActorKill, (void* actor));
DEFINE_HOOK(OnEnemyDefeat, (void* actor));
DEFINE_HOOK(OnBossDefeat, (void* actor));
diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
index a2235f7f0..f0de65364 100644
--- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
+++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp
@@ -95,6 +95,13 @@ void GameInteractor_ExecuteOnActorUpdate(void* actor) {
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorUpdate>(actor);
}
+void GameInteractor_ExecuteOnActorDraw(void* actor) {
+ GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorDraw>(actor);
+ GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorDraw>(((Actor*)actor)->id, actor);
+ GameInteractor::Instance->ExecuteHooksForPtr<GameInteractor::OnActorDraw>((uintptr_t)actor, actor);
+ GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorDraw>(actor);
+}
+
void GameInteractor_ExecuteOnActorKill(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorKill>(actor);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorKill>(((Actor*)actor)->id, actor);
diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
index 60326614d..24fe78dc1 100644
--- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
+++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h
@@ -22,6 +22,7 @@ void GameInteractor_ExecuteOnOcarinaSongAction();
bool GameInteractor_ShouldActorInit(void* actor);
void GameInteractor_ExecuteOnActorInit(void* actor);
void GameInteractor_ExecuteOnActorUpdate(void* actor);
+void GameInteractor_ExecuteOnActorDraw(void* actor);
void GameInteractor_ExecuteOnActorKill(void* actor);
void GameInteractor_ExecuteOnEnemyDefeat(void* actor);
void GameInteractor_ExecuteOnBossDefeat(void* actor);
diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c
index 38843bbec..1c514f92f 100644
--- a/soh/src/code/z_actor.c
+++ b/soh/src/code/z_actor.c
@@ -2744,6 +2744,7 @@ void Actor_Draw(PlayState* play, Actor* actor) {
}
actor->draw(actor, play);
+ GameInteractor_ExecuteOnActorDraw(actor);
if (actor->colorFilterTimer != 0) {
if (actor->colorFilterParams & 0x2000) {