From 045de62c95bba1f782d170fb0e68354047f6ef11 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 4 Dec 2024 20:26:11 -0600 Subject: [PATCH] Add custom collectible thing --- soh/include/z64item.h | 8 +- .../custom-collectible/CustomCollectible.cpp | 209 ++++++++++++++++++ .../custom-collectible/CustomCollectible.h | 24 ++ soh/soh/OTRGlobals.cpp | 3 + soh/src/code/z_draw.c | 3 + soh/src/code/z_parameter.c | 21 +- .../actors/ovl_player_actor/z_player.c | 22 +- 7 files changed, 275 insertions(+), 15 deletions(-) create mode 100644 soh/soh/Enhancements/custom-collectible/CustomCollectible.cpp create mode 100644 soh/soh/Enhancements/custom-collectible/CustomCollectible.h diff --git a/soh/include/z64item.h b/soh/include/z64item.h index b1fa897fe..408667764 100644 --- a/soh/include/z64item.h +++ b/soh/include/z64item.h @@ -309,6 +309,7 @@ typedef enum { /* 0x99 */ ITEM_STICK_UPGRADE_30, /* 0x9A */ ITEM_NUT_UPGRADE_30, /* 0x9B */ ITEM_NUT_UPGRADE_40, + /* 0x9C */ ITEM_SHIP, // SOH [Enhancement] Added to enable custom item gives /* 0xFC */ ITEM_LAST_USED = 0xFC, /* 0xFE */ ITEM_NONE_FE = 0xFE, /* 0xFF */ ITEM_NONE = 0xFF @@ -458,9 +459,10 @@ typedef enum { /* 0x79 */ GI_NUT_UPGRADE_30, /* 0x7A */ GI_NUT_UPGRADE_40, /* 0x7B */ GI_BULLET_BAG_50, - /* 0x7C */ GI_ICE_TRAP, // freezes link when opened from a chest - /* 0x7D */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg) - /* 0x84 */ GI_MAX + /* 0x7C */ GI_SHIP, // SOH [Enhancement] Added to enable custom item gives + /* 0x7D */ GI_ICE_TRAP, // freezes link when opened from a chest + /* 0x7E */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg) + /* 0x7F */ GI_MAX } GetItemID; typedef enum { diff --git a/soh/soh/Enhancements/custom-collectible/CustomCollectible.cpp b/soh/soh/Enhancements/custom-collectible/CustomCollectible.cpp new file mode 100644 index 000000000..7bbbd8b55 --- /dev/null +++ b/soh/soh/Enhancements/custom-collectible/CustomCollectible.cpp @@ -0,0 +1,209 @@ +#include "CustomCollectible.h" +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include "soh/frame_interpolation.h" +#include "soh/Enhancements/custom-message/CustomMessageManager.h" + +extern "C" { +#include "z64actor.h" +#include "functions.h" +#include "variables.h" +#include "macros.h" +#include "objects/object_md/object_md.h" +extern PlayState* gPlayState; +} + +EnItem00* CustomCollectible::Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc, + ActorFunc drawFunc) { + if (!gPlayState) { + return nullptr; + } + + Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ITEM00, posX, posY, posZ, flags, rot, params, ITEM00_NONE, 0); + EnItem00* enItem00 = (EnItem00*)actor; + + if (actionFunc != NULL) { + enItem00->actionFunc = (EnItem00ActionFunc)actionFunc; + } + + if (drawFunc != NULL) { + actor->draw = drawFunc; + } + + return enItem00; +} + +void CustomCollectible_Init(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_BOBBING) { + actor->shape.yOffset = 1250.0f; + } else { + actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::HIDE_TILL_OVERHEAD) { + Actor_SetScale(actor, 0.0f); + } else { + Actor_SetScale(actor, 0.015f); + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::KEEP_ON_PLAYER) { + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::TOSS_ON_SPAWN) { + actor->velocity.y = 8.0f; + actor->speedXZ = 2.0f; + actor->gravity = -1.4f; + actor->world.rot.y = Rand_ZeroOne() * 40000.0f; + } + + enItem00->unk_15A = -1; +} + +// By default this will just assume the GID was passed in as the rot z, if you want different functionality you should +// override the draw +void CustomCollectible_Draw(Actor* actor, PlayState* play) { + Matrix_Scale(30.0f, 30.0f, 30.0f, MTXMODE_APPLY); + GetItem_Draw(play, CUSTOM_ITEM_PARAM); +} + +void CustomCollectible_Update(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + Player* player = GET_PLAYER(play); + + if (!(CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_SPINNING)) { + actor->shape.rot.y += 960; + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_BOBBING) { + actor->shape.yOffset = 1250.0f; + } else { + actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::HIDE_TILL_OVERHEAD) { + Actor_SetScale(actor, 0.0f); + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::KEEP_ON_PLAYER) { + actor->gravity = 0.0f; + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + if (CUSTOM_ITEM_FLAGS & CustomCollectible::KILL_ON_TOUCH) { + // Pretty self explanatory, if the player is within range, kill the actor and call the action function + if ((actor->xzDistToPlayer <= 50.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) { + if (enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION; + } + Actor_Kill(actor); + } + } else if (CUSTOM_ITEM_FLAGS & CustomCollectible::GIVE_OVERHEAD) { + // If the item hasn't been picked up (unk_15A == -1) and the player is within range + if (enItem00->unk_15A == -1 && (actor->xzDistToPlayer <= 50.0f) && + (fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) { + // Fire the action function + if (enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION; + } + Sfx_PlaySfxCentered(NA_SE_SY_GET_ITEM); + // Set the unk_15A to 15, this indicates the item has been picked up and will start the overhead animation + enItem00->unk_15A = 15; + CUSTOM_ITEM_FLAGS |= CustomCollectible::STOP_BOBBING; + CUSTOM_ITEM_FLAGS |= CustomCollectible::KEEP_ON_PLAYER; + } + + // If the item has been picked up + if (enItem00->unk_15A > 0) { + // Reduce the size a bit, but also makes it visible for HIDE_TILL_OVERHEAD + Actor_SetScale(actor, 0.010f); + + // Decrement the unk_15A, which will be used to bob the item up and down + enItem00->unk_15A--; + + // Account for the different heights of the player forms + f32 height = 45.0f; + // TODO: Check for adult? + + // Bob the item up and down + actor->world.pos.y += (height + (Math_SinS(enItem00->unk_15A * 15000) * (enItem00->unk_15A * 0.3f))); + } + + // Finally, once the bobbing animation is done, kill the actor + if (enItem00->unk_15A == 0) { + Actor_Kill(actor); + } + } else if (CUSTOM_ITEM_FLAGS & CustomCollectible::GIVE_ITEM_CUTSCENE) { + // If the item hasn't been picked up and the player is within range + if (!Actor_HasParent(actor, play) && enItem00->unk_15A == -1) { + Actor_OfferGetItem(actor, play, GI_SHIP, 50.0f, 20.0f); + } else { + if (enItem00->unk_15A == -1) { + CUSTOM_ITEM_FLAGS |= CustomCollectible::STOP_BOBBING; + CUSTOM_ITEM_FLAGS |= CustomCollectible::KEEP_ON_PLAYER; + CUSTOM_ITEM_FLAGS |= CustomCollectible::HIDE_TILL_OVERHEAD; + } + + // Begin incrementing the unk_15A, indicating the item has been picked up + enItem00->unk_15A++; + + // For the first 20 frames, wait while the player's animation plays + if (enItem00->unk_15A >= 20) { + // After the first 20 frames, show the item and call the action function + if (enItem00->unk_15A == 20 && enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION; + } + // Override the bobbing animation to be a fixed height + actor->shape.yOffset = 900.0f; + Actor_SetScale(actor, 0.007f); + + f32 height = 45.0f; + // TODO: Check for adult? + + actor->world.pos.y += height; + } + + // Once the player is no longer in the "Give Item" state, kill the actor + if (!(player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM)) { + Actor_Kill(actor); + } + } + } + + if (actor->gravity != 0.0f) { + Actor_MoveForward(actor); + Actor_UpdateBgCheckInfo(play, actor, 20.0f, 15.0f, 15.0f, 0x1D); + } + + if (actor->bgCheckFlags & 0x0003) { + actor->speedXZ = 0.0f; + } + + Collider_UpdateCylinder(actor, &enItem00->collider); + CollisionCheck_SetAC(play, &play->colChkCtx, &enItem00->collider.base); +} + +void CustomCollectible::RegisterHooks() { + GameInteractor::Instance->RegisterGameHookForID( + ACTOR_EN_ITEM00, [](void* actorRef, bool* should) { + Actor* actor = (Actor*)actorRef; + if (actor->params != ITEM00_NONE) { + return; + } + + actor->init = CustomCollectible_Init; + actor->update = CustomCollectible_Update; + actor->draw = CustomCollectible_Draw; + actor->destroy = NULL; + + // Set the rotX/rotZ back to 0, the original values can be accessed from actor->home + actor->world.rot.x = 0; + actor->world.rot.z = 0; + }); +} diff --git a/soh/soh/Enhancements/custom-collectible/CustomCollectible.h b/soh/soh/Enhancements/custom-collectible/CustomCollectible.h new file mode 100644 index 000000000..ff2549b0f --- /dev/null +++ b/soh/soh/Enhancements/custom-collectible/CustomCollectible.h @@ -0,0 +1,24 @@ +extern "C" { +#include "z64actor.h" +} + +#define CUSTOM_ITEM_FLAGS (actor->home.rot.x) +#define CUSTOM_ITEM_PARAM (actor->home.rot.z) + +namespace CustomCollectible { + +enum CustomCollectibleFlags : int16_t { + KILL_ON_TOUCH = 1 << 0, // 0000 0000 0000 0001 + GIVE_OVERHEAD = 1 << 1, // 0000 0000 0000 0010 + GIVE_ITEM_CUTSCENE = 1 << 2, // 0000 0000 0000 0100 + HIDE_TILL_OVERHEAD = 1 << 3, // 0000 0000 0000 1000 + KEEP_ON_PLAYER = 1 << 4, // 0000 0000 0001 0000 + STOP_BOBBING = 1 << 5, // 0000 0000 0010 0000 + STOP_SPINNING = 1 << 6, // 0000 0000 0100 0000 + CALLED_ACTION = 1 << 7, // 0000 0000 1000 0000 + TOSS_ON_SPAWN = 1 << 8, // 0000 0001 0000 0000 +}; +void RegisterHooks(); +EnItem00* Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc = NULL, + ActorFunc drawFunc = NULL); +}; // namespace CustomCollectible diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 58d820352..b9ee217db 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -82,6 +82,7 @@ #include "Enhancements/mods.h" #include "Enhancements/game-interactor/GameInteractor.h" #include "Enhancements/randomizer/draw.h" +#include "Enhancements/custom-collectible/CustomCollectible.h" #include #include #include @@ -760,6 +761,7 @@ extern "C" void VanillaItemTable_Init() { GET_ITEM(ITEM_NUT_UPGRADE_30, OBJECT_GI_NUTS, GID_NUTS, 0xA7, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_30), GET_ITEM(ITEM_NUT_UPGRADE_40, OBJECT_GI_NUTS, GID_NUTS, 0xA8, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_40), GET_ITEM(ITEM_BULLET_BAG_50, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG_50, 0x6C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_50), + GET_ITEM(ITEM_SHIP, OBJECT_UNSET_16E, GID_MAXIMUM, 0x00, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_SHIP), GET_ITEM_NONE, GET_ITEM_NONE, GET_ITEM_NONE // GI_MAX - if you need to add to this table insert it before this entry. @@ -1301,6 +1303,7 @@ extern "C" void InitOTR(int argc, char* argv[]) { DebugConsole_Init(); InitMods(); + CustomCollectible::RegisterHooks(); ActorDB::AddBuiltInCustomActors(); // #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer CVarClear(CVAR_GENERAL("RandomizerNewFileDropped")); diff --git a/soh/src/code/z_draw.c b/soh/src/code/z_draw.c index 19e6c2910..66ea16878 100644 --- a/soh/src/code/z_draw.c +++ b/soh/src/code/z_draw.c @@ -399,6 +399,9 @@ DrawItemTableEntry sDrawItemTable[] = { * Calls the corresponding draw function for the given draw ID */ void GetItem_Draw(PlayState* play, s16 drawId) { + if (drawId < 0 || drawId >= GID_MAXIMUM) { + return; + } sDrawItemTable[drawId].drawFunc(play, drawId); } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index ffc677fb4..c290447a4 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1873,9 +1873,19 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) { * @return u8 */ u8 Item_Give(PlayState* play, u8 item) { - // prevents getting sticks without the bag in case something got missed - if (IS_RANDO && (item == ITEM_STICK || item == ITEM_STICKS_5 || item == ITEM_STICKS_10) && - Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_STICK_BAG) && CUR_UPG_VALUE(UPG_STICKS) == 0) { + // TODO: Add ShouldItemGive + // if (!GameInteractor_ShouldItemGive(item) || item == ITEM_SHIP) { + if (item == ITEM_SHIP) { + return ITEM_NONE; + } + + //prevents getting sticks without the bag in case something got missed + if ( + IS_RANDO && + (item == ITEM_STICK || item == ITEM_STICKS_5 || item == ITEM_STICKS_10) && + Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_STICK_BAG) && + CUR_UPG_VALUE(UPG_STICKS) == 0 + ) { return item; } @@ -2476,6 +2486,11 @@ u8 Item_CheckObtainability(u8 item) { s16 slot = SLOT(item); s32 temp; + // SOH [Enhancements] Added to enable custom item gives + if (item == ITEM_SHIP) { + return ITEM_NONE; + } + if (item >= ITEM_STICKS_5) { slot = SLOT(sExtraItemBases[item - ITEM_STICKS_5]); } diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 4a2d792f5..4bae31e5d 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -7340,16 +7340,20 @@ s32 Player_ActionHandler_2(Player* this, PlayState* play) { Item_CheckObtainability(giEntry.itemId) == ITEM_NONE || IS_RANDO; // Only skip cutscenes for drops when they're items/consumables from bushes/rocks/enemies. - uint8_t isDropToSkip = - (interactedActor->id == ACTOR_EN_ITEM00 && interactedActor->params != ITEM00_HEART_PIECE && - interactedActor->params != ITEM00_SMALL_KEY && - interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY && - interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY_GI) || - interactedActor->id == ACTOR_EN_KAREBABA || interactedActor->id == ACTOR_EN_DEKUBABA; + uint8_t isDropToSkip = + ( + interactedActor->id == ACTOR_EN_ITEM00 && + interactedActor->params != ITEM00_HEART_PIECE && + interactedActor->params != ITEM00_SMALL_KEY && + interactedActor->params != ITEM00_NONE && + interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY && + interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY_GI + ) || + interactedActor->id == ACTOR_EN_KAREBABA || + interactedActor->id == ACTOR_EN_DEKUBABA; - // Skip cutscenes from picking up consumables with "Fast Pickup Text" enabled, even when the player - // never picked it up before. But only for bushes/rocks/enemies because otherwise it can lead to - // softlocks in deku mask theatre and potentially other places. + // Skip cutscenes from picking up consumables with "Fast Pickup Text" enabled, even when the player never picked it up before. + // But only for bushes/rocks/enemies because otherwise it can lead to softlocks in deku mask theatre and potentially other places. uint8_t skipItemCutscene = CVarGetInteger(CVAR_ENHANCEMENT("FastDrops"), 0) && isDropToSkip; // Same as above but for rando. Rando is different because we want to enable cutscenes for items that