From 435f8f73637575ac53b118dc5c77f035393a6195 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Sun, 26 Nov 2023 21:00:59 -0600 Subject: [PATCH] Add snowballs to kak & hyrule field --- soh/soh/ActorDB.cpp | 17 ++ soh/soh/Enhancements/mods.cpp | 36 +++ soh/src/code/z_play.c | 1 + .../actors/ovl_En_Snowball/z_en_snowball.c | 229 ++++++++++++++++++ .../actors/ovl_En_Snowball/z_en_snowball.h | 31 +++ 5 files changed, 314 insertions(+) create mode 100644 soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c create mode 100644 soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index 3f515d19a..1ffe5bf3e 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -604,6 +604,22 @@ static ActorDBInit EnPartnerInit = { }; extern "C" s16 gEnPartnerId; +#include "src/overlays/actors/ovl_En_Snowball/z_en_snowball.h" +static ActorDBInit EnSnowballInit = { + "En_Snowball", + "Snowball", + ACTORCAT_ITEMACTION, + (ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED | ACTOR_FLAG_DRAGGED_BY_HOOKSHOT | ACTOR_FLAG_CAN_PRESS_SWITCH), + OBJECT_GAMEPLAY_KEEP, + sizeof(EnSnowball), + (ActorFunc)EnSnowball_Init, + (ActorFunc)EnSnowball_Destroy, + (ActorFunc)EnSnowball_Update, + (ActorFunc)EnSnowball_Draw, + nullptr, +}; +extern "C" s16 gEnSnowballId; + #include "src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.h" static ActorDBInit EnChristmasTreeInit = { "En_ChristmasTree", @@ -638,6 +654,7 @@ extern "C" s16 gEnChristmasDecoId; void ActorDB::AddBuiltInCustomActors() { gEnPartnerId = ActorDB::Instance->AddEntry(EnPartnerInit).entry.id; + gEnSnowballId = ActorDB::Instance->AddEntry(EnSnowballInit).entry.id; gEnChristmasTreeId = ActorDB::Instance->AddEntry(EnChristmasTreeInit).entry.id; gEnChristmasDecoId = ActorDB::Instance->AddEntry(EnChristmasDecoInit).entry.id; } diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 338cbfc40..686375658 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -32,6 +32,7 @@ extern "C" { #include "functions.h" extern SaveContext gSaveContext; extern PlayState* gPlayState; +extern "C" s16 gEnSnowballId; extern void Overlay_DisplayText(float duration, const char* text); uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum); } @@ -1059,6 +1060,40 @@ void RegisterRandomizedEnemySizes() { }); } +static CollisionPoly snowballPoly; +static Vec3f snowballPos; +static f32 raycastResult; + +void RegisterSnowballs() { + GameInteractor::Instance->RegisterGameHook([]() { + if (gPlayState->sceneNum != SCENE_HYRULE_FIELD && gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { + return; + } + + int actorsSpawned = 0; + + while (actorsSpawned < 30) { + snowballPos.x = (float)(Random( + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -10000 : -2700) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 5000 : 2000) + 10000 + ) - (float)10000.0f); + snowballPos.y = 5000; + snowballPos.z = (float)(Random( + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -1000 : -2000) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 15000 : 2000) + 10000 + ) - (float)10000.0f); + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + if (raycastResult > BGCHECK_Y_MIN) { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, gEnSnowballId, snowballPos.x, raycastResult, + snowballPos.z, 0, 0, 0, gPlayState->sceneNum == SCENE_HYRULE_FIELD, 0); + actorsSpawned++; + } + } + }); +} + void InitMods() { RegisterTTS(); RegisterInfiniteMoney(); @@ -1088,5 +1123,6 @@ void InitMods() { RegisterAltTrapTypes(); RegisterRandomizerSheikSpawn(); RegisterRandomizedEnemySizes(); + RegisterSnowballs(); NameTag_RegisterHooks(); } diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 7cdb5f8dd..050714836 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -36,6 +36,7 @@ PlayState* gPlayState; s16 firstInit = 0; s16 gEnPartnerId; +s16 gEnSnowballId; s16 gEnChristmasTreeId; s16 gEnChristmasDecoId; diff --git a/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c new file mode 100644 index 000000000..a6c07a245 --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c @@ -0,0 +1,229 @@ +/* + * File: z_en_snowball.c + * Overlay: ovl_En_Snowball + * Description: Rollable Snowball + */ + +#include "z_en_snowball.h" +#include "objects/gameplay_keep/gameplay_keep.h" +#include "objects/object_goroiwa/object_goroiwa.h" +#include "soh_assets.h" + +#define FLAGS ACTOR_FLAG_UPDATE_WHILE_CULLED + +void EnSnowball_Init(Actor* thisx, PlayState* play); +void EnSnowball_Destroy(Actor* thisx, PlayState* play); +void EnSnowball_Update(Actor* thisx, PlayState* play); +void EnSnowball_Draw(Actor* thisx, PlayState* play); + +static ColliderJntSphElementInit sJntSphElementsInit[] = { + { + { + ELEMTYPE_UNK0, + { 0x20000000, 0x00, 0x04 }, + { 0x00000000, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_NONE, + OCELEM_ON, + }, + { 0, { { 0, 0, 0 }, 14 }, 100 }, + }, +}; + +static ColliderJntSphInit sJntSphInit = { + { + COLTYPE_NONE, + AT_ON | AT_TYPE_ENEMY, + AC_NONE, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_2, + COLSHAPE_JNTSPH, + }, + 1, + sJntSphElementsInit, +}; + +static CollisionCheckInfoInit sColChkInfoInit = { 0, 3, 15, MASS_HEAVY }; + +void EnSnowball_UpdateCollider(EnSnowball* this) { + Sphere16* worldSphere = &this->collider.elements[0].dim.worldSphere; + + worldSphere->center.x = this->actor.world.pos.x; + worldSphere->center.y = this->actor.world.pos.y + (this->actor.scale.x * 500.0f); + worldSphere->center.z = this->actor.world.pos.z; + worldSphere->radius = (this->actor.scale.x * 500.0f); +} + +void EnSnowball_InitCollider(EnSnowball* this, PlayState* play) { + Collider_InitJntSph(play, &this->collider); + Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->colliderItems); + EnSnowball_UpdateCollider(this); + this->collider.elements[0].dim.worldSphere.radius = (this->actor.scale.x * 500.0f); +} + +static InitChainEntry sInitChain[] = { + ICHAIN_F32_DIV1000(gravity, -860, ICHAIN_CONTINUE), ICHAIN_F32_DIV1000(minVelocityY, -15000, ICHAIN_CONTINUE), + ICHAIN_VEC3F_DIV1000(scale, 5, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneForward, 1500, ICHAIN_CONTINUE), + ICHAIN_F32(uncullZoneScale, 150, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneDownward, 1500, ICHAIN_STOP), +}; + +void EnSnowball_Init(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + + Actor_ProcessInitChain(&this->actor, sInitChain); + EnSnowball_InitCollider(this, play); + CollisionCheck_SetInfo(&this->actor.colChkInfo, NULL, &sColChkInfoInit); + ActorShape_Init(&this->actor.shape, 595.0f, ActorShadow_DrawCircle, 9.4f); + this->actor.shape.shadowAlpha = 200; + + if (thisx->params == 1) { + this->actor.speedXZ += 5.0f; + this->actor.world.rot.y = Rand_ZeroFloat(65536.0f); + } +} + +void EnSnowball_Destroy(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + + Collider_DestroyJntSph(play, &this->collider); +} + +void EnSnowball_Update(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + Actor* player = GET_PLAYER(play); + + // Kill the actor if it falls too far + if (thisx->world.pos.y < -10000.0f) { + Actor_Kill(thisx); + return; + } + + u8 meanBoulder = thisx->params == 1 && this->actor.scale.x > 0.1f; + + // Check if the player is close enough to start rolling + if (this->actor.xzDistToPlayer < MAX(20.0f, this->actor.scale.x * 600.0f) && !meanBoulder) { + /// Flip the actor's rotation away from the player + thisx->world.rot.y = thisx->yawTowardsPlayer + 0x8000; + this->actor.speedXZ = MAX(5.0f, this->actor.speedXZ); + } + + if (this->collider.base.atFlags & AT_HIT) { + this->collider.base.atFlags &= ~AT_HIT; + // Flip the actor's rotation away from the player + thisx->world.rot.y = thisx->yawTowardsPlayer + 0x8000; + + func_8002F6D4(play, &this->actor, 2.0f, this->actor.yawTowardsPlayer, 0.0f, 0); + Player_PlaySfx(&GET_PLAYER(play)->actor, NA_SE_PL_BODY_HIT); + } + + // Slow down the actor and increase it's scale + if (this->actor.speedXZ > 0.0f) { + CollisionPoly snowballPoly; + u8 goingUp = this->actor.world.pos.y - this->prevY > 0.001f; + u8 goingDown = this->actor.world.pos.y - this->prevY < -0.001f; + + // friction + if (thisx->params != 1) { + this->actor.speedXZ -= 0.1f; + } + + if (goingDown) { + // Increase the speed if going down hill + f32 speed = (this->prevY - this->actor.world.pos.y) * 0.15f; + this->actor.speedXZ += MIN(speed, 0.5f); + } else if (goingUp) { + // Reduce the speed if going up hill + this->actor.speedXZ -= (this->actor.world.pos.y - this->prevY) * 0.1f; + } + + if (goingUp || goingDown) { + // Check if going straight, one degree right, or one degree left will result in steeper slope + // Check straight + Vec3f snowballPos = this->actor.world.pos; + snowballPos.y += 100.0f; + snowballPos.x += Math_SinS(this->actor.world.rot.y) * 1.0f; + snowballPos.z += Math_CosS(this->actor.world.rot.y) * 1.0f; + float straightSlope = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + // Check one degree right + snowballPos = this->actor.world.pos; + snowballPos.y += 100.0f; + snowballPos.x += Math_SinS(this->actor.world.rot.y + 0x100) * 1.0f; + snowballPos.z += Math_CosS(this->actor.world.rot.y + 0x100) * 1.0f; + float rightSlope = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + // Check one degree left + snowballPos = this->actor.world.pos; + snowballPos.y += 100.0f; + snowballPos.x += Math_SinS(this->actor.world.rot.y - 0x100) * 1.0f; + snowballPos.z += Math_CosS(this->actor.world.rot.y - 0x100) * 1.0f; + float leftSlope = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + if (straightSlope > rightSlope || straightSlope > leftSlope) { + if (rightSlope < leftSlope) { + this->actor.world.rot.y += 0x100; + } else { + this->actor.world.rot.y -= 0x100; + } + } + } + + // Check if the actor is colliding with a wall and bounce off + if (thisx->bgCheckFlags & 8) { + if (ABS((s16)(thisx->wallYaw - thisx->world.rot.y)) > 0x4000) { + thisx->world.rot.y = ((thisx->wallYaw - thisx->world.rot.y) + thisx->wallYaw) - 0x8000; + } + if (thisx->params != 1) { + thisx->speedXZ *= 0.7f; + } + thisx->bgCheckFlags &= ~8; + if (this->actor.speedXZ > 5.0f) { + Audio_PlayActorSound2(thisx, NA_SE_EV_BOMB_BOUND); + } + } + + Actor_SetScale(&this->actor, MIN(0.15f, this->actor.scale.x + (this->actor.speedXZ * 0.00001f))); + } + + if (this->actor.speedXZ < 0.0f) { + this->actor.speedXZ = 0.0f; + } + + // Based on speed and scale, rotate the snowball + // The larger the snowball, the slower it rotates + this->sRot += (this->actor.speedXZ * 15.0f) / this->actor.scale.x; + + // record the actor's position + this->prevY = this->actor.world.pos.y; + + // Process movement (moves foward based on speed and rotation) + Actor_MoveForward(thisx); + + // Prevent actor from going through the ground or walls + Actor_UpdateBgCheckInfo(play, &this->actor, MAX(10.0f, this->actor.scale.x * 250.0f), MAX(10.0f, this->actor.scale.x * 500.0f), 0.0f, 0xFF); + + EnSnowball_UpdateCollider(this); + // Add collision checks if the actor is a mean boulder + if (meanBoulder) { + CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + } +} + +void EnSnowball_Draw(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_RotateZYX(this->sRot, thisx->world.rot.y, 0, MTXMODE_APPLY); + + Matrix_Translate(0.0f, 7600.0f, -148.649f, MTXMODE_APPLY); + Matrix_Scale(4.844f, 4.844f, 4.844f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + gSPDisplayList(POLY_OPA_DISP++, gLinkAdultPompomDL); + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h new file mode 100644 index 000000000..fa8c0779c --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h @@ -0,0 +1,31 @@ +#ifndef Z_EN_SNOWBALL_H +#define Z_EN_SNOWBALL_H + +#include +#include "global.h" + +struct EnSnowball; + +typedef void (*EnSnowballActionFunc)(struct EnSnowball*, PlayState*); + +typedef struct EnSnowball { + Actor actor; + + ColliderJntSph collider; + ColliderJntSphElement colliderItems[1]; + s16 sRot; + f32 prevY; +} EnSnowball; + +#ifdef __cplusplus +extern "C" { +#endif +void EnSnowball_Init(Actor* thisx, PlayState* play); +void EnSnowball_Destroy(Actor* thisx, PlayState* play); +void EnSnowball_Update(Actor* thisx, PlayState* play); +void EnSnowball_Draw(Actor* thisx, PlayState* play); +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file