Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions soh/include/z64actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,6 @@ typedef enum {
/* 0x19 */ ITEM00_BOMBS_SPECIAL,
/* 0x1A */ ITEM00_BOMBCHU,
/* 0x1B */ ITEM00_SOH_DUMMY,
/* 0x1C */ ITEM00_SOH_GIVE_ITEM_ENTRY,
/* 0x1D */ ITEM00_SOH_GIVE_ITEM_ENTRY_GI,
/* 0x1E */ ITEM00_MAX,
/* 0xFF */ ITEM00_NONE = 0xFF
} Item00Type;
Expand Down
4 changes: 3 additions & 1 deletion soh/include/z64item.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -460,7 +461,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 {
Expand Down
260 changes: 260 additions & 0 deletions soh/soh/Enhancements/custom-item/CustomItem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
#include "CustomItem.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"

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);
}

void CustomItem::RegisterHooks() {
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::ShouldActorInit>(
ACTOR_EN_ITEM00, [](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;
});
}
25 changes: 25 additions & 0 deletions soh/soh/Enhancements/custom-item/CustomItem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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
};
void RegisterHooks();
EnItem00* Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc = NULL,
ActorFunc drawFunc = NULL);
}; // namespace CustomItem
45 changes: 45 additions & 0 deletions soh/soh/Enhancements/custom-message/CustomMessageManager.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
#include "CustomMessageManager.h"
#include "CustomMessageInterfaceAddon.h"
#include "CustomMessageTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include <algorithm>
#include <stdint.h>
#include <cstring>
#include <map>
#include <spdlog/spdlog.h>
#include <variables.h>

#include "soh/util.h"

extern "C" {
#include "functions.h"

extern PlayState* gPlayState;
}

using namespace std::literals::string_literals;

static const std::unordered_map<std::string, char> textBoxSpecialCharacters = {
Expand Down Expand Up @@ -212,6 +222,19 @@ const TextBoxPosition& CustomMessage::GetTextBoxPosition() const {
return position;
}

void CustomMessage::LoadIntoFont() {
MessageContext* msgCtx = &gPlayState->msgCtx;
Font* font = &msgCtx->font;
char* buffer = font->msgBuf;
const int maxBufferSize = sizeof(font->msgBuf);

font->charTexBuf[0] = (type << 4) | position;

std::string content = GetForCurrentLanguage(MF_RAW);

msgCtx->msgLength = font->msgLength = SohUtils::CopyStringToCharBuffer(buffer, content, maxBufferSize);
}

CustomMessage CustomMessage::operator+(const CustomMessage& right) const {
std::vector<std::string> newColors = colors;
std::vector<std::string> rColors = right.GetColors();
Expand Down Expand Up @@ -761,6 +784,10 @@ std::string CustomMessage::PLAYER_NAME() {
return "\x0F"s;
}

std::string CustomMessage::SKULLS_DESTROYED() {
return "\x19"s;
}

bool CustomMessageManager::InsertCustomMessage(std::string tableID, uint16_t textID, CustomMessage messages) {
auto foundMessageTable = messageTables.find(tableID);
if (foundMessageTable == messageTables.end()) {
Expand Down Expand Up @@ -821,3 +848,21 @@ 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);
}

void CustomMessageManager::RegisterHooks() {
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnOpenText>(
TEXT_CUSTOM_MESSAGE, [&](u16* textId, bool* loadFromMessageTable) {
*loadFromMessageTable = false;
activeCustomMessage.LoadIntoFont();
});
}
Loading
Loading