Compare commits

...

3 Commits

Author SHA1 Message Date
danielryb
750866145b
Merge a6e69066b2 into 07cfe51010 2024-06-20 00:12:18 +02:00
danielryb
a6e69066b2 add toggle for double SoT 2024-06-19 23:58:43 +02:00
danielryb
2d0ce9f68d add better double SoT 2024-06-19 21:00:58 +02:00
12 changed files with 2076 additions and 39 deletions

View File

@ -268,7 +268,7 @@
data-checked="analog_camera_invert_mode" data-checked="analog_camera_invert_mode"
value="InvertNone" value="InvertNone"
id="analog_camera_inversion_none" id="analog_camera_inversion_none"
style="nav-up: #analog_cam_enabled;" style="nav-up: #analog_cam_enabled; nav-down: #dsot_enabled"
/> />
<label class="config-option__tab-label" for="analog_camera_inversion_none">None</label> <label class="config-option__tab-label" for="analog_camera_inversion_none">None</label>
@ -280,7 +280,7 @@
data-checked="analog_camera_invert_mode" data-checked="analog_camera_invert_mode"
value="InvertX" value="InvertX"
id="analog_camera_inversion_x" id="analog_camera_inversion_x"
style="nav-up: #analog_cam_disabled;" style="nav-up: #analog_cam_disabled; nav-down: #dsot_disabled"
/> />
<label class="config-option__tab-label" for="analog_camera_inversion_x">Invert X</label> <label class="config-option__tab-label" for="analog_camera_inversion_x">Invert X</label>
@ -292,7 +292,7 @@
data-checked="analog_camera_invert_mode" data-checked="analog_camera_invert_mode"
value="InvertY" value="InvertY"
id="analog_camera_inversion_y" id="analog_camera_inversion_y"
style="nav-up: #analog_cam_disabled;" style="nav-up: #analog_cam_disabled; nav-down: #dsot_disabled"
/> />
<label class="config-option__tab-label" for="analog_camera_inversion_y">Invert Y</label> <label class="config-option__tab-label" for="analog_camera_inversion_y">Invert Y</label>
@ -304,11 +304,40 @@
data-checked="analog_camera_invert_mode" data-checked="analog_camera_invert_mode"
value="InvertBoth" value="InvertBoth"
id="analog_camera_inversion_both" id="analog_camera_inversion_both"
style="nav-up: #analog_cam_disabled;" style="nav-up: #analog_cam_disabled; nav-down: #dsot_disabled"
/> />
<label class="config-option__tab-label" for="analog_camera_inversion_both">Invert Both</label> <label class="config-option__tab-label" for="analog_camera_inversion_both">Invert Both</label>
</div> </div>
</div> </div>
<!-- better double song of time -->
<div class="config-option" data-event-mouseover="set_cur_config_index(10)">
<label class="config-option__title">Better Double Song of Time</label>
<div class="config-option__list">
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(10)"
name="dsot_mode"
data-checked="dsot_mode"
value="On"
id="dsot_enabled"
style="nav-up: #analog_camera_inversion_none"
/>
<label class="config-option__tab-label" for="dsot_enabled">On</label>
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(10)"
name="dsot_mode"
data-checked="dsot_mode"
value="Off"
id="dsot_disabled"
style="nav-up: #analog_camera_inversion_x"
/>
<label class="config-option__tab-label" for="dsot_disabled">Off</label>
</div>
</div>
</div> </div>
<!-- Descriptions --> <!-- Descriptions -->
<div class="config__wrapper"> <div class="config__wrapper">
@ -362,6 +391,12 @@
<p data-if="cur_config_index == 9"> <p data-if="cur_config_index == 9">
Inverts the camera controls for the analog camera if it's enabled. <b>None</b> is the default. Inverts the camera controls for the analog camera if it's enabled. <b>None</b> is the default.
</p> </p>
<p data-if="cur_config_index == 10">
Enables hour selection for Double Song of Time.
<br/>
<br/>
To select an hour move the left analog stick in horizontal axis.
</p>
</div> </div>
</div> </div>
</form> </form>

View File

@ -79,6 +79,17 @@ namespace zelda64 {
{zelda64::AnalogCamMode::Off, "Off"} {zelda64::AnalogCamMode::Off, "Off"}
}); });
enum class DsotMode {
On,
Off,
OptionCount
};
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::DsotMode, {
{zelda64::DsotMode::On, "On"},
{zelda64::DsotMode::Off, "Off"}
});
AutosaveMode get_autosave_mode(); AutosaveMode get_autosave_mode();
void set_autosave_mode(AutosaveMode mode); void set_autosave_mode(AutosaveMode mode);
@ -86,6 +97,9 @@ namespace zelda64 {
void set_analog_cam_mode(AnalogCamMode mode); void set_analog_cam_mode(AnalogCamMode mode);
void open_quit_game_prompt(); void open_quit_game_prompt();
DsotMode get_dsot_mode();
void set_dsot_mode(DsotMode mode);
}; };
#endif #endif

403
patches/better_double_sot.c Normal file
View File

