diff --git a/patches/better_double_sot.c b/patches/better_double_sot.c new file mode 100644 index 0000000..417f68c --- /dev/null +++ b/patches/better_double_sot.c @@ -0,0 +1,374 @@ +#include "patches.h" + +void dsot_load_day_number_texture(PlayState* play, s32 day); +void dsot_actor_fixes(PlayState* play); + +bool dsot_used = false; +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; + dsot_used = true; + + // 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. + if ((CURRENT_TIME >= CLOCK_TIME(18, 0)) || (CURRENT_TIME <= CLOCK_TIME(6,0))) { + SEQCMD_STOP_SEQUENCE(SEQ_PLAYER_BGM_MAIN, 0); + } 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_Obj_Tokeidai/z_obj_tokeidai.h" +#include "overlays/actors/ovl_Obj_Tokei_Step/z_obj_tokei_step.h" + +void dsot_ObjTokeidai_fix(ObjTokeidai* this, PlayState* play); +void dsot_ObjTokeiStep_fix(ObjTokeiStep* 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_OBJ_TOKEIDAI: + dsot_ObjTokeidai_fix(actor, play); + break; + case ACTOR_OBJ_TOKEI_STEP: + dsot_ObjTokeiStep_fix(actor, play); + break; + } + } + } +} + +// z_obj_tokeidai + +#define GET_CURRENT_CLOCK_HOUR(this) ((s32)TIME_TO_HOURS_F((this)->clockTime)) +#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_SetupClockOrExteriorGear(ObjTokeidai* this); +void ObjTokeidai_Init(Actor* thisx, PlayState* play); +void ObjTokeidai_Draw(Actor* thisx, PlayState* play); +void ObjTokeidai_Clock_Init(ObjTokeidai* this); +void ObjTokeidai_SetupTowerOpening(ObjTokeidai* this); +void ObjTokeidai_DoNothing(ObjTokeidai* this, PlayState* play); +void ObjTokeidai_RotateOnMinuteChange(ObjTokeidai* this, s32 playSfx); +void ObjTokeidai_RotateOnHourChange(ObjTokeidai* this, 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; + } +} + +#define PAST_MIDNIGHT ((CURRENT_DAY == 3) && (CURRENT_TIME < CLOCK_TIME(6, 0)) && (CURRENT_TIME > CLOCK_TIME(0, 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; + } +} + +// 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); + } +} \ No newline at end of file diff --git a/patches/event_patches.c b/patches/event_patches.c new file mode 100644 index 0000000..6760007 --- /dev/null +++ b/patches/event_patches.c @@ -0,0 +1,813 @@ +#include "patches.h" + +#define PAST_MIDNIGHT CLOCK_TIME(0, 1) + +#include "overlays/actors/ovl_En_Test4/z_en_test4.h" +#include "z64horse.h" +#include "overlays/gamestates/ovl_daytelop/z_daytelop.h" +#include "overlays/actors/ovl_En_Horse/z_en_horse.h" + +#define THIS ((EnTest4*)thisx) + +static s16 sCsIdList[THREEDAY_DAYTIME_MAX]; + +void func_80A42AB8(EnTest4* this, PlayState* play); // EnTest4_HandleEvents +void func_80A42F20(EnTest4* this, PlayState* play); // EnTest4_HandleCutscene + +static s16 sCurCsId; + +void func_800FEAF4(EnvironmentContext* envCtx); // Environment_NewDay + +void func_80A41FA4(EnTest4* this, PlayState* play); // EnTest4_HandleDayNightSwap +void func_80A42198(EnTest4* this); // EnTest4_GetBellTimeOnDay3 +void func_80A425E4(EnTest4* this, PlayState* play); // EnTest4_GetBellTimeAndShrinkScreenBeforeDay3 + +// @recomp Patched to perform transitions when Double SoT is used. +/** + * This function checks for two-specific time-based events: + * 1) The day-night transitions + * 2) The play bells sfx event, which contains screen shrinking and the clocktown day 3 midnight cutscene trigger + */ +void func_80A42AB8(EnTest4* this, PlayState* play) { // EnTest4_HandleEvents + static u16 sDayNightTransitionTimes[THREEDAY_DAYTIME_MAX] = { + CLOCK_TIME(6, 0), // THREEDAY_DAYTIME_NIGHT + CLOCK_TIME(18, 0), // THREEDAY_DAYTIME_DAY + }; + Player* player = GET_PLAYER(play); + + if ((play->transitionMode == TRANS_MODE_OFF) && !Play_InCsMode(play) && (play->numSetupActors <= 0) && + (play->roomCtx.status == 0) && !Play_IsDebugCamEnabled()) { + u16 transitionTime = sDayNightTransitionTimes[this->daytimeIndex]; + s16 curTimeUntilTransition; + s16 prevTimeUntilTransition; + s16 prevTimeUntilBell; + s16 curTimeUntilBell; + + curTimeUntilTransition = CURRENT_TIME - transitionTime; + prevTimeUntilTransition = this->prevTime - transitionTime; + + prevTimeUntilBell = this->prevBellTime - this->nextBellTime; + curTimeUntilBell = CURRENT_TIME - this->nextBellTime; + + extern bool dsot_used; + u8 current_hour = TIME_TO_HOURS_F(CURRENT_TIME); + + // @recomp Check for unusual Double SoT time skips. + if (dsot_used) { + if ((this->daytimeIndex == THREEDAY_DAYTIME_DAY) && (CURRENT_TIME < this->prevTime)) { + if (CURRENT_TIME >= CLOCK_TIME(6, 0)) { + transitionTime = sDayNightTransitionTimes[0]; + } + curTimeUntilTransition = 0; + } + + if ((CURRENT_DAY == 3) && (current_hour == 0)) { + curTimeUntilBell = 0; + this->nextBellTime = CLOCK_TIME(0, 0); + } + } + + // When the day-night transition time is passed: + // `curTimeUntilTransition` will be slightly positive (ahead transition time) + // `prevTimeUntilTransition` will be slightly negative (behind transition time) + // Only when the signs are different will this condition pass + if (((curTimeUntilTransition * prevTimeUntilTransition) <= 0)) { + // day-night transition is occuring + gSaveContext.unk_3CA7 = 1; + if (play->actorCtx.flags & ACTORCTX_FLAG_PICTO_BOX_ON) { + play->actorCtx.flags &= ~ACTORCTX_FLAG_PICTO_BOX_ON; + } + + // @recomp Manual relocation, TODO remove when the recompiler handles this automatically. + s16* sCsIdList_reloc = (s16*)actor_relocate(&this->actor, &sCsIdList); + + if (transitionTime != CLOCK_TIME(6, 0)) { + // previously day, turning night + // @recomp Skip night transition cutscene if Double SoT is used. + if ((!dsot_used) || (current_hour == 18)) { + func_80A41FA4(this, play); + } else { + this->daytimeIndex = THREEDAY_DAYTIME_NIGHT; + this->prevTime = CURRENT_TIME; + // Re-spawn the setup actors. + play->numSetupActors = -play->numSetupActors; + } + } else if (transitionTime == CLOCK_TIME(6, 0)) { + // previously night, turning day + if (CURRENT_DAY == 3) { + // Turn day with mooncrash + Interface_StartMoonCrash(play); + Actor_Kill(&this->actor); + SET_EVENTINF(EVENTINF_17); + } else if (((sCsIdList_reloc[this->daytimeIndex] <= CS_ID_NONE) || + (play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) && + (CURRENT_DAY != 3)) { + // Turn day without cutscene + func_80A41FA4(this, play); + } else { + // Turn day with DayTelop cutscene + gSaveContext.screenScale = 0.0f; + Play_SetRespawnData(&play->state, RESPAWN_MODE_DOWN, Entrance_CreateFromSpawn(0), player->unk_3CE, + PLAYER_PARAMS(0xFF, PLAYER_INITMODE_B), &player->unk_3C0, player->unk_3CC); + func_80169EFC(&play->state); + if (player->stateFlags1 & PLAYER_STATE1_800000) { + EnHorse* rideActor = (EnHorse*)player->rideActor; + + if ((rideActor->type == HORSE_TYPE_EPONA) || (rideActor->type == HORSE_TYPE_2)) { + if (CURRENT_DAY < 3) { + gHorseIsMounted = true; + } else { + gHorseIsMounted = false; + } + } + } + + gSaveContext.respawnFlag = -4; + SET_EVENTINF(EVENTINF_TRIGGER_DAYTELOP); + Actor_Kill(&this->actor); + } + } + + // @recomp Skip night transition cutscene if Double SoT is used. + if ((!dsot_used) || (transitionTime == CLOCK_TIME(6, 0))) { + if ((sCsIdList_reloc[this->daytimeIndex] > CS_ID_NONE) && !(play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON)) { + player->stateFlags1 |= PLAYER_STATE1_200; + this->prevTime = CURRENT_TIME; + } else { + if (this->daytimeIndex == THREEDAY_DAYTIME_NIGHT) { + this->daytimeIndex = THREEDAY_DAYTIME_DAY; + } else { + this->daytimeIndex = THREEDAY_DAYTIME_NIGHT; + } + + gSaveContext.save.time += CLOCK_TIME_MINUTE; + this->prevTime = CURRENT_TIME; + } + + return; + } + } + + // When the bell sfx time is passed: + // `curTimeUntilBell` will be slightly positive (ahead bell time) + // `prevTimeUntilBell` will be slightly negative (behind bell time) + // Only when the signs are different will this condition pass + if ((curTimeUntilBell * prevTimeUntilBell) <= 0) { + Audio_PlaySfx_BigBells(&this->actor.projectedPos, THREEDAY_GET_BIG_BELLS_SFX_VOLUME_INDEX(&this->actor)); + this->prevBellTime = CURRENT_TIME; + + if (CURRENT_DAY == 3) { + // @recomp Don't initate cutscene if it's past midnight. + if ((this->nextBellTime == CLOCK_TIME(0, 0)) && + (CURRENT_TIME < PAST_MIDNIGHT) && + ((gSaveContext.save.saveInfo.inventory.items[SLOT_OCARINA] == ITEM_NONE) || + (play->sceneId == SCENE_CLOCKTOWER))) { + // Initiate Clocktown day 3 midnight festival cutscene + s32 playerParams; + u32 entrance = gSaveContext.save.entrance; + + if (play->actorCtx.flags & ACTORCTX_FLAG_TELESCOPE_ON) { + playerParams = PLAYER_PARAMS(0xFF, PLAYER_INITMODE_TELESCOPE); + } else { + playerParams = PLAYER_PARAMS(0xFF, PLAYER_INITMODE_B); + } + + Play_SetRespawnData(&play->state, RESPAWN_MODE_RETURN, entrance, player->unk_3CE, playerParams, + &player->unk_3C0, player->unk_3CC); + + if ((play->sceneId == SCENE_TENMON_DAI) || (play->sceneId == SCENE_00KEIKOKU)) { + play->nextEntrance = ENTRANCE(TERMINA_FIELD, 0); + } else { + play->nextEntrance = ENTRANCE(SOUTH_CLOCK_TOWN, 0); + } + gSaveContext.nextCutsceneIndex = 0xFFF1; + play->transitionTrigger = TRANS_TRIGGER_START; + play->transitionType = TRANS_TYPE_FADE_BLACK; + player->stateFlags1 |= PLAYER_STATE1_200; + Actor_Kill(&this->actor); + } + func_80A42198(this); + } else { + func_80A425E4(this, play); + } + } + + dsot_used = false; + } +} + +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; + } + } + + 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)) { + // @recomp Don't play final hours until Double SoT finishes. + extern bool dsot_used; + if (!dsot_used) { + 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 Advance hour as Double SoT ends. + if (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 Disable scene reset after Double SoT ends. + 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++; +} \ No newline at end of file diff --git a/patches/message_patches.c b/patches/message_patches.c new file mode 100644 index 0000000..4baf842 --- /dev/null +++ b/patches/message_patches.c @@ -0,0 +1,888 @@ +#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 + 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 ((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) { + // @recomp Patch Song of Double Time. + if (interfaceCtx->restrictions.songOfDoubleTime == 0) { + 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 { + 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; + } +} \ No newline at end of file diff --git a/patches/patches.h b/patches/patches.h index 9ad713b..b77db60 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -108,4 +108,10 @@ void draw_autosave_icon(PlayState* play); void recomp_crash(const char* err); +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 diff --git a/patches/ui_patches.c b/patches/ui_patches.c index bd299c7..5f07315 100644 --- a/patches/ui_patches.c +++ b/patches/ui_patches.c @@ -94,8 +94,8 @@ void Graph_ExecuteAndDraw(GraphicsContext* gfxCtx, GameState* gameState) { GameState_Update(gameState); OPEN_DISPS(gfxCtx); - - // @recomp Send the current framerate to RT64, including any extra VI interrupt periods. + + // @recomp Send the current framerate to RT64, including any extra VI interrupt periods. gEXSetRefreshRate(POLY_OPA_DISP++, 60 / (gameState->framerateDivisor + extra_vis)); // @recomp Edit billboard groups to skip interpolation if the camera also skipped. @@ -485,7 +485,7 @@ void Interface_Draw(PlayState* play) { gEXSetRectAlign(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, -margin_reduction * 4, -margin_reduction * 4, -margin_reduction * 4, -margin_reduction * 4); Magic_DrawMeter(play); - + // @recomp Draw the D-Pad and its item icons as well as the autosave icon if the game is unpaused. if (pauseCtx->state != PAUSE_STATE_MAIN) { draw_dpad(play); @@ -617,7 +617,7 @@ void Interface_Draw(PlayState* play) { Interface_DrawClock(play); } } - + // @recomp Restore normal alignment and reset shift for minigame "Perfect" text gEXSetRectAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, G_EX_ORIGIN_NONE, 0, 0, 0, 0); gEXSetViewportAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, 0, 0); @@ -635,7 +635,7 @@ void Interface_Draw(PlayState* play) { Interface_DrawMinigameIcons(play); Interface_DrawTimers(play); - + // @recomp Restore normal alignment and shift down for minigame countdown or clock gEXSetRectAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, G_EX_ORIGIN_NONE, 0, 0, 0, 0); gEXSetViewportAlign(OVERLAY_DISP++, G_EX_ORIGIN_NONE, 0, 0); @@ -936,7 +936,7 @@ void Message_DrawTextBox(PlayState* play, Gfx** gfxP) { textbox_viewport->vp.vscale[1] = (gCfbHeight / 2) << 2; textbox_viewport->vp.vscale[2] = G_MAXZ; textbox_viewport->vp.vscale[3] = 0; - + textbox_viewport->vp.vtrans[0] = (gCfbWidth / 2) << 2; textbox_viewport->vp.vtrans[1] = (gCfbHeight / 2) << 2; textbox_viewport->vp.vtrans[2] = 0; @@ -990,6 +990,11 @@ void Message_DrawTextBox(PlayState* play, Gfx** gfxP) { gSPPopMatrix(gfx++, G_MTX_MODELVIEW); } + // @recomp Draw clock for Double SoT. + if (play->msgCtx.ocarinaMode == OCARINA_MODE_PROCESS_DOUBLE_TIME) { + dsot_draw_clock(play); + } + // Draw treble clef if (msgCtx->textBoxType == TEXTBOX_TYPE_3) { gDPPipeSync(gfx++); @@ -1137,7 +1142,7 @@ void ShrinkWindow_Draw(GraphicsContext* gfxCtx) { gSPMatrix(gfx++, letterbox_matrix_top, G_MTX_MODELVIEW | G_MTX_PUSH | G_MTX_LOAD); gSPVertex(gfx++, letterbox_verts, 4, 0); gSP2Triangles(gfx++, 0, 1, 3, 0x0, 0, 3, 2, 0x0); - + // @recomp Draw the bottom letterbox element. gSPMatrix(gfx++, letterbox_matrix_bottom, G_MTX_MODELVIEW | G_MTX_NOPUSH | G_MTX_LOAD); gSPVertex(gfx++, letterbox_verts, 4, 0); @@ -1187,7 +1192,7 @@ void ShrinkWindow_Draw(GraphicsContext* gfxCtx) { extern u64 gSceneTitleCardGradientTex[]; -// @recomp Patch the scene title card (the one with purple background when entering a new scene) +// @recomp Patch the scene title card (the one with purple background when entering a new scene) // to not glitch out on the right edge, which is hidden by overscan on N64. void Message_DrawSceneTitleCard(PlayState* play, Gfx** gfxP) { MessageContext* msgCtx = &play->msgCtx; @@ -1217,4 +1222,4 @@ void Message_DrawSceneTitleCard(PlayState* play, Gfx** gfxP) { msgCtx->unk11FFA = XREG(74); Message_DrawTextNES(play, &gfx, 0); *gfxP = gfx++; -} +} \ No newline at end of file