summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Longstaff <JordanLongstaff@users.noreply.github.com>2026-07-11 14:01:02 -0400
committerGitHub <noreply@github.com>2026-07-11 18:01:02 +0000
commitb81d47d79507ecd38692ec3fe24f3313401362c7 (patch)
tree5d30580673541efa197607391f5ae0278749c3be
parent1ae55a0a7edc168b89e6b87037622c59961b5fae (diff)
Hookify text speed options (#6881)
-rw-r--r--soh/soh/Enhancements/Text/BetterOwl.cpp22
-rw-r--r--soh/soh/Enhancements/Text/TextSpeed.cpp90
-rw-r--r--soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h45
-rw-r--r--soh/src/code/z_message_PAL.c96
4 files changed, 179 insertions, 74 deletions
diff --git a/soh/soh/Enhancements/Text/BetterOwl.cpp b/soh/soh/Enhancements/Text/BetterOwl.cpp
new file mode 100644
index 000000000..863664fc3
--- /dev/null
+++ b/soh/soh/Enhancements/Text/BetterOwl.cpp
@@ -0,0 +1,22 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/ShipInit.hpp"
+
+extern "C" {
+#include "z64.h"
+extern PlayState* gPlayState;
+}
+
+#define CVAR_BETTER_OWL_NAME CVAR_ENHANCEMENT("BetterOwl")
+#define CVAR_BETTER_OWL_VALUE CVarGetInteger(CVAR_BETTER_OWL_NAME, 0)
+
+static void RegisterBetterOwl() {
+ COND_VB_SHOULD(VB_OWL_CHOOSE_BETTER, CVAR_BETTER_OWL_VALUE, {
+ MessageContext* msgCtx = &gPlayState->msgCtx;
+ if ((msgCtx->textId == 0x2066 || msgCtx->textId == 0x607B || msgCtx->textId == 0x10C2 ||
+ msgCtx->textId == 0x10C6 || msgCtx->textId == 0x206A)) {
+ msgCtx->choiceIndex = 1;
+ }
+ });
+}
+
+static RegisterShipInitFunc initFunc(RegisterBetterOwl, { CVAR_BETTER_OWL_NAME });
diff --git a/soh/soh/Enhancements/Text/TextSpeed.cpp b/soh/soh/Enhancements/Text/TextSpeed.cpp
new file mode 100644
index 000000000..484226548
--- /dev/null
+++ b/soh/soh/Enhancements/Text/TextSpeed.cpp
@@ -0,0 +1,90 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/ShipInit.hpp"
+
+extern "C" {
+#include "z64.h"
+extern PlayState* gPlayState;
+}
+
+// Text Speed which fills whole box in one frame
+static constexpr int32_t TEXT_SPEED_INSTANT = 6;
+
+#define CVAR_TEXT_SPEED_NAME CVAR_ENHANCEMENT("TextSpeed")
+#define CVAR_SLOW_TEXT_SPEED_NAME CVAR_ENHANCEMENT("SlowTextSpeed")
+
+#define TEXT_SPEED CVarGetInteger(CVAR_TEXT_SPEED_NAME, 1)
+#define SLOW_TEXT_SPEED CVarGetInteger(CVAR_SLOW_TEXT_SPEED_NAME, TEXT_SPEED)
+
+static bool ShouldAdvanceQuickText(u16 textPos) {
+ MessageContext* msgCtx = &gPlayState->msgCtx;
+
+ if (textPos + TEXT_SPEED < msgCtx->textDrawPos) {
+ return false;
+ }
+
+ if (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING ||
+ (msgCtx->msgMode >= MSGMODE_OCARINA_STARTING && msgCtx->msgMode < MSGMODE_SCARECROW_LONG_RECORDING_START)) {
+ return true;
+ }
+
+ return false;
+}
+
+static void FastTextCrawl(u16 textPos, bool* should) {
+ MessageContext* msgCtx = &gPlayState->msgCtx;
+ if (msgCtx->textDelay == 0) {
+ msgCtx->textDrawPos = textPos + TEXT_SPEED;
+ if (msgCtx->textDrawPos > msgCtx->decodedTextLen) {
+ msgCtx->textDrawPos = msgCtx->decodedTextLen + 1;
+ }
+ *should = true;
+ }
+}
+
+static void SlowTextCrawl(bool* should) {
+ MessageContext* msgCtx = &gPlayState->msgCtx;
+ if (msgCtx->textDelayTimer <= 0) {
+ return;
+ }
+ *should = true;
+ if (msgCtx->textDelayTimer > SLOW_TEXT_SPEED) {
+ msgCtx->textDelayTimer -= SLOW_TEXT_SPEED;
+ } else {
+ msgCtx->textDelayTimer = 0;
+ }
+}
+
+static void RegisterTextSpeedModifiers() {
+ COND_VB_SHOULD(VB_ENABLE_QUICKTEXT, TEXT_SPEED > 1, {
+ u16 textPos = va_arg(args, int);
+ if (!*should && ShouldAdvanceQuickText(textPos)) {
+ *should = true;
+ }
+ });
+
+ COND_VB_SHOULD(VB_FIX_TEXT_SPEED_SOFTLOCK, TEXT_SPEED > 1, {
+ MessageContext* msgCtx = &gPlayState->msgCtx;
+ u16 nextTextPos = va_arg(args, int);
+
+ *should = !*should || (nextTextPos > msgCtx->textDrawPos);
+ if (*should) {
+ msgCtx->textDrawPos = nextTextPos;
+ }
+ });
+
+ COND_VB_SHOULD(VB_TEXT_CRAWL_FASTER, TEXT_SPEED >= TEXT_SPEED_INSTANT, {
+ MessageContext* msgCtx = &gPlayState->msgCtx;
+ msgCtx->textDrawPos = msgCtx->decodedTextLen + 1;
+ *should = true;
+ });
+
+ COND_VB_SHOULD(VB_TEXT_CRAWL_FASTER, TEXT_SPEED > 1 && TEXT_SPEED < TEXT_SPEED_INSTANT, {
+ u16 textPos = va_arg(args, int);
+ FastTextCrawl(textPos, should);
+ });
+
+ COND_VB_SHOULD(VB_TEXT_CRAWL_FASTER, SLOW_TEXT_SPEED > 1 && TEXT_SPEED < TEXT_SPEED_INSTANT,
+ { SlowTextCrawl(should); });
+}
+
+static RegisterShipInitFunc initFunc(RegisterTextSpeedModifiers, { CVAR_TEXT_SPEED_NAME, CVAR_SLOW_TEXT_SPEED_NAME });
diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h
index c111ee924..1a38871a9 100644
--- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h
+++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h
@@ -662,19 +662,30 @@ typedef enum {
// #### `result`
// ```c
- // (Message_GetState(&play->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(play)
+ // true
// ```
// #### `args`
- // - None
- VB_END_GERUDO_MEMBERSHIP_TALK,
+ // - `*EnArrow`
+ VB_EN_ARROW_MAGIC_CONSUMPTION,
// #### `result`
// ```c
- // true
+ // i + 1 == msgCtx->textDrawPos &&
+ // (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING ||
+ // (msgCtx->msgMode >= MSGMODE_OCARINA_STARTING &&
+ // msgCtx->msgMode < MSGMODE_SCARECROW_LONG_RECORDING_START))
// ```
// #### `args`
- // - `*EnArrow`
- VB_EN_ARROW_MAGIC_CONSUMPTION,
+ // - `u16` (text position)
+ VB_ENABLE_QUICKTEXT,
+
+ // #### `result`
+ // ```c
+ // (Message_GetState(&play->msgCtx) == TEXT_STATE_EVENT) && Message_ShouldAdvance(play)
+ // ```
+ // #### `args`
+ // - None
+ VB_END_GERUDO_MEMBERSHIP_TALK,
// #### `result`
// ```c
@@ -718,6 +729,12 @@ typedef enum {
VB_FAIRY_HEAL,
// #### `result`
+ // True if the next text position must be beyond the current position; false otherwise
+ // #### `args`
+ // - `u16` (next text position)
+ VB_FIX_TEXT_SPEED_SOFTLOCK,
+
+ // #### `result`
// ```c
// false
// ```
@@ -1660,6 +1677,14 @@ typedef enum {
// #### `result`
// ```c
+ // false
+ // ```
+ // #### `args`
+ // - `None`
+ VB_OWL_CHOOSE_BETTER,
+
+ // #### `result`
+ // ```c
// this->actor.xzDistToPlayer < targetDist
// ```
// #### `args`
@@ -2642,6 +2667,14 @@ typedef enum {
// #### `result`
// ```c
+ // false
+ // ```
+ // #### `args`
+ // - `u16` (text position)
+ VB_TEXT_CRAWL_FASTER,
+
+ // #### `result`
+ // ```c
// (this->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) && (this->heldActor != NULL) &&
// CHECK_BTN_ANY(sControlInput->press.button, buttonsToCheck)
// ```
diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c
index 4c690445a..8dac99c73 100644
--- a/soh/src/code/z_message_PAL.c
+++ b/soh/src/code/z_message_PAL.c
@@ -15,9 +15,6 @@
#include "soh/SaveManager.h"
#include "soh/ResourceManagerHelpers.h"
-// SOH [Enhancement] Text Speed which fills whole box in one frame
-#define TEXT_SPEED_INSTANT 6
-
// #region SOH [NTSC] - Allows custom messages to work on japanese
static bool sDisplayNextMessageAsEnglish = false;
static u8 sLastLanguage = LANGUAGE_ENG;
@@ -956,7 +953,6 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
u16 i;
u16 charTexIdx;
Gfx* gfx = *gfxP;
- int gTextSpeed, gSlowTextSpeed;
play->msgCtx.textPosX = R_TEXT_INIT_XPOS;
play->msgCtx.textPosY = R_TEXT_INIT_YPOS;
@@ -970,9 +966,6 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
msgCtx->unk_E3D0 = 0;
charTexIdx = 0;
- gTextSpeed = CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1);
- gSlowTextSpeed = CVarGetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), gTextSpeed);
-
for (i = 0; i < msgCtx->textDrawPos; i++) {
character = msgCtx->msgBufDecodedWide[i];
@@ -1012,11 +1005,7 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
msgCtx->textPosX += msgCtx->msgBufDecodedWide[++i];
break;
case MESSAGE_TEXTID_JPN:
- // #region SOH [General] Fixes softlock for higher text speeds
- if (gTextSpeed > 1) {
- msgCtx->textDrawPos = i + 1;
- }
- // #endregion
+ GameInteractor_Should(VB_FIX_TEXT_SPEED_SOFTLOCK, false, i + 1);
msgCtx->textboxEndType = TEXTBOX_ENDTYPE_HAS_NEXT;
if (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING) {
Audio_PlaySoundGeneral(0, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
@@ -1027,10 +1016,12 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
*gfxP = gfx;
return;
case MESSAGE_QUICKTEXT_ENABLE_JPN:
- if ((i + 1 == msgCtx->textDrawPos || (gTextSpeed > 1 && i + gTextSpeed >= msgCtx->textDrawPos)) &&
- (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING ||
- (msgCtx->msgMode >= MSGMODE_OCARINA_STARTING &&
- msgCtx->msgMode < MSGMODE_SCARECROW_LONG_RECORDING_START))) {
+ if (GameInteractor_Should(VB_ENABLE_QUICKTEXT,
+ i + 1 == msgCtx->textDrawPos &&
+ (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING ||
+ (msgCtx->msgMode >= MSGMODE_OCARINA_STARTING &&
+ msgCtx->msgMode < MSGMODE_SCARECROW_LONG_RECORDING_START)),
+ i)) {
j = i;
while (true) {
character = msgCtx->msgBufDecodedWide[j];
@@ -1043,7 +1034,8 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
break;
}
}
- if (j > msgCtx->textDrawPos) {
+
+ if (GameInteractor_Should(VB_FIX_TEXT_SPEED_SOFTLOCK, true, j)) {
i = j - 1;
msgCtx->textDrawPos = j;
}
@@ -1151,12 +1143,7 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
msgCtx->choiceTextId = msgCtx->textId;
msgCtx->stateTimer = 4;
msgCtx->choiceIndex = 0;
- if (CVarGetInteger(CVAR_ENHANCEMENT("BetterOwl"), 0)) {
- if ((msgCtx->textId == 0x2066 || msgCtx->textId == 0x607B || msgCtx->textId == 0x10C2 ||
- msgCtx->textId == 0x10C6 || msgCtx->textId == 0x206A)) {
- msgCtx->choiceIndex = 1;
- }
- }
+ GameInteractor_Should(VB_OWL_CHOOSE_BETTER, false);
Font_LoadMessageBoxIcon(font, TEXTBOX_ICON_ARROW);
}
break;
@@ -1184,11 +1171,7 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
*gfxP = gfx;
return;
case MESSAGE_OCARINA_JPN:
- // #region SOH [General] Fixes softlock for higher text speeds
- if (gTextSpeed > 1) {
- msgCtx->textDrawPos = i + 1;
- }
- // #endregion
+ GameInteractor_Should(VB_FIX_TEXT_SPEED_SOFTLOCK, false, i + 1);
if (i + 1 == msgCtx->textDrawPos) {
Message_HandleOcarina(play);
*gfxP = gfx;
@@ -1279,20 +1262,13 @@ void Message_DrawTextJPN(PlayState* play, Gfx** gfxP) {
}
}
- if (gTextSpeed >= TEXT_SPEED_INSTANT) {
- msgCtx->textDrawPos = msgCtx->decodedTextLen + 1;
- } else if (msgCtx->textDelay == 0) {
- msgCtx->textDrawPos = i + gTextSpeed;
- if (msgCtx->textDrawPos > msgCtx->decodedTextLen) {
- msgCtx->textDrawPos = msgCtx->decodedTextLen + 1;
- }
+ if (GameInteractor_Should(VB_TEXT_CRAWL_FASTER, false, i)) {
+ // Logic handled within hooks
} else if (msgCtx->textDelayTimer == 0) {
msgCtx->textDrawPos = i + 1;
msgCtx->textDelayTimer = msgCtx->textDelay;
- } else if (msgCtx->textDelayTimer <= gSlowTextSpeed) {
- msgCtx->textDelayTimer = 0;
} else {
- msgCtx->textDelayTimer -= gSlowTextSpeed;
+ msgCtx->textDelayTimer--;
}
*gfxP = gfx;
}
@@ -1309,13 +1285,12 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
u16 i;
u16 sfxHi;
u16 charTexIdx;
- int gTextSpeed, gSlowTextSpeed;
Font* font = &play->msgCtx.font;
Gfx* gfx = *gfxP;
play->msgCtx.textPosX = R_TEXT_INIT_XPOS;
- if (sTextIsCredits == false) {
+ if (!sTextIsCredits) {
msgCtx->textPosY = R_TEXT_INIT_YPOS;
} else {
msgCtx->textPosY = YREG(1);
@@ -1332,9 +1307,6 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
msgCtx->unk_E3D0 = 0;
charTexIdx = 0;
- gTextSpeed = CVarGetInteger(CVAR_ENHANCEMENT("TextSpeed"), 1);
- gSlowTextSpeed = CVarGetInteger(CVAR_ENHANCEMENT("SlowTextSpeed"), gTextSpeed);
-
for (i = 0; i < msgCtx->textDrawPos; i++) {
character = msgCtx->msgBufDecoded[i];
@@ -1384,10 +1356,12 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
*gfxP = gfx;
return;
case MESSAGE_QUICKTEXT_ENABLE:
- if ((i + 1 == msgCtx->textDrawPos || (gTextSpeed > 1 && i + gTextSpeed >= msgCtx->textDrawPos)) &&
- (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING ||
- (msgCtx->msgMode >= MSGMODE_OCARINA_STARTING &&
- msgCtx->msgMode < MSGMODE_SCARECROW_LONG_RECORDING_START))) {
+ if (GameInteractor_Should(VB_ENABLE_QUICKTEXT,
+ i + 1 == msgCtx->textDrawPos &&
+ (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING ||
+ (msgCtx->msgMode >= MSGMODE_OCARINA_STARTING &&
+ msgCtx->msgMode < MSGMODE_SCARECROW_LONG_RECORDING_START)),
+ i)) {
j = i;
while (true) {
lookAheadCharacter = msgCtx->msgBufDecoded[j];
@@ -1404,12 +1378,11 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
break;
}
}
- if (j > msgCtx->textDrawPos) {
+
+ if (GameInteractor_Should(VB_FIX_TEXT_SPEED_SOFTLOCK, true, j)) {
i = j - 1;
msgCtx->textDrawPos = j;
}
-
- if (character) {}
}
case MESSAGE_QUICKTEXT_DISABLE:
break;
@@ -1526,12 +1499,7 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
msgCtx->choiceTextId = msgCtx->textId;
msgCtx->stateTimer = 4;
msgCtx->choiceIndex = 0;
- if (CVarGetInteger(CVAR_ENHANCEMENT("BetterOwl"), 0)) {
- if ((msgCtx->textId == 0x2066 || msgCtx->textId == 0x607B || msgCtx->textId == 0x10C2 ||
- msgCtx->textId == 0x10C6 || msgCtx->textId == 0x206A)) {
- msgCtx->choiceIndex = 1;
- }
- }
+ GameInteractor_Should(VB_OWL_CHOOSE_BETTER, false);
Font_LoadMessageBoxIcon(font, TEXTBOX_ICON_ARROW);
}
break;
@@ -1559,11 +1527,7 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
*gfxP = gfx;
return;
case MESSAGE_OCARINA:
- // #region SOH [General] Fixes softlock for higher text speeds
- if (gTextSpeed > 1) {
- msgCtx->textDrawPos = i + 1;
- }
- // #endregion
+ GameInteractor_Should(VB_FIX_TEXT_SPEED_SOFTLOCK, false, i + 1);
if (i + 1 == msgCtx->textDrawPos) {
Message_HandleOcarina(play);
*gfxP = gfx;
@@ -1631,17 +1595,13 @@ void Message_DrawText(PlayState* play, Gfx** gfxP) {
break;
}
}
- if (gTextSpeed >= TEXT_SPEED_INSTANT) {
- msgCtx->textDrawPos = msgCtx->decodedTextLen + 1;
- } else if (msgCtx->textDelay == 0) {
- msgCtx->textDrawPos = i + gTextSpeed;
+ if (GameInteractor_Should(VB_TEXT_CRAWL_FASTER, false, i)) {
+ // Logic handled within hooks
} else if (msgCtx->textDelayTimer == 0) {
msgCtx->textDrawPos = i + 1;
msgCtx->textDelayTimer = msgCtx->textDelay;
- } else if (msgCtx->textDelayTimer <= gSlowTextSpeed) {
- msgCtx->textDelayTimer = 0;
} else {
- msgCtx->textDelayTimer -= gSlowTextSpeed;
+ msgCtx->textDelayTimer--;
}
*gfxP = gfx;
}