@ -0,0 +1,403 @@
#include "patches.h"
void dsot_load_day_number_texture(PlayState* play, s32 day);
void dsot_actor_fixes(PlayState* play);
bool dsot_on;
void dsot_determine_enabled(void) {
dsot_on = recomp_dsot_enabled();
}
bool dsot_enabled(void) {
return dsot_on;
}
u8 choiceHour;
void dsot_init_hour_selection(PlayState* play) {
choiceHour = (u16)(TIME_TO_HOURS_F(CURRENT_TIME - 1)) + 1;
if (choiceHour >= 24) {
choiceHour = 0;
}
if (choiceHour == 6) {
if (gSaveContext.save.day == 3) {
choiceHour = 5;
} else {
dsot_load_day_number_texture(play, gSaveContext.save.day + 1);
}
}
}
#define TIMER_INIT 10
#define TIMER_STEP_RESET 0
void dsot_handle_hour_selection(PlayState* play) {
static s8 sAnalogStickTimer = TIMER_INIT;
static s8 sPrevStickX = 0;
static bool sAnalogStickHeld = false;
Input* input = CONTROLLER1(&play->state);
if ((input->rel.stick_x >= 30) && !sAnalogStickHeld) {
u8 prevHour = choiceHour;
sAnalogStickHeld = true;
choiceHour++;
if (choiceHour >= 24) {
choiceHour = 0;
}
if ((CURRENT_DAY == 3) && (prevHour <= 5) && (choiceHour > 5)) {
choiceHour = 5;
} else if ((prevHour <= 6) && (choiceHour > 6)) {
choiceHour = 6;
} else {
Audio_PlaySfx(NA_SE_SY_CURSOR);
}
if ((choiceHour == 6) && (choiceHour > prevHour)) {
dsot_load_day_number_texture(play, gSaveContext.save.day + 1);
}
} else if ((input->rel.stick_x <= -30) && !sAnalogStickHeld) {
u8 prevHour = choiceHour;
sAnalogStickHeld = true;
choiceHour--;
if (choiceHour > 24) {
choiceHour = 23;
}
if (((CLOCK_TIME(choiceHour, 0) <= CURRENT_TIME) && (CLOCK_TIME(prevHour, 0) > CURRENT_TIME)
|| ((choiceHour > prevHour) && (CURRENT_TIME >= CLOCK_TIME(23, 0)))
|| ((choiceHour > prevHour) && (CURRENT_TIME < CLOCK_TIME(1, 0))))) {
choiceHour = prevHour;
}
else {
Audio_PlaySfx(NA_SE_SY_CURSOR);
}
if ((prevHour == 6) && (choiceHour < prevHour)) {
dsot_load_day_number_texture(play, gSaveContext.save.day);
}
} else if (ABS_ALT(input->rel.stick_x) < 30) {
sAnalogStickHeld = false;
sAnalogStickTimer = TIMER_INIT;
}
if (ABS_ALT(input->rel.stick_x) >= 30) {
if (!DECR(sAnalogStickTimer)) {
sAnalogStickHeld = false;
sAnalogStickTimer = TIMER_STEP_RESET;
}
}
if (sPrevStickX * input->rel.stick_x < 0) {
sAnalogStickHeld = false;
sAnalogStickTimer = TIMER_INIT;
}
sPrevStickX = input->rel.stick_x;
}
void dsot_cancel_hour_selection(PlayState* play) {
if (choiceHour == 6) {
dsot_load_day_number_texture(play, gSaveContext.save.day);
}
}
void dsot_advance_hour(PlayState* play) {
gSaveContext.save.time = CLOCK_TIME(choiceHour, 0);
gSaveContext.skyboxTime = CURRENT_TIME;
// Instantly enable/disable rain on day 2.
if ((CURRENT_DAY == 2) && (Environment_GetStormState(play) != STORM_STATE_OFF)) {
if ((CURRENT_TIME >= CLOCK_TIME(8, 0)) && (CURRENT_TIME < CLOCK_TIME(18, 00))) {
gWeatherMode = WEATHER_MODE_RAIN;
play->envCtx.lightningState = LIGHTNING_ON;
play->envCtx.precipitation[PRECIP_RAIN_MAX] = 60;
play->envCtx.precipitation[PRECIP_RAIN_CUR] = 60;
Environment_PlayStormNatureAmbience(play);
} else {
gWeatherMode = WEATHER_MODE_CLEAR;
play->envCtx.lightningState = LIGHTNING_OFF;
play->envCtx.precipitation[PRECIP_RAIN_MAX] = 0;
play->envCtx.precipitation[PRECIP_RAIN_CUR] = 0;
Environment_StopStormNatureAmbience(play);
}
}
// Play music/ambience.
play->envCtx.timeSeqState = TIMESEQ_FADE_DAY_BGM;
if ((CURRENT_TIME >= CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6,0))) {
SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0);
play->envCtx.timeSeqState = TIMESEQ_NIGHT_BEGIN_SFX;
} else if (CURRENT_TIME > CLOCK_TIME(17, 10)) {
SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 240);
play->envCtx.timeSeqState = TIMESEQ_NIGHT_BEGIN_SFX;
}
if ((CURRENT_TIME >= CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6, 0))) {
Audio_PlayAmbience(play->sequenceCtx.ambienceId);
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 1);
play->envCtx.timeSeqState = TIMESEQ_NIGHT_DELAY;
}
if ((CURRENT_TIME >= CLOCK_TIME(19, 0)) || (CURRENT_TIME <= CLOCK_TIME(6, 0))) {
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 0);
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 1);
play->envCtx.timeSeqState = TIMESEQ_DAY_BEGIN_SFX;
}
if ((CURRENT_TIME >= CLOCK_TIME(5, 0)) && (CURRENT_TIME <= CLOCK_TIME(6, 0))) {
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 0);
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_4 << 4 | AMBIENCE_CHANNEL_CRITTER_5, 1, 1);
play->envCtx.timeSeqState = TIMESEQ_DAY_DELAY;
}
dsot_actor_fixes(play);
}
// Loads day number texture from week_static for the three-day clock.
void dsot_load_day_number_texture(PlayState* play, s32 day) {
s16 i = day - 1;
if ((i < 0) || (i >= 3)) {
i = 0;
}
DmaMgr_SendRequest0((void*)(play->interfaceCtx.doActionSegment + 0x780),
SEGMENT_ROM_START_OFFSET(week_static, i * 0x510), 0x510);
}
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
void Interface_DrawClock(PlayState* play);
// Wrapper for Interface_DrawClock to display selected hour.
void dsot_draw_clock(PlayState* play) {
MessageContext *msgCtx = &play->msgCtx;
u8 prev_msgMode = msgCtx->msgMode;
msgCtx->msgMode = MSGMODE_NONE;
PlayState* state = (PlayState*)&play->state;
s32 prev_frameAdvanceCtx = state->frameAdvCtx.enabled;
state->frameAdvCtx.enabled = 0;
u16 prev_pauseCtx_state = play->pauseCtx.state;
play->pauseCtx.state = PAUSE_STATE_OFF;
u16 prev_pauseCtx_debugEditor = play->pauseCtx.state;
play->pauseCtx.debugEditor = DEBUG_EDITOR_NONE;
bool flag_modified = false;
if (!(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) {
play->actorCtx.flags ^= ACTORCTX_FLAG_TELESCOPE_ON;
flag_modified = true;
}
u16 prev_time = gSaveContext.save.time;
gSaveContext.save.time = CLOCK_TIME(choiceHour, 0);
Interface_DrawClock(play);
gSaveContext.save.time = prev_time;
if (flag_modified) {
play->actorCtx.flags ^= ACTORCTX_FLAG_TELESCOPE_ON;
}
play->pauseCtx.debugEditor == prev_pauseCtx_debugEditor;
play->pauseCtx.state = prev_pauseCtx_state;
state->frameAdvCtx.enabled = prev_frameAdvanceCtx;
msgCtx->msgMode = prev_msgMode;
}
#include "overlays/actors/ovl_En_Test4/z_en_test4.h"
#include "overlays/actors/ovl_Obj_Tokei_Step/z_obj_tokei_step.h"
#include "overlays/actors/ovl_Obj_Tokeidai/z_obj_tokeidai.h"
void dsot_ObjEnTest4_fix(EnTest4* this, PlayState* play);
void dsot_ObjTokeiStep_fix(ObjTokeiStep* this, PlayState* play);
void dsot_ObjTokeidai_fix(ObjTokeidai* this, PlayState* play);
// Calls actor specific fixes.
void dsot_actor_fixes(PlayState* play) {
s32 category;
Actor* actor;
Player* player = GET_PLAYER(play);
u32* categoryFreezeMaskP;
ActorListEntry* entry;
ActorContext* actorCtx = &play->actorCtx;
for (category = 0, entry = actorCtx->actorLists; category < ACTORCAT_MAX;
entry++, categoryFreezeMaskP++, category++) {
actor = entry->first;
for (actor = entry->first; actor != NULL; actor = actor->next) {
switch(actor->id) {
case ACTOR_EN_TEST4:
dsot_ObjEnTest4_fix(actor, play);
break;
case ACTOR_OBJ_TOKEI_STEP:
dsot_ObjTokeiStep_fix(actor, play);
break;
case ACTOR_OBJ_TOKEIDAI:
dsot_ObjTokeidai_fix(actor, play);
break;
}
}
}
}
#define PAST_MIDNIGHT ((CURRENT_DAY == 3) && (CURRENT_TIME < CLOCK_TIME(6, 0)) && (CURRENT_TIME > CLOCK_TIME(0, 0)))
// z_obj_en_test_4
void func_80A42198(EnTest4* this); // EnTest4_GetBellTimeOnDay3
void dsot_ObjEnTest4_fix(EnTest4* this, PlayState* play) {
this->prevTime = CURRENT_TIME;
this->prevBellTime = CURRENT_TIME;
// Change daytime to night manually if necessary.
if ((this->daytimeIndex = THREEDAY_DAYTIME_DAY) && (CURRENT_TIME > CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6, 0))) {
this->daytimeIndex = THREEDAY_DAYTIME_NIGHT;
// Re-spawn the setup actors.
play->numSetupActors = -play->numSetupActors;
}
// Setup next bell time.
if (CURRENT_DAY == 3) {
gSaveContext.save.time--;
func_80A42198(this);
gSaveContext.save.time++;
}
}
// z_obj_tokei_step
void ObjTokeiStep_InitStepsOpen(ObjTokeiStep* this);
void ObjTokeiStep_SetupDoNothingOpen(ObjTokeiStep* this);
void ObjTokeiStep_DrawOpen(Actor* thisx, PlayState* play);
void dsot_ObjTokeiStep_fix(ObjTokeiStep* this, PlayState* play) {
if (PAST_MIDNIGHT) {
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
this->dyna.actor.draw = actor_relocate(this, ObjTokeiStep_DrawOpen);
ObjTokeiStep_InitStepsOpen(this);
ObjTokeiStep_SetupDoNothingOpen(this);
DynaPoly_DisableCollision(play, &play->colCtx.dyna, this->dyna.bgId);
}
}
// z_obj_tokeidai
#define GET_CLOCK_FACE_ROTATION(currentClockHour) (TRUNCF_BINANG(currentClockHour * (0x10000 / 24.0f)))
#define GET_MINUTE_RING_OR_EXTERIOR_GEAR_ROTATION(currentClockMinute) \
(TRUNCF_BINANG(currentClockMinute * (0x10000 * 12.0f / 360)))
s32 ObjTokeidai_GetTargetSunMoonPanelRotation(void);
void ObjTokeidai_Init(Actor* thisx, PlayState* play);
void ObjTokeidai_Draw(Actor* thisx, PlayState* play);
void ObjTokeidai_ExteriorGear_Draw(Actor* thisx, PlayState* play);
void ObjTokeidai_Clock_Draw(Actor* thisx, PlayState* play);
void ObjTokeidai_Counterweight_Idle(ObjTokeidai* this, PlayState* play);
void ObjTokeidai_ExteriorGear_Idle(ObjTokeidai* this, PlayState* play);
void ObjTokeidai_TowerClock_Idle(ObjTokeidai* this, PlayState* play);
s32 dsot_ObjTokeidai_get_target_sun_moon_panel_rotation(void) {
if (CURRENT_TIME >= CLOCK_TIME(18, 0) || CURRENT_TIME < CLOCK_TIME(6, 0)) {
return 0x8000;
}
return 0;
}
void dsot_ObjTokeidai_update_clock(ObjTokeidai* this, u16 currentHour, u16 currentMinute) {
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
this->clockTime = CURRENT_TIME;
// Instantly switch to desired hour.
u16 clockHour = currentHour;
if (currentMinute == 0) {
clockHour--;
if (clockHour > 23) {
clockHour = 23;
}
}
this->clockFaceRotation = GET_CLOCK_FACE_ROTATION(clockHour);
this->clockHour = clockHour;
this->clockFaceAngularVelocity = 0;
this->clockFaceRotationTimer = 0;
// Instantly switch to desired minute.
u16 clockMinute = currentMinute - 1;
if (clockMinute > 59) {
clockMinute = 59;
}
this->minuteRingOrExteriorGearRotation = GET_MINUTE_RING_OR_EXTERIOR_GEAR_ROTATION(clockMinute);
this->clockMinute = clockMinute;
this->minuteRingOrExteriorGearAngularVelocity = 0x5A;
this->minuteRingOrExteriorGearRotationTimer = 0;
// Switch the medalion rotation immediately.
if (((currentHour != 6) && (currentHour != 18)) || (currentMinute != 0)) {
this->sunMoonPanelRotation = dsot_ObjTokeidai_get_target_sun_moon_panel_rotation();
this->sunMoonPanelAngularVelocity = 0;
}
}
void dsot_ObjTokeidai_fix(ObjTokeidai* this, PlayState* play) {
s32 type = OBJ_TOKEIDAI_TYPE(&this->actor);
u16 currentHour = TIME_TO_HOURS_F(CURRENT_TIME);
u16 currentMinute = TIME_TO_MINUTES_F(CURRENT_TIME);
switch(type) {
case OBJ_TOKEIDAI_TYPE_COUNTERWEIGHT_TERMINA_FIELD:
case OBJ_TOKEIDAI_TYPE_COUNTERWEIGHT_CLOCK_TOWN:
if (PAST_MIDNIGHT && (this->actionFunc == actor_relocate(this, ObjTokeidai_Counterweight_Idle))) {
this->actor.shape.rot.y = 0;
ObjTokeidai_Init(this, play);
}
break;
case OBJ_TOKEIDAI_TYPE_EXTERIOR_GEAR_TERMINA_FIELD:
case OBJ_TOKEIDAI_TYPE_EXTERIOR_GEAR_CLOCK_TOWN:
if (PAST_MIDNIGHT && (this->actionFunc == actor_relocate(this, ObjTokeidai_ExteriorGear_Idle))) {
dsot_ObjTokeidai_update_clock(this, 0, 0);
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
this->actor.draw = actor_relocate(this, ObjTokeidai_ExteriorGear_Draw);
ObjTokeidai_Init(this, play);
} else {
dsot_ObjTokeidai_update_clock(this, currentHour, currentMinute);
}
break;
case OBJ_TOKEIDAI_TYPE_TOWER_CLOCK_TERMINA_FIELD:
case OBJ_TOKEIDAI_TYPE_TOWER_CLOCK_CLOCK_TOWN:
if (PAST_MIDNIGHT && (this->actionFunc == actor_relocate(this, ObjTokeidai_TowerClock_Idle))) {
dsot_ObjTokeidai_update_clock(this, 0, 0);
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
this->actor.draw = actor_relocate(this, ObjTokeidai_Clock_Draw);
ObjTokeidai_Init(this, play);
} else {
dsot_ObjTokeidai_update_clock(this, currentHour, currentMinute);
}
break;
case OBJ_TOKEIDAI_TYPE_STAIRCASE_TO_ROOFTOP:
if (PAST_MIDNIGHT) {
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
this->actor.draw = actor_relocate(this, ObjTokeidai_Draw);
}
break;
case OBJ_TOKEIDAI_TYPE_WALL_CLOCK:
case OBJ_TOKEIDAI_TYPE_SMALL_WALL_CLOCK:
dsot_ObjTokeidai_update_clock(this, currentHour, currentMinute);
break;
}
}

626
patches/event_patches.c Normal file
View File

