diff --git a/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c b/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c index 9bc35295c9d..2c547a61285 100644 --- a/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c +++ b/soh/src/overlays/actors/ovl_En_Crow/z_en_crow.c @@ -120,6 +120,19 @@ void EnCrow_Init(Actor* thisx, PlayState* play) { ActorShape_Init(&this->actor.shape, 2000.0f, ActorShadow_DrawCircle, 20.0f); sDeathCount = 0; EnCrow_SetupFlyIdle(this); + + + // Keese-Sanity: Keese have a small chance to spawn among the Guays at Lon-Lon, Lake Hylia, and Colossus + if (!CVarGetInteger("gKeeseSanity", 0)) { + return; + } + f32 rnd = Rand_ZeroOne(); + if (play->sceneNum == SCENE_LON_LON_RANCH || play->sceneNum == SCENE_LAKE_HYLIA || play->sceneNum == SCENE_DESERT_COLOSSUS) { + if (rnd < (0.05 * CVarGetInteger("gKeeseSanityIntensity", 0))) { + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_FIREFLY, this->actor.world.pos.x, this->actor.world.pos.y, + this->actor.world.pos.z, 0, 0, 0, 2, false); + } + } } void EnCrow_Destroy(Actor* thisx, PlayState* play) { @@ -365,7 +378,9 @@ void EnCrow_Die(EnCrow* this, PlayState* play) { } else { Item_DropCollectible(play, &this->actor.world.pos, ITEM00_RUPEE_RED); } - if (!CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0)) { + // Keese-Sanity: Allow Guays to respawn only in Lon-Lon, Lake Hylia, and Colossus (when enemy rando is off) + if (!CVarGetInteger("gRandomizedEnemies", 0) && + (play->sceneNum == SCENE_LON_LON_RANCH || play->sceneNum == SCENE_LAKE_HYLIA || play->sceneNum == SCENE_DESERT_COLOSSUS)) { EnCrow_SetupRespawn(this); } else { Actor_Kill(this); diff --git a/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c b/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c index d8d42a65925..acdfc74ad37 100644 --- a/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c +++ b/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.c @@ -33,7 +33,11 @@ void EnFirefly_DisturbDiveAttack(EnFirefly* this, PlayState* play); typedef enum { /* 0 */ KEESE_AURA_NONE, /* 1 */ KEESE_AURA_FIRE, - /* 2 */ KEESE_AURA_ICE + /* 2 */ KEESE_AURA_ICE, + /* 3 */ KEESE_AURA_ELEC, + /* 4 */ KEESE_AURA_VOID, + /* 5 */ KEESE_AURA_WIND, + /* 6 */ KEESE_AURA_BLOOD } KeeseAuraType; const ActorInit En_Firefly_InitVars = { @@ -150,6 +154,41 @@ void EnFirefly_Init(Actor* thisx, PlayState* play) { Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->colliderItems); CollisionCheck_SetInfo(&this->actor.colChkInfo, &sDamageTable, &sColChkInfoInit); + // Every time a Keese spawns, it will choose a random type. Invis, Void, and Blood have lower odds than the others. + f32 rnd = Rand_ZeroOne(), rnd2 = Rand_ZeroOne(), rnd3 = Rand_ZeroOne(); + if (CVarGetInteger("gKeeseSanity", 0)) { + this->onFire = false; + + if (rnd < 0.04) { // Invis Keese (rare) + this->actor.params = KEESE_NORMAL_FLY; + this->actor.flags |= ACTOR_FLAG_REACT_TO_LENS; + this->actor.draw = EnFirefly_DrawInvisible; + this->actor.params &= 0x7FFF; + } + if (rnd >= 0.04 && rnd < 0.222) { // Common + this->actor.params = KEESE_NORMAL_FLY; + } + if (rnd >= 0.222 && rnd < 0.404) { // Common + this->actor.params = KEESE_FIRE_FLY; + this->onFire = true; + } + if (rnd >= 0.404 && rnd < 0.586) { // Common + this->actor.params = KEESE_ICE_FLY; + } + if (rnd >= 0.586 && rnd < 0.768) { // Common + this->actor.params = KEESE_ELEC_FLY; + } + if (rnd >= 0.768 && rnd < 0.95) { // Common + this->actor.params = KEESE_WIND_FLY; + } + if (rnd >= 0.95 && rnd < 0.975) { // Rare + this->actor.params = KEESE_VOID_FLY; + } + if (rnd >= 0.975) { // Rare + this->actor.params = KEESE_BLOOD_FLY; + } + } + if ((this->actor.params & 0x8000) != 0) { this->actor.flags |= ACTOR_FLAG_REACT_TO_LENS; this->actor.draw = EnFirefly_DrawInvisible; @@ -193,7 +232,39 @@ void EnFirefly_Init(Actor* thisx, PlayState* play) { } } + // Keese-Sanity + // Using TextIDs starting at 0x0660 to hook into the custom message system + // for Navi's enemy descriptions + if (this->actor.params == KEESE_ELEC_FLY) { + this->collider.elements[0].info.toucher.effect = 3; // Electric + this->collider.elements[0].info.bumper.effect = 1; // Link gets shocked if attacking them with a sword + this->auraType = KEESE_AURA_ELEC; + this->actor.naviEnemyId = 0x0660; + } + if (this->actor.params == KEESE_WIND_FLY) { + this->auraType = KEESE_AURA_WIND; + this->actor.naviEnemyId = 0x0661; + } + if (this->actor.params == KEESE_VOID_FLY) { + this->auraType = KEESE_AURA_VOID; + this->actor.naviEnemyId = 0x0662; + } + if (this->actor.params == KEESE_BLOOD_FLY) { + this->auraType = KEESE_AURA_BLOOD; + this->actor.naviEnemyId = 0x0663; + } + this->collider.elements[0].dim.worldSphere.radius = sJntSphInit.elements[0].dim.modelSphere.radius; + + // In Keese-Sanity, there's a chance to spawn additional random Keese + if (rnd2 < (0.05 * CVarGetInteger("gKeeseSanityIntensity", 0)) && (CVarGetInteger("gKeeseSanity", 0))) { + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_FIREFLY, this->actor.world.pos.x, this->actor.world.pos.y, + this->actor.world.pos.z, 0, 0, 0, KEESE_NORMAL_FLY, CVarGetInteger("gKeeseEnemyRandoType", 0)); + } // And you might even get a Guay mixed in there + if (rnd3 < (0.017 * CVarGetInteger("gKeeseSanityIntensity", 0)) && (CVarGetInteger("gKeeseSanity", 0))) { + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_CROW, this->actor.world.pos.x, this->actor.world.pos.y, + this->actor.world.pos.z, 0, 0, 0, 0, CVarGetInteger("gKeeseEnemyRandoType", 0)); + } } void EnFirefly_Destroy(Actor* thisx, PlayState* play) { @@ -338,6 +409,13 @@ s32 EnFirefly_SeekTorch(EnFirefly* this, PlayState* play) { f32 currentMinDist; Vec3f flamePos; + // Special Keese types don't seek torches to ignite themselves + if (CVarGetInteger("gKeeseSanity", 0)) { + if (this->actor.params >= KEESE_ICE_FLY) { + return 0; + } + } + findTorch = (ObjSyokudai*)play->actorCtx.actorLists[ACTORCAT_PROP].head; closestTorch = NULL; currentMinDist = 35000.0f; @@ -424,6 +502,7 @@ void EnFirefly_FlyIdle(EnFirefly* this, PlayState* play) { // Fall to the ground after being hit void EnFirefly_Fall(EnFirefly* this, PlayState* play) { + f32 rnd = Rand_ZeroOne(); if (Animation_OnFrame(&this->skelAnime, 6.0f)) { this->skelAnime.playSpeed = 0.0f; } @@ -439,6 +518,12 @@ void EnFirefly_Fall(EnFirefly* this, PlayState* play) { this->timer--; } if ((this->actor.bgCheckFlags & 1) || (this->timer == 0)) { + // In Keese-Sanity, there's a chance to spawn a new random Keese + if (rnd < (0.125 * CVarGetInteger("gKeeseSanityIntensity", 0)) && (CVarGetInteger("gKeeseSanity", 0))) { + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_FIREFLY, this->actor.world.pos.x, this->actor.world.pos.y, + this->actor.world.pos.z, 0, 0, 0, KEESE_NORMAL_FLY, + CVarGetInteger("gKeeseEnemyRandoType", 0)); + } EnFirefly_SetupDie(this); } } @@ -684,6 +769,27 @@ void EnFirefly_Update(Actor* thisx, PlayState* play2) { if (this->actionFunc != EnFirefly_DisturbDiveAttack) { EnFirefly_SetupRebound(this); } + if (!(this->collider.base.atFlags & AT_BOUNCED)) { // If Keese does not hit Link's shield + if (this->actor.params == KEESE_VOID_FLY) { // Void Keese cause player to void out + Play_TriggerRespawn(play); + func_800788CC(NA_SE_OC_ABYSS); + } + if (this->actor.params == KEESE_WIND_FLY) { // Wind Keese cause a large knockback + func_8002F71C(play, &this->actor, (350.0f - this->actor.xzDistToPlayer) * 0.04f + 4.0f, + this->actor.world.rot.y, 8.0f); + } + if (this->actor.params == KEESE_BLOOD_FLY) { // Blood Keese take a large percentage of Link's health, but can't kill him + if (gSaveContext.nayrusLoveTimer == 0) { // Nayru's Love still protects you from this damage + gSaveContext.health += 8; + if (gSaveContext.health > gSaveContext.healthCapacity) { + gSaveContext.health = gSaveContext.healthCapacity; + } + if (gSaveContext.health >= 5) { + Health_ChangeBy(play, -gSaveContext.health * 0.75); + } + } + } + } } EnFirefly_UpdateDamage(this, play); @@ -743,10 +849,32 @@ void EnFirefly_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* static Color_RGBA8 fireAuraEnvColor = { 255, 50, 0, 0 }; static Color_RGBA8 iceAuraPrimColor = { 100, 200, 255, 255 }; static Color_RGBA8 iceAuraEnvColor = { 0, 0, 255, 0 }; + static Color_RGBA8 voidAuraPrimColor = { 23, 10, 46, 255 }; + static Color_RGBA8 voidAuraEnvColor = { 67, 31, 185, 0 }; + static Color_RGBA8 elecAuraPrimColor = { 242, 250, 110, 255 }; + static Color_RGBA8 elecAuraEnvColor = { 75, 80, 200, 0 }; + static Color_RGBA8 windAuraEnvColor = { 10, 160, 40, 255 }; + static Color_RGBA8 windAuraPrimColor = { 136, 241, 182, 0 }; + static Color_RGBA8 bloodAuraEnvColor = { 100, 0, 90, 255 }; + static Color_RGBA8 bloodAuraPrimColor = { 150, 0, 0, 0 }; + static Color_RGB8 fireAuraPrimColor_ori = { 255, 255, 100 }; + static Color_RGB8 fireAuraEnvColor_ori = { 255, 50, 0 }; + static Color_RGB8 iceAuraPrimColor_ori = { 100, 200, 255 }; + static Color_RGB8 iceAuraEnvColor_ori = { 0, 0, 255 }; + Color_RGBA8 customFireAuraPrimColor = CVarGetColor(CVAR_COSMETIC("NPC.FireKeesePrimary.Value"), fireAuraPrimColor); Color_RGBA8 customFireAuraEnvColor = CVarGetColor(CVAR_COSMETIC("NPC.FireKeeseSecondary.Value"), fireAuraEnvColor); Color_RGBA8 customIceAuraPrimColor = CVarGetColor(CVAR_COSMETIC("NPC.IceKeesePrimary.Value"), iceAuraPrimColor); Color_RGBA8 customIceAuraEnvColor = CVarGetColor(CVAR_COSMETIC("NPC.IceKeeseSecondary.Value"), iceAuraEnvColor); + Color_RGBA8 customElecAuraPrimColor = CVarGetColor("gCosmetics.NPC_ElecKeesePrimary.Value", elecAuraPrimColor); + Color_RGBA8 customElecAuraEnvColor = CVarGetColor("gCosmetics.NPC_ElecKeeseSecondary.Value", elecAuraEnvColor); + Color_RGBA8 customVoidAuraPrimColor = CVarGetColor("gCosmetics.NPC_VoidKeesePrimary.Value", voidAuraPrimColor); + Color_RGBA8 customVoidAuraEnvColor = CVarGetColor("gCosmetics.NPC_VoidKeeseSecondary.Value", voidAuraEnvColor); + Color_RGBA8 customWindAuraPrimColor = CVarGetColor("gCosmetics.NPC_WindKeesePrimary.Value", windAuraPrimColor); + Color_RGBA8 customWindAuraEnvColor = CVarGetColor("gCosmetics.NPC_WindKeeseSecondary.Value", windAuraEnvColor); + Color_RGBA8 customBloodAuraPrimColor = CVarGetColor("gCosmetics.NPC_BloodKeesePrimary.Value", bloodAuraPrimColor); + Color_RGBA8 customBloodAuraEnvColor = CVarGetColor("gCosmetics.NPC_BloodKeeseSecondary.Value", bloodAuraEnvColor); + static Vec3f effVelocity = { 0.0f, 0.5f, 0.0f }; static Vec3f effAccel = { 0.0f, 0.5f, 0.0f }; static Vec3f limbSrc = { 0.0f, 0.0f, 0.0f }; @@ -762,7 +890,7 @@ void EnFirefly_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* if (!this->onFire && (limbIndex == 27)) { gSPDisplayList((*gfx)++, gKeeseEyesDL); } else { - if ((this->auraType == KEESE_AURA_FIRE) || (this->auraType == KEESE_AURA_ICE)) { + if ((this->auraType >= KEESE_AURA_FIRE)) { if ((limbIndex == 15) || (limbIndex == 21)) { if (this->actionFunc != EnFirefly_Die) { Matrix_Get(&mtx); @@ -796,6 +924,50 @@ void EnFirefly_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* } else { effEnvColor = &fireAuraEnvColor; } + } else if (this->auraType == KEESE_AURA_ELEC) { + if (CVarGetInteger("gCosmetics.NPC_ElecKeesePrimary.Changed", 0)) { + effPrimColor = &customElecAuraPrimColor; + } else { + effPrimColor = &elecAuraPrimColor; + } + if (CVarGetInteger("gCosmetics.NPC_ElecKeeseSecondary.Changed", 0)) { + effEnvColor = &customElecAuraEnvColor; + } else { + effEnvColor = &elecAuraEnvColor; + } + } else if (this->auraType == KEESE_AURA_VOID) { + if (CVarGetInteger("gCosmetics.NPC_VoidKeesePrimary.Changed", 0)) { + effPrimColor = &customVoidAuraPrimColor; + } else { + effPrimColor = &voidAuraPrimColor; + } + if (CVarGetInteger("gCosmetics.NPC_VoidKeeseSecondary.Changed", 0)) { + effEnvColor = &customVoidAuraEnvColor; + } else { + effEnvColor = &voidAuraEnvColor; + } + } else if (this->auraType == KEESE_AURA_WIND) { + if (CVarGetInteger("gCosmetics.NPC_WindKeesePrimary.Changed", 0)) { + effPrimColor = &customWindAuraPrimColor; + } else { + effPrimColor = &windAuraPrimColor; + } + if (CVarGetInteger("gCosmetics.NPC_WindKeeseSecondary.Changed", 0)) { + effEnvColor = &customWindAuraEnvColor; + } else { + effEnvColor = &windAuraEnvColor; + } + } else if (this->auraType == KEESE_AURA_BLOOD) { + if (CVarGetInteger("gCosmetics.NPC_BloodKeesePrimary.Changed", 0)) { + effPrimColor = &customBloodAuraPrimColor; + } else { + effPrimColor = &bloodAuraPrimColor; + } + if (CVarGetInteger("gCosmetics.NPC_BloodKeeseSecondary.Changed", 0)) { + effEnvColor = &customBloodAuraEnvColor; + } else { + effEnvColor = &bloodAuraEnvColor; + } } else { if (CVarGetInteger(CVAR_COSMETIC("NPC.IceKeesePrimary.Changed"), 0)) { effPrimColor = &customIceAuraPrimColor; diff --git a/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.h b/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.h index 13bc3a8947a..dc21adecf9c 100644 --- a/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.h +++ b/soh/src/overlays/actors/ovl_En_Firefly/z_en_firefly.h @@ -29,7 +29,11 @@ typedef enum { /* 1 */ KEESE_FIRE_PERCH, /* 2 */ KEESE_NORMAL_FLY, /* 3 */ KEESE_NORMAL_PERCH, - /* 4 */ KEESE_ICE_FLY + /* 4 */ KEESE_ICE_FLY, + /* 5 */ KEESE_ELEC_FLY, + /* 6 */ KEESE_VOID_FLY, + /* 7 */ KEESE_WIND_FLY, + /* 8 */ KEESE_BLOOD_FLY } KeeseType; #endif