diff --git a/soh/include/z64item.h b/soh/include/z64item.h index 9bbb8bb30f2..2f8744fb0ca 100644 --- a/soh/include/z64item.h +++ b/soh/include/z64item.h @@ -310,6 +310,7 @@ typedef enum { /* 0x9A */ ITEM_NUT_UPGRADE_30, /* 0x9B */ ITEM_NUT_UPGRADE_40, /* */ ITEM_ROCS_FEATHER, + /* */ ITEM_SHIP, // SOH [Enhancement] Added to enable custom item gives /* 0xFC */ ITEM_LAST_USED = 0xFC, /* 0xFE */ ITEM_NONE_FE = 0xFE, /* 0xFF */ ITEM_NONE = 0xFF @@ -461,7 +462,8 @@ typedef enum { /* 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 + /* 0x7E */ GI_SHIP, // SOH [Enhancement] Added to enable custom item gives + /* 0x7E */ GI_MAX } GetItemID; typedef enum { diff --git a/soh/soh/Enhancements/custom-item/CustomItem.cpp b/soh/soh/Enhancements/custom-item/CustomItem.cpp new file mode 100644 index 00000000000..6b400b370bf --- /dev/null +++ b/soh/soh/Enhancements/custom-item/CustomItem.cpp @@ -0,0 +1,262 @@ +#include "CustomItem.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "z64actor.h" +#include "functions.h" +#include "variables.h" +#include "macros.h" +extern PlayState* gPlayState; +} + +// #region These were copied from z_en_item00.c +static ColliderCylinderInit sCylinderInit = { + { + COLTYPE_NONE, + AT_NONE, + AC_ON | AT_TYPE_PLAYER, + OC1_NONE, + OC2_NONE, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK0, + { 0x00000000, 0x00, 0x00 }, + { 0x00000010, 0x00, 0x00 }, + TOUCH_NONE | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_NONE, + }, + { 10, 30, 0, { 0, 0, 0 } }, +}; + +static InitChainEntry sInitChain[] = { + ICHAIN_F32(targetArrowOffset, 2000, ICHAIN_STOP), +}; +// #endregion + +EnItem00* CustomItem::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, false); + EnItem00* enItem00 = (EnItem00*)actor; + + if (actionFunc != NULL) { + enItem00->actionFunc = (EnItem00ActionFunc)actionFunc; + } + + if (drawFunc != NULL) { + actor->draw = drawFunc; + } + + return enItem00; +} + +void CustomItem_Init(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + if (CUSTOM_ITEM_FLAGS & CustomItem::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 & CustomItem::HIDE_TILL_OVERHEAD) { + Actor_SetScale(actor, 0.0f); + } else { + Actor_SetScale(actor, 0.015f); + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::KEEP_ON_PLAYER) { + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::TOSS_ON_SPAWN) { + actor->velocity.y = 8.0f; + actor->speedXZ = 2.0f; + actor->gravity = -1.4f; + actor->world.rot.y = Rand_ZeroOne() * 40000.0f; + } + + Actor_ProcessInitChain(actor, sInitChain); + Collider_InitCylinder(play, &enItem00->collider); + Collider_SetCylinder(play, &enItem00->collider, actor, &sCylinderInit); + + 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 CustomItem_Draw(Actor* actor, PlayState* play) { + Matrix_Scale(30.0f, 30.0f, 30.0f, MTXMODE_APPLY); + GetItem_Draw(play, CUSTOM_ITEM_PARAM); +} + +// Once the item is touched we need to clear movement vars so the item doesn't sink in the players hands/above head +void CustomItem_ItemTouched(Actor* actor, PlayState* play) { + actor->speedXZ = 0.0f; + actor->velocity.y = 0.0f; + actor->gravity = 0.0f; + actor->shape.yOffset = 1250.0f; +} + +void CustomItem_Update(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + Player* player = GET_PLAYER(play); + + if (!(CUSTOM_ITEM_FLAGS & CustomItem::STOP_SPINNING)) { + actor->shape.rot.y += 960; + } + + if (!(CUSTOM_ITEM_FLAGS & CustomItem::STOP_BOBBING)) { + actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::HIDE_TILL_OVERHEAD) { + Actor_SetScale(actor, 0.0f); + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::KEEP_ON_PLAYER) { + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + // Player range check accounting for goron rolling behavior. Matches EnItem00 range check. + bool playerInRangeOfPickup = (actor->xzDistToPlayer <= 30.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(50.0f)); + + if (CUSTOM_ITEM_FLAGS & CustomItem::KILL_ON_TOUCH) { + // Pretty self explanatory, if the player is within range, kill the actor and call the action function + if (playerInRangeOfPickup) { + if (enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION; + } + Actor_Kill(actor); + } + } else if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_OVERHEAD) { + // If the item hasn't been picked up (unk_15A == -1) and the player is within range + if (enItem00->unk_15A == -1 && playerInRangeOfPickup) { + // Fire the action function + if (enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomItem::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 |= CustomItem::STOP_BOBBING; + CUSTOM_ITEM_FLAGS |= CustomItem::KEEP_ON_PLAYER; + CustomItem_ItemTouched(actor, play); + // Move to player right away on this frame + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + // 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 = LINK_IS_ADULT ? 60.0f : 45.0f; + + // 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 & CustomItem::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, 80.0f); + } else { + if (enItem00->unk_15A == -1) { + // actor->shape.yOffset = 1250.0f; + CUSTOM_ITEM_FLAGS |= CustomItem::STOP_BOBBING; + // Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + CUSTOM_ITEM_FLAGS |= CustomItem::KEEP_ON_PLAYER; + // Actor_SetScale(actor, 0.0f); + CUSTOM_ITEM_FLAGS |= CustomItem::HIDE_TILL_OVERHEAD; + CustomItem_ItemTouched(actor, play); + } + + // 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 |= CustomItem::CALLED_ACTION; + } + // Override the bobbing animation to be a fixed height + actor->shape.yOffset = 900.0f; + Actor_SetScale(actor, 0.007f); + + // Account for the different heights of the player forms + f32 height = LINK_IS_ADULT ? 60.0f : 45.0f; + + 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_MoveXZGravity(actor); + Actor_UpdateBgCheckInfo(play, actor, 20.0f, 15.0f, 15.0f, 0x1D); + } + + if (actor->bgCheckFlags & 0x0003) { + actor->speedXZ = 0.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::ABLE_TO_BOOMERANG) { + Collider_UpdateCylinder(actor, &enItem00->collider); + CollisionCheck_SetAC(play, &play->colChkCtx, &enItem00->collider.base); + } +} + +void CustomItem_Destroy(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + Collider_DestroyCylinder(play, &enItem00->collider); +} + +static RegisterShipInitFunc initFunc( + []() { + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_ITEM00, true, [](void* actorRef, bool* should) { + Actor* actor = (Actor*)actorRef; + if (actor->params != ITEM00_NONE) { + return; + } + + actor->init = CustomItem_Init; + actor->update = CustomItem_Update; + actor->draw = CustomItem_Draw; + actor->destroy = CustomItem_Destroy; + + // 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; + actor->shape.rot.x = 0; + actor->shape.rot.y = 0; + actor->shape.rot.z = 0; + }); + }, + {}); diff --git a/soh/soh/Enhancements/custom-item/CustomItem.h b/soh/soh/Enhancements/custom-item/CustomItem.h new file mode 100644 index 00000000000..14d9b740fd6 --- /dev/null +++ b/soh/soh/Enhancements/custom-item/CustomItem.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 CustomItem { + +enum CustomItemFlags : 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 + ABLE_TO_BOOMERANG = 1 << 9, // 0000 0010 0000 0000 +}; +EnItem00* Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc = NULL, + ActorFunc drawFunc = NULL); +}; // namespace CustomItem diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp index 18f55e75518..21b17f16246 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp +++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp @@ -1,5 +1,7 @@ #include "CustomMessageManager.h" #include "CustomMessageInterfaceAddon.h" +#include "CustomMessageTypes.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" #include #include #include @@ -7,6 +9,15 @@ #include #include #include +#include "soh/ShipInit.hpp" + +#include "soh/util.h" + +extern "C" { +#include "functions.h" + +extern PlayState* gPlayState; +} using namespace std::literals::string_literals; @@ -286,18 +297,9 @@ void CustomMessage::LoadIntoFont() { char* buffer = font->msgBuf; const int maxBufferSize = sizeof(font->msgBuf); font->charTexBuf[0] = (type << 4) | position; - switch (gSaveContext.language) { - case LANGUAGE_FRA: - msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(GetFrench(MF_RAW), buffer, maxBufferSize); - break; - case LANGUAGE_GER: - msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(GetGerman(MF_RAW), buffer, maxBufferSize); - break; - case LANGUAGE_ENG: - default: - msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(GetEnglish(MF_RAW), buffer, maxBufferSize); - break; - } + + std::string content = GetForCurrentLanguage(MF_RAW); + msgCtx->msgLength = font->msgLength = CopyStringToCharBuffer(content, buffer, maxBufferSize); } void CustomMessage::Replace(std::string&& oldStr, std::string&& newStr) { @@ -872,3 +874,22 @@ bool CustomMessageManager::AddCustomMessageTable(std::string tableID) { CustomMessageTable newMessageTable; return messageTables.emplace(tableID, newMessageTable).second; } + +void CustomMessageManager::SetActiveCustomMessage(CustomMessage message) { + activeCustomMessage = message; +} + +void CustomMessageManager::StartTextbox(CustomMessage message) { + activeCustomMessage = message; + + Message_StartTextbox(gPlayState, TEXT_CUSTOM_MESSAGE, &GET_PLAYER(gPlayState)->actor); +} + +static RegisterShipInitFunc initFunc( + []() { + COND_ID_HOOK(OnOpenText, TEXT_CUSTOM_MESSAGE, true, [](u16* textId, bool* loadFromMessageTable) { + *loadFromMessageTable = false; + CustomMessageManager::Instance->activeCustomMessage.LoadIntoFont(); + }); + }, + {}); diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.h b/soh/soh/Enhancements/custom-message/CustomMessageManager.h index 4a0a306bce4..8411b79d8cf 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageManager.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.h @@ -251,6 +251,7 @@ class CustomMessageManager { public: static CustomMessageManager* Instance; + CustomMessage activeCustomMessage; CustomMessageManager() = default; @@ -312,6 +313,22 @@ class CustomMessageManager { * already exists.) */ bool AddCustomMessageTable(std::string tableID); + + /** + * @brief Sets the active custom message, which will be used the next time + * TEXT_CUSTOM_MESSAGE is used for a text box. + * + * @param message the message to set as active + */ + void SetActiveCustomMessage(CustomMessage message); + + /** + * @brief Displays a custom message in a textbox. This is the same as calling + * SetActiveCustomMessage and then beginning a textbox with TEXT_CUSTOM_MESSAGE. + * + * @param message the message to set as active + */ + void StartTextbox(CustomMessage message); }; class MessageNotFoundException : public std::exception { diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 33e79bf07a3..cf25d78b2d2 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -31,6 +31,7 @@ typedef enum { TEXT_PURPLE_RUPEE = 0x00F1, TEXT_HUGE_RUPEE = 0x00F2, TEXT_RANDOMIZER_CUSTOM_ITEM = 0x00F8, + TEXT_CUSTOM_MESSAGE = 0x0109, TEXT_NAVI_DEKU_TREE_SUMMONS = 0x0140, TEXT_NAVI_CMON_BE_BRAVE = 0x0141, TEXT_NAVI_VISIT_THE_PRINCESS = 0x0142, diff --git a/soh/soh/Enhancements/game-interactor/GIEventQueue.cpp b/soh/soh/Enhancements/game-interactor/GIEventQueue.cpp new file mode 100644 index 00000000000..a64fa09114c --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GIEventQueue.cpp @@ -0,0 +1,99 @@ +#include "GameInteractor.h" +#include "soh/Enhancements/custom-item/CustomItem.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "macros.h" +#include "functions.h" + +extern SaveContext gSaveContext; +extern PlayState* gPlayState; +} + +void ProcessEvents() { + Player* player = GET_PLAYER(gPlayState); + + // If the player has a message active, stop + if (gPlayState->msgCtx.msgMode != 0) { + return; + } + + // If the player is in a blocking cutscene, stop + if (Player_InBlockingCsMode(gPlayState, player)) { + return; + } + + // If player is dead, stop + if (player->stateFlags1 & PLAYER_STATE1_DEAD) { + return; + } + + // If there is an event active, stop + const auto& currentEvent = GameInteractor::Instance->currentEvent; + if (auto e = std::get_if(¤tEvent)) { + // no-op + } else { + return; + } + + // If there are no events that need to happen, stop + if (GameInteractor::Instance->events.empty()) { + return; + } + + GameInteractor::Instance->currentEvent = GameInteractor::Instance->events.front(); + const auto& nextEvent = GameInteractor::Instance->currentEvent; + + if (auto e = std::get_if(&nextEvent)) { + EnItem00* enItem00; + // If the player is climbing or in the air, deliver the item without a cutscene but freeze the player + if (!e->showGetItemCutscene || + (player->stateFlags1 & + (PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE | + PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON | + PLAYER_STATE1_CLIMBING_LADDER | PLAYER_STATE1_IN_WATER)) || + (Player_GetExplosiveHeld(player) > -1)) { + enItem00 = CustomItem::Spawn( + player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, + CustomItem::GIVE_OVERHEAD | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param, + [](Actor* actor, PlayState* play) { + Player* player = GET_PLAYER(gPlayState); + const auto& nextEvent = GameInteractor::Instance->currentEvent; + if (auto e = std::get_if(&nextEvent)) { + e->giveItem(actor, play); + if (e->showGetItemCutscene) { + player->actor.freezeTimer = 30; + } + } + }, + e->drawItem); + } else { + enItem00 = CustomItem::Spawn( + player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, + CustomItem::GIVE_ITEM_CUTSCENE | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param, + e->giveItem, e->drawItem); + } + enItem00->actor.destroy = [](Actor* actor, PlayState* play) { + if (!(CUSTOM_ITEM_FLAGS & CustomItem::CALLED_ACTION)) { + // Event was not handled, requeue it + GameInteractor::Instance->events.push_back(GameInteractor::Instance->currentEvent); + } + + GameInteractor::Instance->currentEvent = GIEventNone{}; + }; + } + + GameInteractor::Instance->events.erase(GameInteractor::Instance->events.begin()); +} + +static RegisterShipInitFunc initFunc( + []() { + COND_HOOK(OnGameStateMainStart, true, []() { + // Cleanup all hooks at the start of each frame + GameInteractor::Instance->RemoveAllQueuedHooks(); + }); + + COND_HOOK(OnPlayerUpdate, true, ProcessEvents); + }, + {}); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 8ac2d9dda39..0403a02e33f 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -108,6 +108,22 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); #pragma message("Compiling without support, the Hook Debugger will not be available") #endif +struct GIEventNone {}; + +struct GIEventGiveItem { + // Whether or not to show the get item cutscene. If true and the player is in the air, the + // player will instead be frozen for a few seconds. If this is true you _must_ call + // CustomMessage::SetActiveCustomMessage in the giveItem function otherwise you'll just see a blank message. + bool showGetItemCutscene; + // Arbitrary s16 that can be accessed from within the give/draw functions with CUSTOM_ITEM_PARAM + s16 param; + // These are run in the context of an item00 actor. This isn't super important but can be useful in some cases + ActorFunc giveItem; + ActorFunc drawItem; +}; + +typedef std::variant GIEvent; + typedef uint32_t HOOK_ID; enum HookType { @@ -225,6 +241,10 @@ class GameInteractor { static GameInteractionEffectQueryResult ApplyEffect(GameInteractionEffectBase* effect); static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect); + // EventQueue + std::vector events = {}; + GIEvent currentEvent = GIEventNone(); + // Game Hooks HOOK_ID nextHookId = 1; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 480b981c45f..b3ef20b7b3d 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -19,9 +19,6 @@ void GameInteractor_ExecuteOnExitGame(int32_t fileNum) { } void GameInteractor_ExecuteOnGameStateMainStart() { - // Cleanup all hooks at the start of each frame - GameInteractor::Instance->RemoveAllQueuedHooks(); - GameInteractor::Instance->ExecuteHooks(); } diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index b544b31b1db..91086d347d7 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1215,6 +1215,7 @@ extern "C" void VanillaItemTable_Init() { 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_NONE, GET_ITEM_NONE, + GET_ITEM(ITEM_SHIP, OBJECT_GAMEPLAY_KEEP,GID_MAXIMUM,TEXT_CUSTOM_MESSAGE,0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_NONE, GI_SHIP), GET_ITEM_NONE // GI_MAX - if you need to add to this table insert it before this entry. // clang-format on }; diff --git a/soh/src/code/z_draw.c b/soh/src/code/z_draw.c index b1b4c1cc5bb..bd8a95c9f95 100644 --- a/soh/src/code/z_draw.c +++ b/soh/src/code/z_draw.c @@ -399,6 +399,10 @@ DrawItemTableEntry sDrawItemTable[] = { * Calls the corresponding draw function for the given draw ID */ void GetItem_Draw(PlayState* play, s16 drawId) { + // SoH [Enhancements] Prevent any UB here, GID_MAXIMUM useful for overriding GI draws + 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 049f0eaf574..1a1bfb66aab 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1872,6 +1872,10 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) { * @return u8 */ u8 Item_Give(PlayState* play, u8 item) { + // SoH [Enhancements] Ignore ITEM_SHIP, used for CustomItem + 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) { @@ -2482,6 +2486,10 @@ u8 Item_CheckObtainability(u8 item) { s16 slot = SLOT(item); s32 temp; + // SoH [Enhancements] Ignore ITEM_SHIP, used for CustomItem + 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 5a027c32386..6331e2d039e 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -7347,7 +7347,7 @@ s32 Player_ActionHandler_2(Player* this, PlayState* play) { // 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_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;