@ -0,0 +1,626 @@
#include "patches.h"
u8 func_800FE5D0(struct PlayState* play);
void Environment_UpdateTimeBasedSequence(PlayState* play) {
s32 pad;
//! FAKE:
if (gSaveContext.sceneLayer) {}
if ((play->csCtx.state == CS_STATE_IDLE) && !(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) {
switch (play->envCtx.timeSeqState) {
case TIMESEQ_DAY_BGM:
break;
case TIMESEQ_FADE_DAY_BGM:
if (CURRENT_TIME > CLOCK_TIME(17, 10)) {
SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 240);
play->envCtx.timeSeqState++;
}
break;
case TIMESEQ_NIGHT_BEGIN_SFX:
if (CURRENT_TIME >= CLOCK_TIME(18, 0)) {
play->envCtx.timeSeqState++;
}
break;
case TIMESEQ_EARLY_NIGHT_CRITTERS:
if (play->envCtx.precipitation[PRECIP_RAIN_CUR] < 9) {
Audio_PlayAmbience(play->sequenceCtx.ambienceId);
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 1);
}
play->envCtx.timeSeqState++;
break;
case TIMESEQ_NIGHT_DELAY:
if (CURRENT_TIME >= CLOCK_TIME(19, 0)) {
play->envCtx.timeSeqState++;
}
break;
case TIMESEQ_NIGHT_CRITTERS:
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_0, 1, 0);
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 1);
play->envCtx.timeSeqState++;
break;
case TIMESEQ_DAY_BEGIN_SFX:
if ((CURRENT_TIME < CLOCK_TIME(19, 0)) && (CURRENT_TIME >= CLOCK_TIME(5, 0))) {
play->envCtx.timeSeqState++;
}
break;
case TIMESEQ_MORNING_CRITTERS:
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_1 << 4 | AMBIENCE_CHANNEL_CRITTER_3, 1, 0);
Audio_SetAmbienceChannelIO(AMBIENCE_CHANNEL_CRITTER_4 << 4 | AMBIENCE_CHANNEL_CRITTER_5, 1, 1);
play->envCtx.timeSeqState++;
break;
case TIMESEQ_DAY_DELAY:
break;
default:
break;
}
}
// @recomp Don't play final hours until it's actually past midnight
if ((play->envCtx.timeSeqState != TIMESEQ_REQUEST) && (((void)0, gSaveContext.save.day) == 3) &&
(CURRENT_TIME < CLOCK_TIME(6, 0)) && !func_800FE5D0(play) && (play->transitionTrigger == TRANS_TRIGGER_OFF) &&
(play->transitionMode == TRANS_MODE_OFF) && (play->csCtx.state == CS_STATE_IDLE) &&
((play->sceneId != SCENE_00KEIKOKU) || (((void)0, gSaveContext.sceneLayer) != 1)) &&
(CutsceneManager_GetCurrentCsId() == CS_ID_NONE) &&
(AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN) != NA_BGM_FINAL_HOURS) &&
(AudioSeq_GetActiveSeqId(SEQ_PLAYER_BGM_MAIN) != NA_BGM_SONG_OF_SOARING) &&
(CURRENT_TIME > CLOCK_TIME(0, 0))) {
SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_AMBIENCE, 0);
Audio_PlaySceneSequence(NA_BGM_FINAL_HOURS, 3 - 1);
}
}
#include "overlays/actors/ovl_En_Test6/z_en_test6.h"
#include "z64quake.h"
void func_800B7298(PlayState* play, Actor* csActor, u8 csAction); // Player_SetCsActionWithHaltedActors
void EnTest6_SharedSoTCutscene(EnTest6* this, PlayState* play);
extern CutsceneData sDoubleSoTCsCamData[];
static Vec3f sSubCamUp;
struct SoTCsAmmoDrops;
typedef void (*SoTCsAmmoDropDrawFunc)(EnTest6*, PlayState*, struct SoTCsAmmoDrops*);
typedef enum SoTCsAmmoDropType {
/* 0 */ SOTCS_AMMO_DROP_NONE,
/* 1 */ SOTCS_AMMO_DROP_ARROWS,
/* 2 */ SOTCS_AMMO_DROP_BOMB,
/* 3 */ SOTCS_AMMO_DROP_DEKU_NUT,
/* 4 */ SOTCS_AMMO_DROP_DEKU_STICK,
/* 5 */ SOTCS_AMMO_DROP_RUPEE_GREEN,
/* 6 */ SOTCS_AMMO_DROP_RUPEE_BLUE
} SoTCsAmmoDropType;
typedef struct SoTCsAmmoDrops {
/* 0x00 */ SoTCsAmmoDropType type;
/* 0x04 */ f32 scale;
/* 0x08 */ Vec3f pos;
/* 0x14 */ SoTCsAmmoDropDrawFunc draw;
} SoTCsAmmoDrops; // size = 0x18
extern SoTCsAmmoDrops sSoTCsAmmoDrops[12];
typedef enum SoTCsDrawType {
/* 0 */ SOTCS_DRAW_DOUBLE_SOT,
/* 1 */ SOTCS_DRAW_RESET_CYCLE_SOT,
/* 2 */ SOTCS_DRAW_INVERTED_SOT,
/* 99 */ SOTCS_DRAW_TYPE_NONE = 99
} SoTCsDrawType;
void EnTest6_EnableMotionBlur(s16 alpha);
void EnTest6_DisableMotionBlur(void);
void EnTest6_EnableWhiteFillScreen(PlayState* play, f32 alphaRatio);
void EnTest6_DisableWhiteFillScreen(PlayState* play);
void EnTest6_StopDoubleSoTCutscene(EnTest6* this, PlayState* play);
extern Color_RGB8 sDoubleSoTCsFogColor;
extern Color_RGB8 sDoubleSoTCsAmbientColor;
extern Color_RGB8 sDoubleSoTCsDiffuseColor;
extern s16 sDoubleSoTCsFogNear;
extern s16 sDoubleSoTCsZFar;
void EnTest6_DoubleSoTCutscene(EnTest6* this, PlayState* play) {
Input* input = CONTROLLER1(&play->state);
Player* player = GET_PLAYER(play);
Camera* subCam;
s32 pad;
s16 subCamId;
s16 pad2;
if (this->timer > 115) {
this->doubleSoTEnvLerp += 0.2f;
EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp);
} else if (this->timer > 90) {
this->doubleSoTEnvLerp -= 0.05f;
EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp);
} else if (this->timer == 90) {
this->doubleSoTEnvLerp = 0.0f;
EnTest6_DisableWhiteFillScreen(play);
}
if (this->timer == 1) {
this->doubleSoTEnvLerp = 0.0f;
EnTest6_DisableWhiteFillScreen(play);
} else if (this->timer < 17) {
this->doubleSoTEnvLerp -= 0.06666666f;
EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp);
} else if (this->timer < 22) {
this->doubleSoTEnvLerp += 0.2f;
EnTest6_EnableWhiteFillScreen(play, this->doubleSoTEnvLerp);
}
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
s16* sDoubleSoTCsFogNear_ptr = actor_relocate(&this->actor, &sDoubleSoTCsFogNear);
s16* sDoubleSoTCsZFar_ptr = actor_relocate(&this->actor, &sDoubleSoTCsZFar);
Color_RGB8* sDoubleSoTCsFogColor_ptr = actor_relocate(&this->actor, &sDoubleSoTCsFogColor);
Color_RGB8* sDoubleSoTCsAmbientColor_ptr = actor_relocate(&this->actor, &sDoubleSoTCsAmbientColor);
Color_RGB8* sDoubleSoTCsDiffuseColor_ptr = actor_relocate(&this->actor, &sDoubleSoTCsDiffuseColor);
if (this->timer == 115) {
Environment_LerpAmbientColor(play, sDoubleSoTCsAmbientColor_ptr, 1.0f);
Environment_LerpDiffuseColor(play, sDoubleSoTCsDiffuseColor_ptr, 1.0f);
Environment_LerpFogColor(play, sDoubleSoTCsFogColor_ptr, 1.0f);
Environment_LerpFog(play, *sDoubleSoTCsFogNear_ptr, *sDoubleSoTCsZFar_ptr, 1.0f);
play->unk_18844 = true;
}
if (this->timer == 15) {
Environment_LerpAmbientColor(play, sDoubleSoTCsAmbientColor_ptr, 0.0f);
Environment_LerpDiffuseColor(play, sDoubleSoTCsDiffuseColor_ptr, 0.0f);
Environment_LerpFogColor(play, sDoubleSoTCsFogColor_ptr, 0.0f);
Environment_LerpFog(play, *sDoubleSoTCsFogNear_ptr, *sDoubleSoTCsZFar_ptr, 0.0f);
play->unk_18844 = false;
}
if (this->screenFillAlpha >= 20) {
Environment_LerpAmbientColor(play, sDoubleSoTCsAmbientColor_ptr, this->doubleSoTEnvLerp);
Environment_LerpDiffuseColor(play, sDoubleSoTCsDiffuseColor_ptr, this->doubleSoTEnvLerp);
Environment_LerpFogColor(play, sDoubleSoTCsFogColor_ptr, this->doubleSoTEnvLerp);
Environment_LerpFog(play, *sDoubleSoTCsFogNear_ptr, *sDoubleSoTCsZFar_ptr, this->doubleSoTEnvLerp);
play->unk_18844 = false;
}
Actor_PlaySfx_FlaggedCentered1(&player->actor, NA_SE_PL_FLYING_AIR - SFX_FLAG);
switch (this->timer) {
case 119:
EnTest6_EnableMotionBlur(50);
break;
case 115:
EnTest6_EnableMotionBlur(20);
Distortion_Request(DISTORTION_TYPE_SONG_OF_TIME);
Distortion_SetDuration(90);
this->cueId = SOTCS_CUEID_DOUBLE_INIT;
break;
case 110:
Audio_PlayFanfare(NA_BGM_SONG_OF_DOUBLE_TIME);
break;
case 38:
case 114:
this->cueId = SOTCS_CUEID_DOUBLE_WAIT;
break;
case 76:
this->cueId = SOTCS_CUEID_DOUBLE_CLOCKS_INWARD;
break;
case 61:
EnTest6_EnableMotionBlur(150);
this->cueId = SOTCS_CUEID_DOUBLE_CLOCKS_SPIN;
break;
case 51:
EnTest6_EnableMotionBlur(180);
this->cueId = SOTCS_CUEID_DOUBLE_CLOCKS_OUTWARD;
break;
case 14:
case 15:
EnTest6_EnableMotionBlur(50);
Distortion_RemoveRequest(DISTORTION_TYPE_SONG_OF_TIME);
this->cueId = SOTCS_CUEID_NONE;
break;
case 1:
EnTest6_DisableMotionBlur();
if (CHECK_EVENTINF(EVENTINF_HAS_DAYTIME_TRANSITION_CS)) {
this->cueId = SOTCS_CUEID_DOUBLE_END;
}
break;
default:
break;
}
EnTest6_SharedSoTCutscene(this, play);
if (this->timer == 115) {
subCamId = CutsceneManager_GetCurrentSubCamId(play->playerCsIds[PLAYER_CS_ID_SONG_WARP]);
subCam = Play_GetCamera(play, subCamId);
this->subCamAt = subCam->at;
this->subCamEye = subCam->eye;
this->subCamFov = subCam->fov;
CutsceneCamera_Init(subCam, &this->csCamInfo);
}
if ((this->timer <= 115) && (this->timer >= 16)) {
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
CutsceneData* sDoubleSoTCsCamData_reloc = actor_relocate(&this->actor, sDoubleSoTCsCamData);
CutsceneCamera_UpdateSplines((u8*)sDoubleSoTCsCamData_reloc, &this->csCamInfo);
} else if (this->timer < 16) {
subCamId = CutsceneManager_GetCurrentSubCamId(play->playerCsIds[PLAYER_CS_ID_SONG_WARP]);
// @recomp Manual relocation, TODO remove when the recompiler handles this automatically.
Vec3f* sSubCamUp_ptr = KaleidoManager_GetRamAddr(&sSubCamUp);
Play_SetCameraAtEyeUp(play, subCamId, &this->subCamAt, &this->subCamEye, sSubCamUp_ptr);
Play_SetCameraFov(play, subCamId, this->subCamFov);
Play_SetCameraRoll(play, subCamId, 0);
}
switch (this->timer) {
case 116:
player->actor.freezeTimer = 2;
player->actor.shape.rot.x = 0;
player->actor.shape.rot.y = 0;
player->actor.world.pos.x = 0.0f;
player->actor.world.pos.y = 0.0f;
player->actor.world.pos.z = 0.0f;
player->actor.home.pos.x = 0.0f;
player->actor.home.pos.y = 0.0f;
player->actor.home.pos.z = 0.0f;
break;
case 98:
func_800B7298(play, NULL, PLAYER_CSACTION_64);
break;
case 68:
func_800B7298(play, NULL, PLAYER_CSACTION_65);
break;
case 52:
func_800B7298(play, NULL, PLAYER_CSACTION_88);
break;
case 43:
func_800B7298(play, NULL, PLAYER_CSACTION_114);
break;
case 38:
func_800B7298(play, NULL, PLAYER_CSACTION_WAIT);
break;
case 14:
player->actor.freezeTimer = 5;
player->actor.world.pos = player->actor.home.pos = this->actor.home.pos;
player->actor.shape.rot = this->actor.home.rot;
player->actor.focus.rot.y = player->actor.shape.rot.y;
player->currentYaw = player->actor.shape.rot.y;
player->unk_ABC = 0.0f;
player->unk_AC0 = 0.0f;
player->actor.shape.yOffset = 0.0f;
break;
default:
break;
}
if ((this->screenFillAlpha > 0) && (this->screenFillAlpha < 20)) {
EnTest6_EnableWhiteFillScreen(play, this->screenFillAlpha * 0.05f);
this->screenFillAlpha++;
if (this->screenFillAlpha >= 20) {
this->timer = 15;
this->doubleSoTEnvLerp = 0.9333333f;
}
} else if ((this->timer < 96) && (this->timer > 50) &&
(CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_B))) {
this->screenFillAlpha = 1;
this->timer = 39;
SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_FANFARE, 20);
}
// @recomp Replace DSoT functionality if the option for it is enabled.
if (dsot_enabled() && this->timer == 15) {
dsot_advance_hour(play);
}
if (DECR(this->timer) == 0) {
EnTest6_StopDoubleSoTCutscene(this, play);
}
}
void Play_InitScene(PlayState* this, s32 spawn);
/**
* Processes two different cutscenes:
* return to "Dawn of the First Day" Cs, and Song of Double Time Cs
*/
void EnTest6_SharedSoTCutscene(EnTest6* this, PlayState* play) {
s32 pad[2];
Player* player = GET_PLAYER(play);
f32 yDiff;
s32 i;
s32 cueChannel;
if (Cutscene_IsCueInChannel(play, CS_CMD_ACTOR_CUE_SOTCS)) {
cueChannel = Cutscene_GetCueChannel(play, CS_CMD_ACTOR_CUE_SOTCS);
this->cueId = play->csCtx.actorCues[cueChannel]->id;
switch (this->cueId) {
case SOTCS_CUEID_DOUBLE_WAIT:
break;
case SOTCS_CUEID_DOUBLE_INIT:
this->drawType = SOTCS_DRAW_DOUBLE_SOT;
this->counter = 0;
this->clockAngle = 0;
player->actor.shape.shadowDraw = NULL;
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockSpeed = (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
} else {
this->clockSpeed = 150.0f;
}
if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) {
this->clockColorGray = play->csCtx.actorCues[cueChannel]->startPos.y;
} else {
this->clockColorGray = 38;
}
if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) {
this->clockDist = (u32)play->csCtx.actorCues[cueChannel]->startPos.z;
} else {
this->clockDist = 480.0f;
}
break;
case SOTCS_CUEID_DOUBLE_CLOCKS_INWARD:
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
}
if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) {
this->clockColorGray += (s16)play->csCtx.actorCues[cueChannel]->startPos.y;
} else {
this->clockColorGray += 6;
}
if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) {
this->clockRadialSpeed = (u32)play->csCtx.actorCues[cueChannel]->startPos.z;
} else {
this->clockRadialSpeed = -32.0f;
}
this->clockDist += this->clockRadialSpeed;
break;
case SOTCS_CUEID_DOUBLE_CLOCKS_SPIN:
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
}
if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) {
this->clockColorGray += (s16)play->csCtx.actorCues[cueChannel]->startPos.y;
} else {
this->clockColorGray -= 4;
}
break;
case SOTCS_CUEID_DOUBLE_CLOCKS_OUTWARD:
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
}
if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) {
this->clockColorGray += (s16)play->csCtx.actorCues[cueChannel]->startPos.y;
} else {
this->clockColorGray -= 8;
}
if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) {
this->clockRadialSpeed += (u32)play->csCtx.actorCues[cueChannel]->startPos.z;
} else {
this->clockRadialSpeed += 20.0f;
}
this->clockDist += this->clockRadialSpeed;
if (this->clockDist > 3500.0f) {
this->clockDist = 3500.0f;
this->cueId = SOTCS_CUEID_NONE;
}
break;
case SOTCS_CUEID_RESET_INIT:
this->drawType = SOTCS_DRAW_RESET_CYCLE_SOT;
this->counter = 0;
this->clockAngle = 0;
player->actor.shape.shadowDraw = NULL;
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockSpeed = (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
} else {
this->clockSpeed = 100.0f;
}
if (play->csCtx.actorCues[cueChannel]->startPos.y != 0) {
this->speed = (u32)play->csCtx.actorCues[cueChannel]->startPos.y;
} else {
this->speed = 20.0f;
}
if (play->csCtx.actorCues[cueChannel]->startPos.z != 0) {
this->clockDist = (u32)play->csCtx.actorCues[cueChannel]->startPos.z;
} else {
this->clockDist = 300.0f;
}
this->clockAccel = 0.0f;
break;
case SOTCS_CUEID_RESET_CLOCKS_SLOW_DOWN:
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockAccel = (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
} else {
this->clockAccel = -5.0f;
}
this->clockSpeed += this->clockAccel;
break;
case SOTCS_CUEID_RESET_CLOCKS_SPEED_UP:
if (play->csCtx.actorCues[cueChannel]->startPos.x != 0) {
this->clockAccel += (u32)play->csCtx.actorCues[cueChannel]->startPos.x;
} else {
this->clockAccel += 2.0f;
}
this->clockSpeed += this->clockAccel;
if (this->clockSpeed > 10000.0f) {
this->clockSpeed = 10000.0f;
this->cueId = SOTCS_CUEID_NONE;
}
break;
case SOTCS_CUEID_NONE:
default:
this->drawType = SOTCS_DRAW_TYPE_NONE;
return;
case SOTCS_CUEID_DOUBLE_END:
Play_SetRespawnData(&play->state, RESPAWN_MODE_RETURN, ((void)0, gSaveContext.save.entrance),
player->unk_3CE, PLAYER_PARAMS(0xFF, PLAYER_INITMODE_B), &player->unk_3C0,
player->unk_3CC);
this->drawType = SOTCS_DRAW_TYPE_NONE;
play->transitionTrigger = TRANS_TRIGGER_START;
play->nextEntrance = gSaveContext.respawn[RESPAWN_MODE_RETURN].entrance;
play->transitionType = TRANS_TYPE_FADE_BLACK;
if ((CURRENT_TIME > CLOCK_TIME(18, 0)) || (CURRENT_TIME < CLOCK_TIME(6, 0))) {
gSaveContext.respawnFlag = -0x63;
SET_EVENTINF(EVENTINF_TRIGGER_DAYTELOP);
} else {
gSaveContext.respawnFlag = 2;
}
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
return;
}
} else {
switch (this->cueId) {
case SOTCS_CUEID_DOUBLE_INIT:
this->drawType = SOTCS_DRAW_DOUBLE_SOT;
this->counter = 0;
this->clockAngle = 0;
player->actor.shape.shadowDraw = NULL;
this->clockColorGray = 38;
this->clockSpeed = 150.0f;
this->clockDist = 480.0f;
case SOTCS_CUEID_DOUBLE_WAIT:
break;
case SOTCS_CUEID_DOUBLE_CLOCKS_INWARD:
this->clockRadialSpeed = -32.0f;
this->clockColorGray += 6;
this->clockDist += -32.0f;
break;
case SOTCS_CUEID_DOUBLE_CLOCKS_SPIN:
this->clockColorGray -= 4;
break;
case SOTCS_CUEID_DOUBLE_CLOCKS_OUTWARD:
this->clockColorGray -= 8;
this->clockRadialSpeed += 20.0f;
this->clockDist += this->clockRadialSpeed;
if (this->clockDist > 3500.0f) {
this->clockDist = 3500.0f;
this->cueId = SOTCS_CUEID_NONE;
}
break;
case SOTCS_CUEID_RESET_INIT:
this->drawType = SOTCS_DRAW_RESET_CYCLE_SOT;
this->counter = 0;
this->clockAngle = 0;
player->actor.shape.shadowDraw = NULL;
this->clockSpeed = 100.0f;
this->speed = 20.0f;
this->clockDist = 300.0f;
this->clockAccel = 0.0f;
break;
case SOTCS_CUEID_RESET_CLOCKS_SLOW_DOWN:
this->clockAccel = -5.0f;
this->clockSpeed += -5.0f;
break;
case SOTCS_CUEID_RESET_CLOCKS_SPEED_UP:
this->clockAccel += 2.0f;
this->clockSpeed += this->clockAccel;
if (this->clockSpeed > 10000.0f) {
this->clockSpeed = 10000.0f;
this->cueId = SOTCS_CUEID_NONE;
}
break;
case SOTCS_CUEID_NONE:
default:
this->drawType = SOTCS_DRAW_TYPE_NONE;
return;
case SOTCS_CUEID_DOUBLE_END:
// @recomp Replace DSoT functionality if the option for it is enabled.
if (!dsot_enabled() && (CURRENT_TIME > CLOCK_TIME(12, 0))) {
Play_SetRespawnData(&play->state, RESPAWN_MODE_RETURN, ((void)0, gSaveContext.save.entrance),
player->unk_3CE, PLAYER_PARAMS(0xFF, PLAYER_INITMODE_B), &player->unk_3C0,
player->unk_3CC);
this->drawType = SOTCS_DRAW_TYPE_NONE;
play->transitionTrigger = TRANS_TRIGGER_START;
play->nextEntrance = gSaveContext.respawn[RESPAWN_MODE_RETURN].entrance;
play->transitionType = TRANS_TYPE_FADE_BLACK;
gSaveContext.respawnFlag = 2;
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
return;
}
}
SoTCsAmmoDrops* sSoTCsAmmoDrops_reloc = KaleidoManager_GetRamAddr(sSoTCsAmmoDrops);
if (this->drawType == SOTCS_DRAW_RESET_CYCLE_SOT) {
for (i = 0; i < ARRAY_COUNT(sSoTCsAmmoDrops_reloc); i++) {
sSoTCsAmmoDrops_reloc[i].pos.x += 2.0f * ((2.0f * Rand_ZeroOne()) - 1.0f);
sSoTCsAmmoDrops_reloc[i].pos.z += 2.0f * ((2.0f * Rand_ZeroOne()) - 1.0f);
sSoTCsAmmoDrops_reloc[i].pos.y += 3.0f;
if (player->actor.world.pos.y < sSoTCsAmmoDrops_reloc[i].pos.y) {
yDiff = sSoTCsAmmoDrops_reloc[i].pos.y - player->actor.world.pos.y;
if (yDiff > 550.0f) {
yDiff = 1.0f;
} else {
yDiff /= 550.0f;
}
sSoTCsAmmoDrops_reloc[i].scale = SQ(yDiff);
} else {
sSoTCsAmmoDrops_reloc[i].scale = -10.0f;
}
}
}
this->counter++;
}

925
patches/message_patches.c Normal file
View File

@ -0,0 +1,925 @@
#include "patches.h"
extern u8 D_801C6A70;
extern s32 sCharTexSize;
extern s32 sCharTexScale;
extern s32 D_801F6B08;
typedef enum {
/* 0 */ PAUSE_ITEM,
/* 1 */ PAUSE_MAP,
/* 2 */ PAUSE_QUEST,
/* 3 */ PAUSE_MASK,
/* 4 */ PAUSE_WORLD_MAP
} PauseMenuPage;
typedef enum {
/* 0x00 */ PAUSE_STATE_OFF,
/* 0x01 */ PAUSE_STATE_OPENING_0,
/* 0x02 */ PAUSE_STATE_OPENING_1,
/* 0x03 */ PAUSE_STATE_OPENING_2,
/* 0x04 */ PAUSE_STATE_OPENING_3,
/* 0x05 */ PAUSE_STATE_OPENING_4,
/* 0x06 */ PAUSE_STATE_MAIN, // Pause menu ready for player inputs.
/* 0x07 */ PAUSE_STATE_SAVEPROMPT,
/* 0x08 */ PAUSE_STATE_GAMEOVER_0,
/* 0x09 */ PAUSE_STATE_GAMEOVER_1,
/* 0x0A */ PAUSE_STATE_GAMEOVER_2,
/* 0x0B */ PAUSE_STATE_GAMEOVER_3,
/* 0x0C */ PAUSE_STATE_GAMEOVER_4,
/* 0x0D */ PAUSE_STATE_GAMEOVER_5,
/* 0x0E */ PAUSE_STATE_GAMEOVER_SAVE_PROMPT,
/* 0x0F */ PAUSE_STATE_GAMEOVER_7,
/* 0x10 */ PAUSE_STATE_GAMEOVER_8,
/* 0x11 */ PAUSE_STATE_GAMEOVER_CONTINUE_PROMPT,
/* 0x12 */ PAUSE_STATE_GAMEOVER_10,
/* 0x13 */ PAUSE_STATE_OWL_WARP_0,
/* 0x14 */ PAUSE_STATE_OWL_WARP_1,
/* 0x15 */ PAUSE_STATE_OWL_WARP_2,
/* 0x16 */ PAUSE_STATE_OWL_WARP_3,
/* 0x17 */ PAUSE_STATE_OWL_WARP_SELECT, // Selecting the destination
/* 0x18 */ PAUSE_STATE_OWL_WARP_CONFIRM, // Confirming the choice given
/* 0x19 */ PAUSE_STATE_OWL_WARP_6,
/* 0x1A */ PAUSE_STATE_UNPAUSE_SETUP, // Unpause
/* 0x1B */ PAUSE_STATE_UNPAUSE_CLOSE
} PauseState;
extern s16 sLastPlayedSong;
extern s16 sTextboxUpperYPositions[];
extern s16 sTextboxLowerYPositions[];
extern s16 sTextboxMidYPositions[];
extern s16 sTextboxXPositions[TEXTBOX_TYPE_MAX];
extern s16 D_801D0464[];
extern s16 D_801D045C[];
void func_80150A84(PlayState* play);
void Message_GrowTextbox(PlayState* play);
void Message_Decode(PlayState* play);
s32 Message_BombersNotebookProcessEventQueue(PlayState* play);
void Message_HandleChoiceSelection(PlayState* play, u8 numChoices);
bool Message_ShouldAdvanceSilent(PlayState* play);
void ShrinkWindow_Letterbox_SetSizeTarget(s32 target);
s32 ShrinkWindow_Letterbox_GetSizeTarget(void);
// @recomp Patched to change functionality of Double SoT.
void Message_Update(PlayState* play) {
static u8 D_801D0468 = 0;
MessageContext* msgCtx = &play->msgCtx;
SramContext* sramCtx = &play->sramCtx; // Optional
PauseContext* pauseCtx = &play->pauseCtx;
InterfaceContext* interfaceCtx = &play->interfaceCtx;
Input* input = CONTROLLER1(&play->state);
s16 avgScreenPosY;
s16 screenPosX;
u16 temp_v1_2;
s16 playerScreenPosY;
s16 actorScreenPosY;
s16 sp48;
s32 sp44;
s32 sp40;
u16 sp3E;
s16 var_v1;
u16 temp;
msgCtx->stickAdjX = input->rel.stick_x;
msgCtx->stickAdjY = input->rel.stick_y;
avgScreenPosY = 0;
// If stickAdj is held, set a delay to allow the cursor to read the next input.
// The first delay is given a longer time than all subsequent delays.
if (msgCtx->stickAdjX < -30) {
if (msgCtx->stickXRepeatState == -1) {
msgCtx->stickXRepeatTimer--;
if (msgCtx->stickXRepeatTimer < 0) {
// Allow the input to register and apply the delay for all subsequent repeated inputs
msgCtx->stickXRepeatTimer = 2;
} else {
// Cancel the current input
msgCtx->stickAdjX = 0;
}
} else {
// Allow the input to register and apply the delay for the first repeated input
msgCtx->stickXRepeatTimer = 10;
msgCtx->stickXRepeatState = -1;
}
} else if (msgCtx->stickAdjX > 30) {
if (msgCtx->stickXRepeatState == 1) {
msgCtx->stickXRepeatTimer--;
if (msgCtx->stickXRepeatTimer < 0) {
// Allow the input to register and apply the delay for all subsequent repeated inputs
msgCtx->stickXRepeatTimer = 2;
} else {
// Cancel the current input
msgCtx->stickAdjX = 0;
}
} else {
// Allow the input to register and apply the delay for the first repeated input
msgCtx->stickXRepeatTimer = 10;
msgCtx->stickXRepeatState = 1;
}
} else {
msgCtx->stickXRepeatState = 0;
}
if (msgCtx->stickAdjY < -30) {
if (msgCtx->stickYRepeatState == -1) {
msgCtx->stickYRepeatTimer--;
if (msgCtx->stickYRepeatTimer < 0) {
// Allow the input to register and apply the delay for all subsequent repeated inputs
msgCtx->stickYRepeatTimer = 2;
} else {
// Cancel the current input
msgCtx->stickAdjY = 0;
}
} else {
// Allow the input to register and apply the delay for the first repeated input
msgCtx->stickYRepeatTimer = 10;
msgCtx->stickYRepeatState = -1;
}
} else if (msgCtx->stickAdjY > 30) {
if (msgCtx->stickYRepeatState == 1) {
msgCtx->stickYRepeatTimer--;
if (msgCtx->stickYRepeatTimer < 0) {
// Allow the input to register and apply the delay for all subsequent repeated inputs
msgCtx->stickYRepeatTimer = 2;
} else {
// Cancel the current input
msgCtx->stickAdjY = 0;
}
} else {
// Allow the input to register and apply the delay for the first repeated input
msgCtx->stickYRepeatTimer = 10;
msgCtx->stickYRepeatState = 1;
}
} else {
msgCtx->stickYRepeatState = 0;
}
if (msgCtx->msgLength == 0) {
return;
}
switch (msgCtx->msgMode) {
case MSGMODE_TEXT_START:
D_801C6A70++;
temp = false;
if ((D_801C6A70 >= 4) || ((msgCtx->talkActor == NULL) && (D_801C6A70 >= 2))) {
temp = true;
}
if (temp) {
if (msgCtx->talkActor != NULL) {
Actor_GetScreenPos(play, &GET_PLAYER(play)->actor, &screenPosX, &playerScreenPosY);
Actor_GetScreenPos(play, msgCtx->talkActor, &screenPosX, &actorScreenPosY);
if (playerScreenPosY >= actorScreenPosY) {
avgScreenPosY = ((playerScreenPosY - actorScreenPosY) / 2) + actorScreenPosY;
} else {
avgScreenPosY = ((actorScreenPosY - playerScreenPosY) / 2) + playerScreenPosY;
}
} else {
msgCtx->textboxX = msgCtx->textboxXTarget;
msgCtx->textboxY = msgCtx->textboxYTarget;
}
var_v1 = msgCtx->textBoxType;
if ((u32)msgCtx->textBoxPos == 0) {
if ((play->sceneId == SCENE_UNSET_04) || (play->sceneId == SCENE_UNSET_05) ||
(play->sceneId == SCENE_UNSET_06)) {
if (avgScreenPosY < 100) {
msgCtx->textboxYTarget = sTextboxLowerYPositions[var_v1];
} else {
msgCtx->textboxYTarget = sTextboxUpperYPositions[var_v1];
}
} else {
if (avgScreenPosY < 160) {
msgCtx->textboxYTarget = sTextboxLowerYPositions[var_v1];
} else {
msgCtx->textboxYTarget = sTextboxUpperYPositions[var_v1];
}
}
} else if (msgCtx->textBoxPos == 1) {
msgCtx->textboxYTarget = sTextboxUpperYPositions[var_v1];
} else if (msgCtx->textBoxPos == 2) {
msgCtx->textboxYTarget = sTextboxMidYPositions[var_v1];
} else if (msgCtx->textBoxPos == 7) {
msgCtx->textboxYTarget = 0x9E;
} else {
msgCtx->textboxYTarget = sTextboxLowerYPositions[var_v1];
}
msgCtx->textboxXTarget = sTextboxXPositions[var_v1];
if ((gSaveContext.options.language == LANGUAGE_JPN) && !msgCtx->textIsCredits) {
msgCtx->unk11FFE[0] = (s16)(msgCtx->textboxYTarget + 7);
msgCtx->unk11FFE[1] = (s16)(msgCtx->textboxYTarget + 0x19);
msgCtx->unk11FFE[2] = (s16)(msgCtx->textboxYTarget + 0x2B);
} else {
msgCtx->unk11FFE[0] = (s16)(msgCtx->textboxYTarget + 0x14);
msgCtx->unk11FFE[1] = (s16)(msgCtx->textboxYTarget + 0x20);
msgCtx->unk11FFE[2] = (s16)(msgCtx->textboxYTarget + 0x2C);
}
if ((msgCtx->textBoxType == TEXTBOX_TYPE_4) || (msgCtx->textBoxType == TEXTBOX_TYPE_5)) {
msgCtx->msgMode = MSGMODE_TEXT_STARTING;
msgCtx->textboxX = msgCtx->textboxXTarget;
msgCtx->textboxY = msgCtx->textboxYTarget;
msgCtx->unk12008 = 0x100;
msgCtx->unk1200A = 0x40;
msgCtx->unk1200C = 0x200;
msgCtx->unk1200E = 0x200;
break;
}
Message_GrowTextbox(play);
Audio_PlaySfx_IfNotInCutscene(NA_SE_NONE);
msgCtx->stateTimer = 0;
msgCtx->msgMode = MSGMODE_TEXT_BOX_GROWING;
if (!pauseCtx->itemDescriptionOn) {
func_80150A84(play);
}
}
break;
case MSGMODE_TEXT_BOX_GROWING:
Message_GrowTextbox(play);
break;
case MSGMODE_TEXT_STARTING:
msgCtx->msgMode = MSGMODE_TEXT_NEXT_MSG;
if (!pauseCtx->itemDescriptionOn) {
if (msgCtx->currentTextId == 0xFF) {
func_8011552C(play, DO_ACTION_STOP);
} else if (msgCtx->currentTextId != 0xF8) {
func_8011552C(play, DO_ACTION_NEXT);
}
}
break;
case MSGMODE_TEXT_NEXT_MSG:
Message_Decode(play);
if (msgCtx->textFade) {
Interface_SetHudVisibility(HUD_VISIBILITY_NONE);
}
if (D_801D0468 != 0) {
msgCtx->textDrawPos = msgCtx->decodedTextLen;
D_801D0468 = 0;
}
break;
case MSGMODE_TEXT_CONTINUING:
msgCtx->stateTimer--;
if (msgCtx->stateTimer == 0) {
Message_Decode(play);
}
break;
case MSGMODE_TEXT_DISPLAYING:
if (msgCtx->textBoxType != TEXTBOX_TYPE_4) {
if (CHECK_BTN_ALL(input->press.button, BTN_B) && !msgCtx->textUnskippable) {
msgCtx->textboxSkipped = true;
msgCtx->textDrawPos = msgCtx->decodedTextLen;
} else if (CHECK_BTN_ALL(input->press.button, BTN_A) && !msgCtx->textUnskippable) {
while (true) {
temp_v1_2 = msgCtx->decodedBuffer.wchar[msgCtx->textDrawPos];
if ((temp_v1_2 == 0x10) || (temp_v1_2 == 0x12) || (temp_v1_2 == 0x1B) || (temp_v1_2 == 0x1C) ||
(temp_v1_2 == 0x1D) || (temp_v1_2 == 0x19) || (temp_v1_2 == 0xE0) || (temp_v1_2 == 0xBF) ||
(temp_v1_2 == 0x15) || (temp_v1_2 == 0x1A)) {
break;
}
msgCtx->textDrawPos++;
}
}
} else if (CHECK_BTN_ALL(input->press.button, BTN_A) && (msgCtx->textUnskippable == 0)) {
while (true) {
temp_v1_2 = msgCtx->decodedBuffer.wchar[msgCtx->textDrawPos];
if ((temp_v1_2 == 0x10) || (temp_v1_2 == 0x12) || (temp_v1_2 == 0x1B) || (temp_v1_2 == 0x1C) ||
(temp_v1_2 == 0x1D) || (temp_v1_2 == 0x19) || (temp_v1_2 == 0xE0) || (temp_v1_2 == 0xBF) ||
(temp_v1_2 == 0x15) || (temp_v1_2 == 0x1A)) {
break;
}
msgCtx->textDrawPos++;
}
}
break;
case MSGMODE_TEXT_AWAIT_INPUT:
if (Message_ShouldAdvance(play)) {
msgCtx->msgMode = MSGMODE_TEXT_DISPLAYING;
msgCtx->textDrawPos++;
}
break;
case MSGMODE_TEXT_DELAYED_BREAK:
msgCtx->stateTimer--;
if (msgCtx->stateTimer == 0) {
msgCtx->msgMode = MSGMODE_TEXT_NEXT_MSG;
}
break;
case MSGMODE_TEXT_AWAIT_NEXT:
if (Message_ShouldAdvance(play)) {
msgCtx->msgMode = MSGMODE_TEXT_NEXT_MSG;
msgCtx->msgBufPos++;
}
break;
case MSGMODE_TEXT_DONE:
if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_50) || (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_52)) {
msgCtx->stateTimer--;
if ((msgCtx->stateTimer == 0) ||
((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_52) && Message_ShouldAdvance(play))) {
if (msgCtx->nextTextId != 0xFFFF) {
Audio_PlaySfx(NA_SE_SY_MESSAGE_PASS);
Message_ContinueTextbox(play, msgCtx->nextTextId);
} else if (msgCtx->bombersNotebookEventQueueCount != 0) {
if (Message_BombersNotebookProcessEventQueue(play) == 0) {
Message_CloseTextbox(play);
}
} else {
Message_CloseTextbox(play);
}
}
} else {
if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_30) || (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_40) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_42) || (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_41)) {
return;
}
switch (msgCtx->textboxEndType) {
case TEXTBOX_ENDTYPE_55:
msgCtx->textColorAlpha += 20;
if (msgCtx->textColorAlpha >= 255) {
msgCtx->textColorAlpha = 255;
msgCtx->textboxEndType = TEXTBOX_ENDTYPE_56;
}
break;
case TEXTBOX_ENDTYPE_56:
msgCtx->stateTimer--;
if (msgCtx->stateTimer == 0) {
msgCtx->textboxEndType = TEXTBOX_ENDTYPE_57;
}
break;
case TEXTBOX_ENDTYPE_57:
msgCtx->textColorAlpha -= 20;
if (msgCtx->textColorAlpha <= 0) {
msgCtx->textColorAlpha = 0;
if (msgCtx->nextTextId != 0xFFFF) {
Audio_PlaySfx(NA_SE_SY_MESSAGE_PASS);
Message_ContinueTextbox(play, msgCtx->nextTextId);
return;
}
if (msgCtx->bombersNotebookEventQueueCount != 0) {
if (Message_BombersNotebookProcessEventQueue(play) == 0) {
Message_CloseTextbox(play);
return;
}
} else {
Message_CloseTextbox(play);
return;
}
}
break;
case TEXTBOX_ENDTYPE_10:
Message_HandleChoiceSelection(play, 1);
break;
case TEXTBOX_ENDTYPE_11:
Message_HandleChoiceSelection(play, 2);
break;
case TEXTBOX_ENDTYPE_12:
Message_HandleChoiceSelection(play, 1);
break;
default:
break;
}
if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) &&
(play->msgCtx.ocarinaMode == OCARINA_MODE_ACTIVE)) {
if (Message_ShouldAdvance(play)) {
if (msgCtx->choiceIndex == 0) {
play->msgCtx.ocarinaMode = OCARINA_MODE_WARP;
} else {
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
Message_CloseTextbox(play);
}
} else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) &&
(play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_SOT)) {
if (Message_ShouldAdvance(play)) {
if (msgCtx->choiceIndex == 0) {
Audio_PlaySfx_MessageDecide();
msgCtx->msgMode = MSGMODE_NEW_CYCLE_0;
msgCtx->decodedTextLen -= 3;
msgCtx->unk120D6 = 0;
msgCtx->unk120D4 = 0;
} else {
Audio_PlaySfx_MessageCancel();
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
Message_CloseTextbox(play);
}
}
} else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) &&
(play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_INVERTED_TIME)) {
if (Message_ShouldAdvance(play)) {
if (msgCtx->choiceIndex == 0) {
Audio_PlaySfx_MessageDecide();
if (gSaveContext.save.timeSpeedOffset == 0) {
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_INV_SOT_SLOW;
gSaveContext.save.timeSpeedOffset = -2;
} else {
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_INV_SOT_FAST;
gSaveContext.save.timeSpeedOffset = 0;
}
Message_CloseTextbox(play);
} else {
Audio_PlaySfx_MessageCancel();
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
Message_CloseTextbox(play);
}
}
} else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) &&
(play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME)) {
// @recomp Replace DSoT functionality if the option for it is enabled.
if (dsot_enabled()) {
// @recomp
dsot_handle_hour_selection(play);
if (Message_ShouldAdvance(play)) {
if (msgCtx->choiceIndex == 0) {
Audio_PlaySfx_MessageDecide();
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT;
gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF;
} else {
Audio_PlaySfx_MessageCancel();
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
// @recomp
dsot_cancel_hour_selection(play);
}
Message_CloseTextbox(play);
}
} else {
if (Message_ShouldAdvance(play)) {
if (msgCtx->choiceIndex == 0) {
Audio_PlaySfx_MessageDecide();
if (gSaveContext.save.isNight != 0) {
gSaveContext.save.time = CLOCK_TIME(6, 0);
} else {
gSaveContext.save.time = CLOCK_TIME(18, 0);
}
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_DOUBLE_SOT;
gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF;
} else {
Audio_PlaySfx_MessageCancel();
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
Message_CloseTextbox(play);
}
}
} else if ((msgCtx->textboxEndType != TEXTBOX_ENDTYPE_10) ||
(pauseCtx->state != PAUSE_STATE_OWL_WARP_CONFIRM)) {
if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) &&
(play->msgCtx.ocarinaMode == OCARINA_MODE_1B)) {
if (Message_ShouldAdvance(play)) {
if (msgCtx->choiceIndex == 0) {
Audio_PlaySfx_MessageDecide();
play->msgCtx.ocarinaMode = OCARINA_MODE_WARP_TO_ENTRANCE;
} else {
Audio_PlaySfx_MessageCancel();
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
Message_CloseTextbox(play);
}
} else if ((msgCtx->textboxEndType == TEXTBOX_ENDTYPE_60) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_61) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_10) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_11) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_50) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_52) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_55) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_56) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_57) ||
(msgCtx->textboxEndType == TEXTBOX_ENDTYPE_62)) {
//! FAKE: debug?
if (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_50) {}
} else if (pauseCtx->itemDescriptionOn) {
if ((input->rel.stick_x != 0) || (input->rel.stick_y != 0) ||
CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_B) ||
CHECK_BTN_ALL(input->press.button, BTN_START)) {
Audio_PlaySfx(NA_SE_SY_DECIDE);
Message_CloseTextbox(play);
}
} else if (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_RESTRICTED_SONG) {
if (Message_ShouldAdvanceSilent(play)) {
Audio_PlaySfx(NA_SE_SY_DECIDE);
Message_CloseTextbox(play);
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
} else if ((msgCtx->currentTextId != 0x2790) && Message_ShouldAdvanceSilent(play)) {
if (msgCtx->nextTextId != 0xFFFF) {
Audio_PlaySfx(NA_SE_SY_MESSAGE_PASS);
Message_ContinueTextbox(play, msgCtx->nextTextId);
} else if ((msgCtx->bombersNotebookEventQueueCount == 0) ||
(Message_BombersNotebookProcessEventQueue(play) != 1)) {
if (msgCtx->currentTextId == 0x579) {
gSaveContext.hudVisibility = HUD_VISIBILITY_IDLE;
}
Audio_PlaySfx(NA_SE_SY_DECIDE);
Message_CloseTextbox(play);
}
}
}
}
break;
case MSGMODE_TEXT_CLOSING:
msgCtx->stateTimer--;
if (msgCtx->stateTimer != 0) {
break;
}
if (sLastPlayedSong == OCARINA_SONG_SOARING) {
if (interfaceCtx->restrictions.songOfSoaring == 0) {
if (func_8010A0A4(play) || (play->sceneId == SCENE_SECOM)) {
Message_StartTextbox(play, 0x1B93, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_1B;
sLastPlayedSong = 0xFF;
} else if (!msgCtx->ocarinaSongEffectActive) {
if (gSaveContext.save.saveInfo.playerData.owlActivationFlags != 0) {
pauseCtx->unk_2C8 = pauseCtx->pageIndex;
pauseCtx->unk_2CA = pauseCtx->cursorPoint[4];
pauseCtx->pageIndex = PAUSE_ITEM;
pauseCtx->state = PAUSE_STATE_OWL_WARP_0;
func_800F4A10(play);
pauseCtx->pageIndex = PAUSE_MAP;
sLastPlayedSong = 0xFF;
Message_CloseTextbox(play);
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
gSaveContext.prevHudVisibility = HUD_VISIBILITY_A_B;
func_80115844(play, DO_ACTION_STOP);
GameState_SetFramerateDivisor(&play->state, 2);
if (ShrinkWindow_Letterbox_GetSizeTarget() != 0) {
ShrinkWindow_Letterbox_SetSizeTarget(0);
}
Audio_PlaySfx_PauseMenuOpenOrClose(1);
break;
}
sLastPlayedSong = 0xFF;
Message_StartTextbox(play, 0xFB, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG;
} else {
msgCtx->stateTimer = 1;
}
} else {
sLastPlayedSong = 0xFF;
Message_StartTextbox(play, 0x1B95, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG;
}
break;
}
if ((msgCtx->currentTextId == 0xC) || (msgCtx->currentTextId == 0xD) || (msgCtx->currentTextId == 0xC5) ||
(msgCtx->currentTextId == 0xC6) || (msgCtx->currentTextId == 0xC7) ||
(msgCtx->currentTextId == 0x2165) || (msgCtx->currentTextId == 0x2166) ||
(msgCtx->currentTextId == 0x2167) || (msgCtx->currentTextId == 0x2168)) {
gSaveContext.healthAccumulator = 20 * 0x10; // Refill 20 hearts
}
if ((play->csCtx.state == CS_STATE_IDLE) && (gSaveContext.save.cutsceneIndex < 0xFFF0) &&
((play->activeCamId == CAM_ID_MAIN) ||
((play->transitionTrigger == TRANS_TRIGGER_OFF) && (play->transitionMode == TRANS_MODE_OFF))) &&
(play->msgCtx.ocarinaMode == OCARINA_MODE_END)) {
if (((u32)gSaveContext.prevHudVisibility == HUD_VISIBILITY_IDLE) ||
(gSaveContext.prevHudVisibility == HUD_VISIBILITY_NONE) ||
(gSaveContext.prevHudVisibility == HUD_VISIBILITY_NONE_ALT)) {
gSaveContext.prevHudVisibility = HUD_VISIBILITY_ALL;
}
gSaveContext.hudVisibility = HUD_VISIBILITY_IDLE;
}
if ((msgCtx->currentTextId >= 0x1BB2) && (msgCtx->currentTextId <= 0x1BB6) &&
(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) {
Message_StartTextbox(play, 0x5E6, NULL);
break;
}
if (msgCtx->bombersNotebookEventQueueCount != 0) {
if (Message_BombersNotebookProcessEventQueue(play) == 0) {
msgCtx->stateTimer = 1;
}
break;
}
msgCtx->msgLength = 0;
msgCtx->msgMode = MSGMODE_NONE;
msgCtx->currentTextId = 0;
msgCtx->stateTimer = 0;
XREG(31) = 0;
if (pauseCtx->itemDescriptionOn) {
func_8011552C(play, DO_ACTION_INFO);
pauseCtx->itemDescriptionOn = false;
}
if (msgCtx->textboxEndType == TEXTBOX_ENDTYPE_30) {
msgCtx->textboxEndType = TEXTBOX_ENDTYPE_00;
play->msgCtx.ocarinaMode = OCARINA_MODE_WARP;
} else {
msgCtx->textboxEndType = TEXTBOX_ENDTYPE_00;
}
if (EQ_MAX_QUEST_HEART_PIECE_COUNT) {
RESET_HEART_PIECE_COUNT;
gSaveContext.save.saveInfo.playerData.healthCapacity += 0x10;
gSaveContext.save.saveInfo.playerData.health += 0x10;
}
if (msgCtx->ocarinaAction != OCARINA_ACTION_CHECK_NOTIME_DONE) {
s16 pad;
if (sLastPlayedSong == OCARINA_SONG_TIME) {
if (interfaceCtx->restrictions.songOfTime == 0) {
Message_StartTextbox(play, 0x1B8A, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_SOT;
} else {
sLastPlayedSong = 0xFF;
Message_StartTextbox(play, 0x1B95, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG;
}
} else if (sLastPlayedSong == OCARINA_SONG_INVERTED_TIME) {
if (interfaceCtx->restrictions.invSongOfTime == 0) {
if (R_TIME_SPEED != 0) {
if (gSaveContext.save.timeSpeedOffset == 0) {
Message_StartTextbox(play, 0x1B8C, NULL);
} else {
Message_StartTextbox(play, 0x1B8D, NULL);
}
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_INVERTED_TIME;
} else {
Message_StartTextbox(play, 0x1B8B, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
} else {
sLastPlayedSong = 0xFF;
Message_StartTextbox(play, 0x1B95, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG;
}
} else if (sLastPlayedSong == OCARINA_SONG_DOUBLE_TIME) {
if (interfaceCtx->restrictions.songOfDoubleTime == 0) {
// @recomp
dsot_determine_enabled();
// @recomp Replace DSoT functionality if the option for it is enabled.
if (dsot_enabled()) {
if ((CURRENT_DAY != 3) || (CURRENT_TIME < CLOCK_TIME(5, 0)) || (CURRENT_TIME >= CLOCK_TIME(6, 0))) {
Message_StartTextbox(play, D_801D0464[0], NULL);
// @recomp Replace message text.
char *buf = play->msgCtx.font.msgBuf.schar;
buf[25] = ' ';
buf[26] = 1;
buf[27] = 'S';
buf[28] = 'e';
buf[29] = 'l';
buf[30] = 'e';
buf[31] = 'c';
buf[32] = 't';
buf[33] = 'e';
buf[34] = 'd';
buf[35] = ' ';
buf[36] = 'H';
buf[37] = 'o';
buf[38] = 'u';
buf[39] = 'r';
buf[40] = 0;
buf[41] = '?';
buf[42] = 17;
buf[43] = 17;
buf[44] = 2;
buf[45] = -62;
buf[46] = 'Y';
buf[47] = 'e';
buf[48] = 's';
buf[49] = 17;
buf[50] = 'N';
buf[51] = 'o';
buf[52] = -65;
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME;
// @recomp
dsot_init_hour_selection(play);
} else {
Message_StartTextbox(play, 0x1B94, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
} else {
if ((CURRENT_DAY != 3) || (gSaveContext.save.isNight == 0)) {
if (gSaveContext.save.isNight) {
Message_StartTextbox(play, D_801D0464[CURRENT_DAY - 1], NULL);
} else {
Message_StartTextbox(play, D_801D045C[CURRENT_DAY - 1], NULL);
}
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_DOUBLE_TIME;
} else {
Message_StartTextbox(play, 0x1B94, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
}
}
} else {
sLastPlayedSong = 0xFF;
Message_StartTextbox(play, 0x1B95, NULL);
play->msgCtx.ocarinaMode = OCARINA_MODE_PROCESS_RESTRICTED_SONG;
}
} else if ((msgCtx->ocarinaAction == OCARINA_ACTION_FREE_PLAY_DONE) &&
((play->msgCtx.ocarinaMode == OCARINA_MODE_ACTIVE) ||
(play->msgCtx.ocarinaMode == OCARINA_MODE_EVENT) ||
(play->msgCtx.ocarinaMode == OCARINA_MODE_PLAYED_SCARECROW_SPAWN) ||
(play->msgCtx.ocarinaMode == OCARINA_MODE_PLAYED_FULL_EVAN_SONG))) {
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
if (msgCtx->lastPlayedSong == OCARINA_SONG_SOARING) {
play->msgCtx.ocarinaMode = OCARINA_MODE_ACTIVE;
}
}
sLastPlayedSong = 0xFF;
}
break;
case MSGMODE_OCARINA_PLAYING:
if (CHECK_BTN_ALL(input->press.button, BTN_B)) {
AudioOcarina_SetInstrument(OCARINA_INSTRUMENT_OFF);
play->msgCtx.ocarinaMode = OCARINA_MODE_END;
Message_CloseTextbox(play);
} else {
msgCtx->ocarinaButtonIndex = OCARINA_BTN_INVALID;
}
break;
case MSGMODE_OCARINA_AWAIT_INPUT:
if ((msgCtx->ocarinaAction != OCARINA_ACTION_PROMPT_EVAN_PART1_SECOND_HALF) &&
(msgCtx->ocarinaAction != OCARINA_ACTION_PROMPT_EVAN_PART2_SECOND_HALF)) {
if (Message_ShouldAdvance(play)) {
Message_DisplayOcarinaStaff(play, msgCtx->ocarinaAction);
}
}
break;
case MSGMODE_SCARECROW_SPAWN_RECORDING_ONGOING:
if (CHECK_BTN_ALL(input->press.button, BTN_B)) {
AudioOcarina_SetRecordingState(OCARINA_RECORD_OFF);
Audio_PlaySfx(NA_SE_SY_OCARINA_ERROR);
Message_CloseTextbox(play);
msgCtx->msgMode = MSGMODE_SCARECROW_SPAWN_RECORDING_FAILED;
} else {
msgCtx->ocarinaButtonIndex = OCARINA_BTN_INVALID;
}
break;
case MSGMODE_SCENE_TITLE_CARD_FADE_IN_BACKGROUND:
msgCtx->textboxColorAlphaCurrent += XREG(73);
if (msgCtx->textboxColorAlphaCurrent >= 255) {
msgCtx->textboxColorAlphaCurrent = 255;
msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_FADE_IN_TEXT;
}
break;
case MSGMODE_SCENE_TITLE_CARD_FADE_IN_TEXT:
msgCtx->textColorAlpha += XREG(73);
if (msgCtx->textColorAlpha >= 255) {
msgCtx->textColorAlpha = 255;
msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_DISPLAYING;
}
break;
case MSGMODE_SCENE_TITLE_CARD_DISPLAYING:
msgCtx->stateTimer--;
if (msgCtx->stateTimer == 0) {
msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_FADE_OUT_TEXT;
}
break;
case MSGMODE_SCENE_TITLE_CARD_FADE_OUT_TEXT:
msgCtx->textColorAlpha -= XREG(70);
if (msgCtx->textColorAlpha <= 0) {
msgCtx->textColorAlpha = 0;
msgCtx->msgMode = MSGMODE_SCENE_TITLE_CARD_FADE_OUT_BACKGROUND;
}
break;
case MSGMODE_SCENE_TITLE_CARD_FADE_OUT_BACKGROUND:
msgCtx->textboxColorAlphaCurrent -= XREG(70);
if (msgCtx->textboxColorAlphaCurrent <= 0) {
if ((msgCtx->currentTextId >= 0x1BB2) && (msgCtx->currentTextId <= 0x1BB6) &&
(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) {
Message_StartTextbox(play, 0x5E6, NULL);
Interface_SetHudVisibility(HUD_VISIBILITY_NONE_ALT);
} else {
//! FAKE: debug?
if (msgCtx->currentTextId >= 0x100) {
if (msgCtx && msgCtx && msgCtx) {}
}
msgCtx->textboxColorAlphaCurrent = 0;
msgCtx->msgLength = 0;
msgCtx->msgMode = MSGMODE_NONE;
msgCtx->currentTextId = 0;
msgCtx->stateTimer = 0;
}
}
break;
case MSGMODE_NEW_CYCLE_0:
play->state.unk_A3 = 1;
sp44 = gSaveContext.save.cutsceneIndex;
sp3E = CURRENT_TIME;
sp40 = gSaveContext.save.day;
Sram_SaveEndOfCycle(play);
gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF;
func_8014546C(&play->sramCtx);
gSaveContext.save.day = sp40;
gSaveContext.save.time = sp3E;
gSaveContext.save.cutsceneIndex = sp44;
if (gSaveContext.fileNum != 0xFF) {
Sram_SetFlashPagesDefault(&play->sramCtx, gFlashSaveStartPages[gSaveContext.fileNum * 2],
gFlashSpecialSaveNumPages[gSaveContext.fileNum * 2]);
Sram_StartWriteToFlashDefault(&play->sramCtx);
}
msgCtx->msgMode = MSGMODE_NEW_CYCLE_1;
break;
case MSGMODE_NEW_CYCLE_1:
if (gSaveContext.fileNum != 0xFF) {
play->state.unk_A3 = 1;
if (play->sramCtx.status == 0) {
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT;
msgCtx->msgMode = MSGMODE_NEW_CYCLE_2;
}
} else {
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT;
msgCtx->msgMode = MSGMODE_NEW_CYCLE_2;
}
break;
case MSGMODE_OWL_SAVE_0:
play->state.unk_A3 = 1;
gSaveContext.save.isOwlSave = true;
Play_SaveCycleSceneFlags(&play->state);
func_8014546C(&play->sramCtx);
if (gSaveContext.fileNum != 0xFF) {
Sram_SetFlashPagesOwlSave(&play->sramCtx, gFlashOwlSaveStartPages[gSaveContext.fileNum * 2],
gFlashOwlSaveNumPages[gSaveContext.fileNum * 2]);
Sram_StartWriteToFlashOwlSave(&play->sramCtx);
}
msgCtx->msgMode = MSGMODE_OWL_SAVE_1;
break;
case MSGMODE_OWL_SAVE_1:
if (gSaveContext.fileNum != 0xFF) {
play->state.unk_A3 = 1;
if (play->sramCtx.status == 0) {
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT;
msgCtx->msgMode = MSGMODE_OWL_SAVE_2;
}
} else {
play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT;
msgCtx->msgMode = MSGMODE_OWL_SAVE_2;
}
if (msgCtx->msgMode == MSGMODE_OWL_SAVE_2) {
gSaveContext.gameMode = GAMEMODE_OWL_SAVE;
play->transitionTrigger = TRANS_TRIGGER_START;
play->transitionType = TRANS_TYPE_FADE_BLACK;
play->nextEntrance = ENTRANCE(CUTSCENE, 0);
gSaveContext.save.cutsceneIndex = 0;
gSaveContext.sceneLayer = 0;
}
break;
case MSGMODE_9:
case MSGMODE_PAUSED:
case MSGMODE_NEW_CYCLE_2:
case MSGMODE_OWL_SAVE_2:
break;
default:
msgCtx->ocarinaButtonIndex = OCARINA_BTN_INVALID;
break;
}
}

View File

@ -11,5 +11,6 @@ DECLARE_FUNC(void, recomp_handle_quicksave_actions_main, OSMesgQueue* enter_mq,
DECLARE_FUNC(u16, recomp_get_pending_warp); DECLARE_FUNC(u16, recomp_get_pending_warp);
DECLARE_FUNC(u32, recomp_get_pending_set_time); DECLARE_FUNC(u32, recomp_get_pending_set_time);
DECLARE_FUNC(s32, recomp_autosave_enabled); DECLARE_FUNC(s32, recomp_autosave_enabled);
DECLARE_FUNC(s32, recomp_dsot_enabled);
#endif #endif

View File

@ -108,4 +108,12 @@ void draw_autosave_icon(PlayState* play);
void recomp_crash(const char* err); void recomp_crash(const char* err);
void dsot_determine_enabled(void);
bool dsot_enabled(void);
void dsot_init_hour_selection(PlayState* play);
void dsot_handle_hour_selection(PlayState* play);
void dsot_cancel_hour_selection(PlayState* play);
void dsot_advance_hour(PlayState* play);
void dsot_draw_clock(PlayState* play);
#endif #endif

View File

@ -60,3 +60,4 @@ recomp_get_inverted_axes = 0x8F0000A4;
recomp_high_precision_fb_enabled = 0x8F0000A8; recomp_high_precision_fb_enabled = 0x8F0000A8;
recomp_get_resolution_scale = 0x8F0000AC; recomp_get_resolution_scale = 0x8F0000AC;
recomp_get_analog_inverted_axes = 0x8F0000B0; recomp_get_analog_inverted_axes = 0x8F0000B0;
recomp_dsot_enabled = 0x8F0000B4;

View File

@ -990,6 +990,11 @@ void Message_DrawTextBox(PlayState* play, Gfx** gfxP) {
gSPPopMatrix(gfx++, G_MTX_MODELVIEW); gSPPopMatrix(gfx++, G_MTX_MODELVIEW);
} }
// @recomp Replace DSoT functionality if the option for it is enabled.
if (dsot_enabled() && (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME)) {
dsot_draw_clock(play);
}
// Draw treble clef // Draw treble clef
if (msgCtx->textBoxType == TEXTBOX_TYPE_3) { if (msgCtx->textBoxType == TEXTBOX_TYPE_3) {
gDPPipeSync(gfx++); gDPPipeSync(gfx++);

View File

@ -221,6 +221,7 @@ bool save_general_config(const std::filesystem::path& path) {
config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode(); config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode();
config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode(); config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode();
config_json["debug_mode"] = zelda64::get_debug_mode_enabled(); config_json["debug_mode"] = zelda64::get_debug_mode_enabled();
config_json["dsot_mode"] = zelda64::get_dsot_mode();
return save_json_with_backups(path, config_json); return save_json_with_backups(path, config_json);
} }
@ -237,6 +238,7 @@ void set_general_settings_from_json(const nlohmann::json& config_json) {
zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off)); zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off));
zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone)); zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone));
zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false)); zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
zelda64::set_dsot_mode(from_or_default(config_json, "dsot_mode", zelda64::DsotMode::On));
} }
bool load_general_config(const std::filesystem::path& path) { bool load_general_config(const std::filesystem::path& path) {

View File

@ -95,6 +95,10 @@ extern "C" void recomp_autosave_enabled(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, static_cast<s32>(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On)); _return(ctx, static_cast<s32>(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On));
} }
extern "C" void recomp_dsot_enabled(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, static_cast<s32>(zelda64::get_dsot_mode() == zelda64::DsotMode::On));
}
extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) { extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
u32 rom = _arg<0, u32>(rdram, ctx); u32 rom = _arg<0, u32>(rdram, ctx);
PTR(void) ram = _arg<1, PTR(void)>(rdram, ctx); PTR(void) ram = _arg<1, PTR(void)>(rdram, ctx);

View File

@ -281,6 +281,7 @@ struct ControlOptionsContext {
zelda64::CameraInvertMode camera_invert_mode; zelda64::CameraInvertMode camera_invert_mode;
zelda64::AnalogCamMode analog_cam_mode; zelda64::AnalogCamMode analog_cam_mode;
zelda64::CameraInvertMode analog_camera_invert_mode; zelda64::CameraInvertMode analog_camera_invert_mode;
zelda64::DsotMode dsot_mode;
}; };
ControlOptionsContext control_options_context; ControlOptionsContext control_options_context;
@ -401,6 +402,17 @@ void zelda64::set_analog_camera_invert_mode(zelda64::CameraInvertMode mode) {
} }
} }
zelda64::DsotMode zelda64::get_dsot_mode() {
return control_options_context.dsot_mode;
}
void zelda64::set_dsot_mode(zelda64::DsotMode mode) {
control_options_context.dsot_mode = mode;
if (general_model_handle) {
general_model_handle.DirtyVariable("dsot_mode");
}
}
struct SoundOptionsContext { struct SoundOptionsContext {
std::atomic<int> main_volume; // Option to control the volume of all sound std::atomic<int> main_volume; // Option to control the volume of all sound
std::atomic<int> bgm_volume; std::atomic<int> bgm_volume;
@ -903,6 +915,7 @@ public:
bind_option(constructor, "camera_invert_mode", &control_options_context.camera_invert_mode); bind_option(constructor, "camera_invert_mode", &control_options_context.camera_invert_mode);
bind_option(constructor, "analog_cam_mode", &control_options_context.analog_cam_mode); bind_option(constructor, "analog_cam_mode", &control_options_context.analog_cam_mode);
bind_option(constructor, "analog_camera_invert_mode", &control_options_context.analog_camera_invert_mode); bind_option(constructor, "analog_camera_invert_mode", &control_options_context.analog_camera_invert_mode);
bind_option(constructor, "dsot_mode", &control_options_context.dsot_mode);
general_model_handle = constructor.GetModelHandle(); general_model_handle = constructor.GetModelHandle();
} }