pokefirered/src/quest_log.c

1774 lines
56 KiB
C

#include "global.h"
#include "gflib.h"
#include "task.h"
#include "menu.h"
#include "menu_helpers.h"
#include "text_window.h"
#include "event_data.h"
#include "script.h"
#include "overworld.h"
#include "field_fadetransition.h"
#include "field_weather.h"
#include "event_object_movement.h"
#include "event_object_lock.h"
#include "field_player_avatar.h"
#include "item.h"
#include "region_map.h"
#include "map_name_popup.h"
#include "wild_encounter.h"
#include "help_system.h"
#include "pokemon_storage_system.h"
#include "save.h"
#include "quest_log_objects.h"
#include "quest_log_player.h"
#include "quest_log.h"
#include "rtc.h"
#include "strings.h"
#include "constants/event_objects.h"
#include "constants/maps.h"
#include "constants/quest_log.h"
#include "constants/field_weather.h"
#include "constants/event_object_movement.h"
enum {
WIN_TOP_BAR, // Contains the "Previously on..." text
WIN_BOTTOM_BAR, // Empty, only briefly visible at the end or when the description window isn't covering it.
WIN_DESCRIPTION,
WIN_COUNT
};
#define DESC_WIN_WIDTH (DISPLAY_WIDTH / 8)
#define DESC_WIN_HEIGHT 6
#define DESC_WIN_SIZE (DESC_WIN_WIDTH * DESC_WIN_HEIGHT * TILE_SIZE_4BPP)
// sQuestLogActionRecordBuffer should be large enough to fill a scene's script with the maximum number of actions
#define SCRIPT_BUFFER_SIZE (sizeof(gSaveBlock1Ptr->questLog[0].script) / sizeof(struct QuestLogAction))
enum {
END_MODE_NONE,
END_MODE_FINISH,
END_MODE_SCENE,
};
struct PlaybackControl
{
u8 state:4;
u8 playingEvent:2;
u8 endMode:2;
u8 cursor;
u8 timer;
u8 overlapTimer;
};
struct FlagOrVarRecord
{
u16 idx:15;
u16 isFlag:1;
u16 value;
};
COMMON_DATA u8 gQuestLogPlaybackState = 0;
COMMON_DATA u16 sMaxActionsInScene = 0;
COMMON_DATA struct FieldInput gQuestLogFieldInput = {0};
COMMON_DATA struct QuestLogAction * sCurSceneActions = NULL;
static struct FlagOrVarRecord * sFlagOrVarRecords;
static u16 sNumFlagsOrVars;
static EWRAM_DATA u8 sCurrentSceneNum = 0;
static EWRAM_DATA u8 sNumScenes = 0;
EWRAM_DATA u8 gQuestLogState = 0;
static EWRAM_DATA u16 sRecordSequenceStartIdx = 0;
static EWRAM_DATA u8 sWindowIds[WIN_COUNT] = {0};
EWRAM_DATA u16 *gQuestLogDefeatedWildMonRecord = NULL;
EWRAM_DATA u16 *gQuestLogRecordingPointer = NULL;
static EWRAM_DATA u16 *sEventData[32] = {NULL};
static EWRAM_DATA void (* sQuestLogCB)(void) = NULL;
static EWRAM_DATA u16 *sPalettesBackup = NULL;
static EWRAM_DATA struct PlaybackControl sPlaybackControl = {0};
static EWRAM_DATA struct QuestLogAction sQuestLogActionRecordBuffer[SCRIPT_BUFFER_SIZE] = {0};
EWRAM_DATA u16 gQuestLogCurActionIdx = 0;
static EWRAM_DATA u8 sMovementScripts[OBJECT_EVENT_TEMPLATES_COUNT][2] = {{0}};
static EWRAM_DATA u16 sNextActionDelay = 0;
static EWRAM_DATA u16 sLastQuestLogCursor = 0;
static EWRAM_DATA u16 sFlagOrVarPlayhead = 0;
static void QLogCB_Recording(void);
static void QLogCB_Playback(void);
static void SetPlayerInitialCoordsAtScene(u8);
static void SetNPCInitialCoordsAtScene(u8);
static void RecordSceneEnd(void);
static void BackUpTrainerRematches(void);
static void BackUpMapLayout(void);
static void SetGameStateAtScene(u8);
static u8 TryRecordActionSequence(struct QuestLogAction *);
static void Task_BeginQuestLogPlayback(u8);
static void QL_LoadObjectsAndTemplates(u8);
static void QLPlayback_InitOverworldState(void);
static void SetPokemonCounts(void);
static u16 QuestLog_GetPartyCount(void);
static u16 QuestLog_GetBoxMonCount(void);
static void RestoreTrainerRematches(void);
static void ReadQuestLogScriptFromSav1(u8, struct QuestLogAction *);
static void DoSceneEndTransition(s8 delay);
static void DoSkipToEndTransition(s8 delay);
static void QuestLog_AdvancePlayhead(void);
static void QuestLog_StartFinalScene(void);
static void Task_AvoidDisplay(u8);
static void QuestLog_PlayCurrentEvent(void);
static void HandleShowQuestLogMessage(void);
static u8 GetQuestLogTextDisplayDuration(void);
static void DrawSceneDescription(void);
static void CopyDescriptionWindowTiles(u8);
static void QuestLog_CloseTextWindow(void);
static void QuestLog_WaitFadeAndCancelPlayback(void);
static bool8 FieldCB2_FinalScene(void);
static void Task_FinalScene_WaitFade(u8);
static void Task_QuestLogScene_SavedGame(u8);
static void Task_WaitAtEndOfQuestLog(u8);
static void Task_EndQuestLog(u8);
static bool8 RestoreScreenAfterPlayback(u8);
static void QL_SlightlyDarkenSomePals(void);
static void TogglePlaybackStateForOverworldLock(u8);
static void ResetActions(u8, struct QuestLogAction *, u16);
static bool8 RecordHeadAtEndOfEntryOrScriptContext2Enabled(void);
static bool8 RecordHeadAtEndOfEntry(void);
static const struct WindowTemplate sWindowTemplates[WIN_COUNT] = {
[WIN_TOP_BAR] = {
.bg = 0,
.tilemapLeft = 0,
.tilemapTop = 0,
.width = 30,
.height = 2,
.paletteNum = 15,
.baseBlock = 0x0e9
},
[WIN_BOTTOM_BAR] = {
.bg = 0,
.tilemapLeft = 0,
.tilemapTop = 18,
.width = 30,
.height = 2,
.paletteNum = 15,
.baseBlock = 0x0ad
},
[WIN_DESCRIPTION] = {
.bg = 0,
.tilemapLeft = 0,
.tilemapTop = 14,
.width = DESC_WIN_WIDTH,
.height = DESC_WIN_HEIGHT,
.paletteNum = 15,
.baseBlock = 0x14c
}
};
static const u8 sTextColors[3] = {TEXT_DYNAMIC_COLOR_6, TEXT_COLOR_WHITE, TEXT_DYNAMIC_COLOR_3};
static const u16 sDescriptionWindow_Gfx[] = INCBIN_U16("graphics/quest_log/description_window.4bpp");
static const u8 sQuestLogTextLineYCoords[] = {17, 10, 3};
void QL_AddASLROffset(void *oldSaveBlockPtr)
{
// For some reason the caller passes the original pointer and the
// amount the save moved is recalculated, instead of just passing
// the offset to begin with.
ptrdiff_t offset = (void *)gSaveBlock1Ptr - oldSaveBlockPtr;
if (gQuestLogDefeatedWildMonRecord)
gQuestLogDefeatedWildMonRecord = (void *)gQuestLogDefeatedWildMonRecord + offset;
if (gQuestLogState == 0)
return;
if (gQuestLogRecordingPointer)
gQuestLogRecordingPointer = (void *)gQuestLogRecordingPointer + offset;
if (gQuestLogState == QL_STATE_PLAYBACK)
{
int i;
for (i = 0; i < (int)ARRAY_COUNT(sEventData); i++)
if (sEventData[i])
sEventData[i] = (void *)sEventData[i] + offset;
}
}
void ResetQuestLog(void)
{
memset(gSaveBlock1Ptr->questLog, 0, sizeof(gSaveBlock1Ptr->questLog));
sCurrentSceneNum = 0;
gQuestLogState = 0;
sQuestLogCB = NULL;
gQuestLogRecordingPointer = NULL;
gQuestLogDefeatedWildMonRecord = NULL;
QL_ResetEventStates();
ResetDeferredLinkEvent();
}
static void ClearSavedScene(u8 sceneNum)
{
memset(&gSaveBlock1Ptr->questLog[sceneNum], 0, sizeof(gSaveBlock1Ptr->questLog[sceneNum]));
gQuestLogDefeatedWildMonRecord = NULL;
}
void QL_ResetDefeatedWildMonRecord(void)
{
gQuestLogDefeatedWildMonRecord = NULL;
}
void RunQuestLogCB(void)
{
if (sQuestLogCB != NULL)
sQuestLogCB();
}
bool8 QL_IsRoomToSaveEvent(const void * cursor, size_t size)
{
const void *start = gSaveBlock1Ptr->questLog[sCurrentSceneNum].script;
const void *end = gSaveBlock1Ptr->questLog[sCurrentSceneNum].end;
end -= size;
if (cursor < start || cursor > end)
return FALSE;
return TRUE;
}
// Identical to QL_IsRoomToSaveEvent
bool8 QL_IsRoomToSaveAction(const void * cursor, size_t size)
{
const void *start = gSaveBlock1Ptr->questLog[sCurrentSceneNum].script;
const void *end = gSaveBlock1Ptr->questLog[sCurrentSceneNum].end;
end -= size;
if (cursor < start || cursor > end)
return FALSE;
return TRUE;
}
static void SetQuestLogState(u8 state)
{
gQuestLogState = state;
if (state == QL_STATE_RECORDING)
sQuestLogCB = QLogCB_Recording;
else
sQuestLogCB = QLogCB_Playback;
}
static void QLogCB_Recording(void)
{
if (TryRecordActionSequence(sQuestLogActionRecordBuffer) != 1)
{
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
RecordSceneEnd();
gQuestLogState = 0;
sQuestLogCB = NULL;
}
}
static void QLogCB_Playback(void)
{
if (sPlaybackControl.state == 2)
sPlaybackControl.state = 0;
if (sPlaybackControl.endMode == END_MODE_NONE)
{
if (gQuestLogPlaybackState != QL_PLAYBACK_STATE_STOPPED
|| sPlaybackControl.state == 1
|| (sPlaybackControl.cursor < ARRAY_COUNT(sEventData)
&& sEventData[sPlaybackControl.cursor] != NULL))
QuestLog_PlayCurrentEvent();
else
{
sPlaybackControl.endMode = END_MODE_SCENE;
LockPlayerFieldControls();
DoSceneEndTransition(0);
}
}
}
void GetQuestLogState(void)
{
gSpecialVar_Result = gQuestLogState;
}
u8 GetQuestLogStartType(void)
{
return gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType;
}
void QL_StartRecordingAction(u16 eventId)
{
if (sCurrentSceneNum >= QUEST_LOG_SCENE_COUNT)
sCurrentSceneNum = 0;
ClearSavedScene(sCurrentSceneNum);
QL_ResetRepeatEventTracker();
gQuestLogRecordingPointer = gSaveBlock1Ptr->questLog[sCurrentSceneNum].script;
if (IS_LINK_QL_EVENT(eventId) || eventId == QL_EVENT_DEPARTED)
gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType = QL_START_WARP;
else
gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType = QL_START_NORMAL;
SetPokemonCounts();
SetPlayerInitialCoordsAtScene(sCurrentSceneNum);
SetNPCInitialCoordsAtScene(sCurrentSceneNum);
BackUpTrainerRematches();
BackUpMapLayout();
SetGameStateAtScene(sCurrentSceneNum);
sRecordSequenceStartIdx = 0;
ResetActions(QL_PLAYBACK_STATE_RECORDING, sQuestLogActionRecordBuffer, sizeof(sQuestLogActionRecordBuffer));
TryRecordActionSequence(sQuestLogActionRecordBuffer);
SetQuestLogState(QL_STATE_RECORDING);
}
static void SetPlayerInitialCoordsAtScene(u8 sceneNum)
{
struct QuestLogScene * questLog = &gSaveBlock1Ptr->questLog[sceneNum];
questLog->mapGroup = gSaveBlock1Ptr->location.mapGroup;
questLog->mapNum = gSaveBlock1Ptr->location.mapNum;
questLog->warpId = gSaveBlock1Ptr->location.warpId;
questLog->x = gSaveBlock1Ptr->pos.x;
questLog->y = gSaveBlock1Ptr->pos.y;
}
static void SetNPCInitialCoordsAtScene(u8 sceneNum)
{
struct QuestLogScene * questLog = &gSaveBlock1Ptr->questLog[sceneNum];
u16 i;
QL_RecordObjects(questLog);
for (i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
{
if (gSaveBlock1Ptr->objectEventTemplates[i].x < 0)
{
questLog->objectEventTemplates[i].x = -1 * gSaveBlock1Ptr->objectEventTemplates[i].x;
questLog->objectEventTemplates[i].negx = TRUE;
}
else
{
questLog->objectEventTemplates[i].x = (u8)gSaveBlock1Ptr->objectEventTemplates[i].x;
questLog->objectEventTemplates[i].negx = FALSE;
}
if (gSaveBlock1Ptr->objectEventTemplates[i].y < 0)
{
questLog->objectEventTemplates[i].y = (-gSaveBlock1Ptr->objectEventTemplates[i].y << 24) >> 24;
questLog->objectEventTemplates[i].negy = TRUE;
}
else
{
questLog->objectEventTemplates[i].y = (u8)gSaveBlock1Ptr->objectEventTemplates[i].y;
questLog->objectEventTemplates[i].negy = FALSE;
}
questLog->objectEventTemplates[i].elevation = gSaveBlock1Ptr->objectEventTemplates[i].elevation;
questLog->objectEventTemplates[i].movementType = gSaveBlock1Ptr->objectEventTemplates[i].movementType;
}
}
static void SetGameStateAtScene(u8 sceneNum)
{
struct QuestLogScene * questLog = &gSaveBlock1Ptr->questLog[sceneNum];
CpuCopy16(gSaveBlock1Ptr->flags, questLog->flags, sizeof(gSaveBlock1Ptr->flags));
CpuCopy16(gSaveBlock1Ptr->vars, questLog->vars, sizeof(gSaveBlock1Ptr->vars));
}
static void BackUpTrainerRematches(void)
{
u16 i, j;
u16 vars[4];
// Save the contents of gSaveBlock1Ptr->trainerRematches to the 4 saveblock
// vars starting at VAR_QLBAK_TRAINER_REMATCHES. The rematch array is 100 bytes
// long, but each byte is only ever 0 or 1 to indicate that a rematch is available.
// They're compressed into single bits across 4 u16 save vars, which is only enough
// to save 64 elements of gSaveBlock1Ptr->trainerRematches. 64 however is the maximum
// that could ever be used, as its the maximum number of NPCs per map (OBJECT_EVENT_TEMPLATES_COUNT).
for (i = 0; i < ARRAY_COUNT(vars); i++)
{
vars[i] = 0;
// 16 bits per var
for (j = 0; j < 16; j++)
{
#if FREE_MATCH_CALL == FALSE
if (gSaveBlock1Ptr->trainerRematches[16 * i + j])
vars[i] += (1 << j);
#endif //FREE_MATCH_CALL
}
VarSet(VAR_QLBAK_TRAINER_REMATCHES + i, vars[i]);
}
}
static void BackUpMapLayout(void)
{
VarSet(VAR_QLBAK_MAP_LAYOUT, gSaveBlock1Ptr->mapLayoutId);
}
static void RecordSceneEnd(void)
{
QL_RecordAction_SceneEnd(gQuestLogRecordingPointer);
if (++sCurrentSceneNum >= QUEST_LOG_SCENE_COUNT)
sCurrentSceneNum = 0;
}
static bool8 TryRecordActionSequence(struct QuestLogAction * actions)
{
u16 i;
for (i = sRecordSequenceStartIdx; i < gQuestLogCurActionIdx; i++)
{
if (gQuestLogRecordingPointer == NULL)
return FALSE;
switch (actions[i].type)
{
case QL_ACTION_MOVEMENT:
case QL_ACTION_GFX_CHANGE:
gQuestLogRecordingPointer = QL_RecordAction_MovementOrGfxChange(gQuestLogRecordingPointer, &actions[i]);
break;
default:
gQuestLogRecordingPointer = QL_RecordAction_Input(gQuestLogRecordingPointer, &actions[i]);
break;
}
if (gQuestLogRecordingPointer == NULL)
{
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
return FALSE;
}
}
if (gQuestLogPlaybackState == QL_PLAYBACK_STATE_STOPPED)
{
gQuestLogRecordingPointer = QL_RecordAction_SceneEnd(gQuestLogRecordingPointer);
return FALSE;
}
sRecordSequenceStartIdx = gQuestLogCurActionIdx;
return TRUE;
}
void TryStartQuestLogPlayback(u8 taskId)
{
u8 i;
UpdateLoadedSeason();
QL_EnableRecordingSteps();
sNumScenes = 0;
for (i = 0; i < QUEST_LOG_SCENE_COUNT; i++)
{
if (gSaveBlock1Ptr->questLog[i].startType != 0)
sNumScenes++;
}
if (sNumScenes != 0)
{
gHelpSystemEnabled = FALSE;
Task_BeginQuestLogPlayback(taskId);
DestroyTask(taskId);
}
else
{
SetMainCallback2(CB2_ContinueSavedGame);
DestroyTask(taskId);
}
}
static void Task_BeginQuestLogPlayback(u8 taskId)
{
gSaveBlock1Ptr->location.mapGroup = MAP_GROUP(MAP_ROUTE1);
gSaveBlock1Ptr->location.mapNum = MAP_NUM(MAP_ROUTE1);
gSaveBlock1Ptr->location.warpId = WARP_ID_NONE;
sCurrentSceneNum = 0;
gDisableMapMusicChangeOnMapLoad = 1;
DisableWildEncounters(TRUE);
QLPlayback_InitOverworldState();
}
void QL_InitSceneObjectsAndActions(void)
{
ReadQuestLogScriptFromSav1(sCurrentSceneNum, sQuestLogActionRecordBuffer);
QL_ResetRepeatEventTracker();
ResetActions(QL_PLAYBACK_STATE_RUNNING, sQuestLogActionRecordBuffer, sizeof(sQuestLogActionRecordBuffer));
QL_LoadObjectsAndTemplates(sCurrentSceneNum);
}
static bool8 FieldCB2_QuestLogStartPlaybackWithWarpExit(void)
{
LoadPalette(GetTextWindowPalette(4), BG_PLTT_ID(15), PLTT_SIZE_4BPP);
SetQuestLogState(QL_STATE_PLAYBACK);
FieldCB_DefaultWarpExit();
sPlaybackControl = (struct PlaybackControl){};
sPlaybackControl.state = 2;
return 1;
}
static bool8 FieldCB2_QuestLogStartPlaybackStandingInPlace(void)
{
LoadPalette(GetTextWindowPalette(4), BG_PLTT_ID(15), PLTT_SIZE_4BPP);
SetQuestLogState(QL_STATE_PLAYBACK);
FieldCB_WarpExitFadeFromBlack();
sPlaybackControl = (struct PlaybackControl){};
sPlaybackControl.state = 2;
return 1;
}
void DrawPreviouslyOnQuestHeader(u8 sceneNum)
{
u8 i;
for (i = 0; i < WIN_COUNT; i++)
{
sWindowIds[i] = AddWindow(&sWindowTemplates[i]);
FillWindowPixelRect(sWindowIds[i], 15, 0, 0, sWindowTemplates[i].width * 8, sWindowTemplates[i].height * 8);
}
StringExpandPlaceholders(gStringVar4, gText_QuestLog_PreviouslyOnYourQuest);
// Scene numbers count from 4 to 0, 0 being where the player saved
if (sceneNum != 0)
{
ConvertIntToDecimalStringN(gStringVar1, sceneNum, STR_CONV_MODE_LEFT_ALIGN, 1);
StringAppend(gStringVar4, gStringVar1);
}
AddTextPrinterParameterized4(sWindowIds[WIN_TOP_BAR], FONT_NORMAL, 2, 2, 1, 2, sTextColors, 0, gStringVar4);
PutWindowTilemap(sWindowIds[WIN_TOP_BAR]);
PutWindowTilemap(sWindowIds[WIN_BOTTOM_BAR]);
CopyWindowToVram(sWindowIds[WIN_TOP_BAR], COPYWIN_GFX);
CopyWindowToVram(sWindowIds[WIN_DESCRIPTION], COPYWIN_GFX);
CopyWindowToVram(sWindowIds[WIN_BOTTOM_BAR], COPYWIN_FULL);
}
void CommitQuestLogWindow1(void)
{
PutWindowTilemap(sWindowIds[WIN_BOTTOM_BAR]);
CopyWindowToVram(sWindowIds[WIN_BOTTOM_BAR], COPYWIN_MAP);
}
static void QL_LoadObjectsAndTemplates(u8 sceneNum)
{
struct QuestLogScene *questLog = &gSaveBlock1Ptr->questLog[sceneNum];
u16 i;
for (i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
{
if (questLog->objectEventTemplates[i].negx)
gSaveBlock1Ptr->objectEventTemplates[i].x = -questLog->objectEventTemplates[i].x;
else
gSaveBlock1Ptr->objectEventTemplates[i].x = questLog->objectEventTemplates[i].x;
if (questLog->objectEventTemplates[i].negy)
gSaveBlock1Ptr->objectEventTemplates[i].y = -(u8)questLog->objectEventTemplates[i].y;
else
gSaveBlock1Ptr->objectEventTemplates[i].y = questLog->objectEventTemplates[i].y;
gSaveBlock1Ptr->objectEventTemplates[i].elevation = questLog->objectEventTemplates[i].elevation;
gSaveBlock1Ptr->objectEventTemplates[i].movementType = questLog->objectEventTemplates[i].movementType;
}
QL_LoadObjects(questLog, gSaveBlock1Ptr->objectEventTemplates);
}
static void QLPlayback_SetInitialPlayerPosition(u8 sceneNum, bool8 isWarp)
{
if (!isWarp)
{
gSaveBlock1Ptr->location.mapGroup = gSaveBlock1Ptr->questLog[sceneNum].mapGroup;
gSaveBlock1Ptr->location.mapNum = gSaveBlock1Ptr->questLog[sceneNum].mapNum;
gSaveBlock1Ptr->location.warpId = gSaveBlock1Ptr->questLog[sceneNum].warpId;
gSaveBlock1Ptr->pos.x = gSaveBlock1Ptr->questLog[sceneNum].x;
gSaveBlock1Ptr->pos.y = gSaveBlock1Ptr->questLog[sceneNum].y;
}
else
{
struct WarpData warp;
warp.mapGroup = gSaveBlock1Ptr->questLog[sceneNum].mapGroup;
warp.mapNum = gSaveBlock1Ptr->questLog[sceneNum].mapNum;
warp.warpId = gSaveBlock1Ptr->questLog[sceneNum].warpId;
warp.x = gSaveBlock1Ptr->questLog[sceneNum].x;
warp.y = gSaveBlock1Ptr->questLog[sceneNum].y;
Overworld_SetWarpDestinationFromWarp(&warp);
}
}
static void QLPlayback_InitOverworldState(void)
{
gQuestLogState = QL_STATE_PLAYBACK;
ResetSpecialVars();
ClearBag();
ClearPCItemSlots();
if (GetQuestLogStartType() == QL_START_NORMAL)
{
QLPlayback_SetInitialPlayerPosition(sCurrentSceneNum, FALSE);
gFieldCallback2 = FieldCB2_QuestLogStartPlaybackStandingInPlace;
SetMainCallback2(CB2_SetUpOverworldForQLPlayback);
}
else
{
QLPlayback_SetInitialPlayerPosition(sCurrentSceneNum, TRUE);
WarpIntoMap();
gFieldCallback2 = FieldCB2_QuestLogStartPlaybackWithWarpExit;
SetMainCallback2(CB2_SetUpOverworldForQLPlaybackWithWarpExit);
}
}
void QL_CopySaveState(void)
{
struct QuestLogScene * questLog = &gSaveBlock1Ptr->questLog[sCurrentSceneNum];
CpuCopy16(questLog->flags, gSaveBlock1Ptr->flags, sizeof(gSaveBlock1Ptr->flags));
CpuCopy16(questLog->vars, gSaveBlock1Ptr->vars, sizeof(gSaveBlock1Ptr->vars));
RestoreTrainerRematches();
}
// The number of bits allocated to store the number of pokemon in the PC
#define NUM_PC_COUNT_BITS 12
void QL_ResetPartyAndPC(void)
{
struct {
struct Pokemon mon;
u16 partyCount;
u16 boxMonCount;
} *prev = AllocZeroed(sizeof(*prev));
u16 packedCounts, i, count, j;
CreateMon(&prev->mon, SPECIES_RATTATA, 1, USE_RANDOM_IVS, FALSE, 0, OT_ID_PLAYER_ID, 0);
packedCounts = VarGet(VAR_QUEST_LOG_MON_COUNTS);
prev->partyCount = packedCounts >> NUM_PC_COUNT_BITS;
prev->boxMonCount = packedCounts % (1 << NUM_PC_COUNT_BITS);
count = QuestLog_GetPartyCount();
if (count > prev->partyCount)
{
for (i = 0; i < count - prev->partyCount; i++)
ZeroMonData(&gPlayerParty[PARTY_SIZE - 1 - i]);
}
else if (count < prev->partyCount)
{
// Clear 5 slots in the PC?
for (i = 0; i < PARTY_SIZE - 1; i++)
ZeroBoxMonAt(0, i);
// Replace the additional slots with placeholder Pokémon.
for (i = count; i < prev->partyCount; i++)
CopyMon(&gPlayerParty[i], &prev->mon, sizeof(struct Pokemon));
}
count = QuestLog_GetBoxMonCount();
if (count > prev->boxMonCount)
{
for (i = 0; i < TOTAL_BOXES_COUNT; i++)
{
for (j = 0; j < IN_BOX_COUNT; j++)
{
if (GetBoxMonDataAt(i, j, MON_DATA_SANITY_HAS_SPECIES))
{
ZeroBoxMonAt(i, j);
if (--count == prev->boxMonCount)
break;
}
}
if (count == prev->boxMonCount)
break;
}
}
else if (count < prev->boxMonCount)
{
for (i = 0; i < TOTAL_BOXES_COUNT; i++)
{
for (j = 0; j < IN_BOX_COUNT; j++)
{
struct BoxPokemon * boxMon = GetBoxedMonPtr(i, j);
if (!GetBoxMonData(boxMon, MON_DATA_SANITY_HAS_SPECIES))
{
// Replace the additional slots with placeholder Pokémon.
CopyMon(boxMon, &prev->mon.box, sizeof(struct BoxPokemon));
if (++count == prev->boxMonCount)
break;
}
}
if (count == prev->boxMonCount)
break;
}
}
Free(prev);
}
static void SetPokemonCounts(void)
{
u16 partyCount = QuestLog_GetPartyCount();
u16 boxesCount = QuestLog_GetBoxMonCount();
VarSet(VAR_QUEST_LOG_MON_COUNTS, (partyCount << NUM_PC_COUNT_BITS) + boxesCount);
}
static u16 QuestLog_GetPartyCount(void)
{
u16 count = 0;
u16 i;
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_SANITY_HAS_SPECIES))
count++;
}
return count;
}
static u16 QuestLog_GetBoxMonCount(void)
{
u16 count = 0;
u16 i, j;
for (i = 0; i < TOTAL_BOXES_COUNT; i++)
{
for (j = 0; j < IN_BOX_COUNT; j++)
{
if (GetBoxMonDataAt(i, j, MON_DATA_SANITY_HAS_SPECIES))
count++;
}
}
return count;
}
// Inverse of BackUpTrainerRematches
static void RestoreTrainerRematches(void)
{
u16 i, j;
u16 vars[4];
for (i = 0; i < ARRAY_COUNT(vars); i++)
{
vars[i] = VarGet(VAR_QLBAK_TRAINER_REMATCHES + i);
// 16 bits per var
for (j = 0; j < 16; j++)
{
#if FREE_MATCH_CALL == FALSE
if (vars[i] & 1)
gSaveBlock1Ptr->trainerRematches[16 * i + j] = 30;
else
gSaveBlock1Ptr->trainerRematches[16 * i + j] = 0;
#endif //FREE_MATCH_CALL
vars[i] >>= 1;
}
}
}
// Inverse of BackUpMapLayout
void QL_RestoreMapLayoutId(void)
{
gSaveBlock1Ptr->mapLayoutId = VarGet(VAR_QLBAK_MAP_LAYOUT);
if (gSaveBlock1Ptr->mapLayoutId == 0)
{
struct MapHeader header = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum);
gSaveBlock1Ptr->mapLayoutId = header.mapLayoutId;
}
}
static void ReadQuestLogScriptFromSav1(u8 sceneNum, struct QuestLogAction * actions)
{
u16 i;
u16 *script;
u16 actionNum = 0;
u16 eventNum = 0;
memset(actions, 0, ARRAY_COUNT(sEventData) * sizeof(struct QuestLogAction));
for (i = 0; i < ARRAY_COUNT(sEventData); i++)
sEventData[i] = NULL;
script = gSaveBlock1Ptr->questLog[sceneNum].script;
for (i = 0; i < ARRAY_COUNT(sEventData); i++)
{
switch (script[0] & QL_CMD_EVENT_MASK)
{
case QL_EVENT_INPUT:
script = QL_LoadAction_Input(script, &actions[actionNum]);
actionNum++;
break;
case QL_EVENT_GFX_CHANGE:
case QL_EVENT_MOVEMENT:
script = QL_LoadAction_MovementOrGfxChange(script, &actions[actionNum]);
actionNum++;
break;
case QL_EVENT_SCENE_END:
script = QL_LoadAction_SceneEnd(script, &actions[actionNum]);
actionNum++;
break;
case QL_EVENT_WAIT:
script = QL_LoadAction_Wait(script, &actions[actionNum]);
actionNum++;
break;
default: // Normal event
script = QL_SkipCommand(script, &sEventData[eventNum]);
if (eventNum == 0)
QL_UpdateLastDepartedLocation(sEventData[0]);
eventNum++;
break;
}
if (script == NULL)
break;
}
}
static void DoSceneEndTransition(s8 delay)
{
FadeScreen(FADE_TO_BLACK, delay);
sQuestLogCB = QuestLog_AdvancePlayhead;
}
static void QuestLog_AdvancePlayhead(void)
{
if (gPaletteFade.active)
return;
LockPlayerFieldControls();
if (++sCurrentSceneNum < QUEST_LOG_SCENE_COUNT && gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType != 0)
{
sNumScenes--;
QLPlayback_InitOverworldState();
}
else
{
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
QuestLog_StartFinalScene();
}
}
static void QuestLog_StartFinalScene(void)
{
ResetSpecialVars();
Save_ResetSaveCounters();
LoadGameSave(SAVE_NORMAL);
SetMainCallback2(CB2_EnterFieldFromQuestLog);
gFieldCallback2 = FieldCB2_FinalScene;
FreeAllWindowBuffers();
gQuestLogState = QL_STATE_PLAYBACK_LAST;
sQuestLogCB = NULL;
}
void QuestLog_AdvancePlayhead_(void)
{
QuestLog_AdvancePlayhead();
}
#define tTimer data[0]
#define tState data[1]
#define DATA_IDX_CALLBACK 14 // data[14] and data[15]
// This is used to avoid recording or displaying certain windows or images, like a shop menu.
// During playback it returns TRUE (meaning the action should be avoided) and calls the
// provided callback, which would be used to e.g. destroy any resources that were set up to do
// whatever is being avoided. In all cases the provided callback will be QL_DestroyAbortedDisplay.
// If we are not currently in playback return FALSE (meaning allow the action to occur) and
// stop recording (if we are currently).
bool8 QL_AvoidDisplay(void (*callback)(void))
{
u8 taskId;
switch (gQuestLogState)
{
case QL_STATE_RECORDING:
QuestLog_CutRecording();
break;
case QL_STATE_PLAYBACK:
gQuestLogPlaybackState = QL_PLAYBACK_STATE_ACTION_END;
taskId = CreateTask(Task_AvoidDisplay, 80);
gTasks[taskId].tTimer = 0;
gTasks[taskId].tState = 0;
SetWordTaskArg(taskId, DATA_IDX_CALLBACK, (uintptr_t)callback);
return TRUE;
}
return FALSE;
}
static void Task_AvoidDisplay(u8 taskId)
{
void (*routine)(void);
s16 *data = gTasks[taskId].data;
switch (tState)
{
case 0:
// Instead of displaying anything, wait and then end the scene.
if (++tTimer == 127)
{
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, 0);
sPlaybackControl.endMode = END_MODE_SCENE;
tState++;
}
break;
case 1:
if (!gPaletteFade.active)
{
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
// Call the provided function (if any). In practice this is always QL_DestroyAbortedDisplay
routine = (void (*)(void)) GetWordTaskArg(taskId, DATA_IDX_CALLBACK);
if (routine != NULL)
routine();
DestroyTask(taskId);
sQuestLogCB = QuestLog_AdvancePlayhead;
}
break;
}
}
#undef tTimer
#undef tState
static void QuestLog_PlayCurrentEvent(void)
{
if (sPlaybackControl.state == 1)
{
if (--sPlaybackControl.timer != 0)
return;
sPlaybackControl.state = 0;
sPlaybackControl.playingEvent = TRUE;
TogglePlaybackStateForOverworldLock(2);
}
if (sPlaybackControl.playingEvent == TRUE)
{
if (++sPlaybackControl.overlapTimer > 15)
{
QuestLog_CloseTextWindow();
sPlaybackControl.playingEvent = FALSE;
sPlaybackControl.overlapTimer = 0;
}
}
if (sPlaybackControl.cursor < ARRAY_COUNT(sEventData))
{
if (QL_TryRepeatEvent(sEventData[sPlaybackControl.cursor]) == TRUE)
HandleShowQuestLogMessage();
else if (QL_LoadEvent(sEventData[sPlaybackControl.cursor]) == TRUE)
HandleShowQuestLogMessage();
}
}
static void HandleShowQuestLogMessage(void)
{
if (sPlaybackControl.state == 0)
{
sPlaybackControl.state = 1;
sPlaybackControl.playingEvent = FALSE;
sPlaybackControl.overlapTimer = 0;
sPlaybackControl.timer = GetQuestLogTextDisplayDuration();
if (gQuestLogRepeatEventTracker.counter == 0)
sPlaybackControl.cursor++;
if (sPlaybackControl.cursor > ARRAY_COUNT(sEventData))
return;
DrawSceneDescription();
}
TogglePlaybackStateForOverworldLock(1); // lock
}
static u8 GetQuestLogTextDisplayDuration(void)
{
u16 i;
u16 count = 0;
for (i = 0; i < 0x400 && gStringVar4[i] != EOS; i++)
{
if (gStringVar4[i] != CHAR_NEWLINE)
count++;
}
if (count < 20)
return 0x5F;
if (count < 36)
return 0x7F;
if (count < 46)
return 0xBF;
return 0xFF;
}
bool8 QL_IsTrainerSightDisabled(void)
{
if (gQuestLogState != QL_STATE_PLAYBACK)
return FALSE;
if (gQuestLogPlaybackState == QL_PLAYBACK_STATE_STOPPED || sPlaybackControl.state == 1 || sPlaybackControl.state == 2)
return TRUE;
return FALSE;
}
void QL_HandleInput(void)
{
// Ignore input if we're currently ending a scene/playback
if (sPlaybackControl.endMode != END_MODE_NONE)
return;
if (JOY_NEW(A_BUTTON))
{
// Pressed A, skip to next scene
sPlaybackControl.endMode = END_MODE_SCENE;
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
DoSceneEndTransition(-3);
}
else if (JOY_NEW(B_BUTTON))
{
// Pressed B, end playback
sPlaybackControl.endMode = END_MODE_FINISH;
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
DoSkipToEndTransition(-3);
}
}
bool8 QuestLogScenePlaybackIsEnding(void)
{
if (sPlaybackControl.endMode != END_MODE_NONE)
return TRUE;
return FALSE;
}
void QuestLog_DrawPreviouslyOnQuestHeaderIfInPlaybackMode(void)
{
if (gQuestLogState == QL_STATE_PLAYBACK)
DrawPreviouslyOnQuestHeader(sNumScenes);
}
static void DrawSceneDescription(void)
{
u16 i;
u8 numLines = 0;
for (i = 0; i < 0x100 && gStringVar4[i] != EOS; i++)
{
if (gStringVar4[i] == CHAR_NEWLINE)
numLines++;
}
PutWindowTilemap(sWindowIds[WIN_DESCRIPTION]);
CopyDescriptionWindowTiles(sWindowIds[WIN_DESCRIPTION]);
AddTextPrinterParameterized4(sWindowIds[WIN_DESCRIPTION], FONT_NORMAL, 2, sQuestLogTextLineYCoords[numLines], 1, 0, sTextColors, 0, gStringVar4);
ScheduleBgCopyTilemapToVram(0);
}
static void CopyDescriptionWindowTiles(u8 windowId)
{
const u16 *src = sDescriptionWindow_Gfx;
u16 *buffer = Alloc(DESC_WIN_SIZE);
u8 i, j, y;
if (buffer)
{
for (i = 0; i < DESC_WIN_HEIGHT; i++)
{
switch (i)
{
default:
// Middle tile
y = 1;
break;
case 0:
// Top edge tile
y = 0;
break;
case DESC_WIN_HEIGHT - 1:
// Bottom edge tile
y = 2;
break;
}
for (j = 0; j < DESC_WIN_WIDTH; j++)
CpuCopy32(src + 16 * y, buffer + 16 * (2 * (15 * i) + j), TILE_SIZE_4BPP);
}
CopyToWindowPixelBuffer(windowId, (const u8 *)buffer, DESC_WIN_SIZE, 0);
Free(buffer);
}
}
static void QuestLog_CloseTextWindow(void)
{
ClearWindowTilemap(sWindowIds[WIN_DESCRIPTION]);
FillWindowPixelRect(sWindowIds[WIN_DESCRIPTION], 15, 0, 0, 0xf0, 0x30);
CopyWindowToVram(sWindowIds[WIN_DESCRIPTION], COPYWIN_GFX);
PutWindowTilemap(sWindowIds[WIN_BOTTOM_BAR]);
CopyWindowToVram(sWindowIds[WIN_BOTTOM_BAR], COPYWIN_MAP);
}
static void DoSkipToEndTransition(s8 delay)
{
FadeScreen(FADE_TO_BLACK, delay);
sQuestLogCB = QuestLog_WaitFadeAndCancelPlayback;
}
static void QuestLog_WaitFadeAndCancelPlayback(void)
{
if (!gPaletteFade.active)
{
LockPlayerFieldControls();
for (sCurrentSceneNum = sCurrentSceneNum; sCurrentSceneNum < QUEST_LOG_SCENE_COUNT; sCurrentSceneNum++)
{
if (gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType == 0)
break;
ReadQuestLogScriptFromSav1(sCurrentSceneNum, sQuestLogActionRecordBuffer);
}
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
QuestLog_StartFinalScene();
}
}
void QuestLog_InitPalettesBackup(void)
{
if (gQuestLogState == QL_STATE_PLAYBACK_LAST)
sPalettesBackup = AllocZeroed(PLTT_SIZE);
}
void QuestLog_BackUpPalette(u16 offset, u16 size)
{
CpuCopy16(&gPlttBufferUnfaded[offset], &sPalettesBackup[offset], PLTT_SIZEOF(size));
}
static bool8 FieldCB2_FinalScene(void)
{
LoadPalette(GetTextWindowPalette(4), BG_PLTT_ID(15), PLTT_SIZE_4BPP);
DrawPreviouslyOnQuestHeader(0);
FieldCB_WarpExitFadeFromBlack();
CreateTask(Task_FinalScene_WaitFade, 0xFF);
return TRUE;
}
static void Task_FinalScene_WaitFade(u8 taskId)
{
struct Task *task = &gTasks[taskId];
if (ArePlayerFieldControlsLocked() != TRUE)
{
FreezeObjectEvents();
HandleEnforcedLookDirectionOnPlayerStopMoving();
StopPlayerAvatar();
LockPlayerFieldControls();
task->func = Task_QuestLogScene_SavedGame;
}
}
static void Task_QuestLogScene_SavedGame(u8 taskId)
{
struct Task *task = &gTasks[taskId];
if (!gPaletteFade.active)
{
if (sPlaybackControl.endMode != END_MODE_FINISH)
{
GetMapNameGeneric(gStringVar1, gMapHeader.regionMapSectionId);
StringExpandPlaceholders(gStringVar4, gText_QuestLog_SavedGameAtLocation);
DrawSceneDescription();
}
task->data[0] = 0;
task->data[1] = 0;
task->func = Task_WaitAtEndOfQuestLog;
FreezeObjectEvents();
LockPlayerFieldControls();
}
}
#define tTimer data[0]
static void Task_WaitAtEndOfQuestLog(u8 taskId)
{
struct Task *task = &gTasks[taskId];
if (JOY_NEW(A_BUTTON | B_BUTTON) || task->tTimer >= 127 || sPlaybackControl.endMode == END_MODE_FINISH)
{
QuestLog_CloseTextWindow();
task->tTimer = 0;
task->func = Task_EndQuestLog;
gQuestLogState = 0;
}
else
task->tTimer++;
}
#undef tTimer
#define tState data[0]
#define tTimer data[1]
static void Task_EndQuestLog(u8 taskId)
{
s16 *data = gTasks[taskId].data;
u8 i;
switch (tState)
{
case 0:
gDisableMapMusicChangeOnMapLoad = 0;
Overworld_PlaySpecialMapMusic();
QL_SlightlyDarkenSomePals();
FillWindowPixelRect(sWindowIds[WIN_TOP_BAR],
0xF, 0, 0,
sWindowTemplates[WIN_TOP_BAR].width * 8,
sWindowTemplates[WIN_TOP_BAR].height * 8);
tState++;
break;
case 1:
if (RestoreScreenAfterPlayback(taskId))
{
for (i = 0; i < WIN_COUNT; i++)
{
ClearWindowTilemap(sWindowIds[i]);
CopyWindowToVram(sWindowIds[i], COPYWIN_MAP);
RemoveWindow(sWindowIds[i]);
}
tTimer = 0;
tState++;
}
break;
case 2:
if (tTimer < 32)
tTimer++;
else
tState++;
break;
default:
if (sPlaybackControl.endMode == END_MODE_FINISH)
ShowMapNamePopup(TRUE);
CpuCopy16(sPalettesBackup, gPlttBufferUnfaded, PLTT_SIZE);
Free(sPalettesBackup);
sPlaybackControl = (struct PlaybackControl){};
ClearPlayerHeldMovementAndUnfreezeObjectEvents();
UnlockPlayerFieldControls();
gTextFlags.autoScroll = FALSE;
gGlobalFieldTintMode = QL_TINT_NONE;
DisableWildEncounters(FALSE);
gHelpSystemEnabled = TRUE;
DestroyTask(taskId);
break;
}
}
#undef tState
#undef tTimer
#define tTimer data[1]
// Scroll the top and bottom windows offscreen and restore the screen tint to the original color.
static bool8 RestoreScreenAfterPlayback(u8 taskId)
{
s16 *data = gTasks[taskId].data;
if (tTimer > 15)
return TRUE;
CopyPaletteInvertedTint(&gPlttBufferUnfaded[BG_PLTT_ID(0) + 1], &gPlttBufferFaded[BG_PLTT_ID(0) + 1], 0xDF, 15 - tTimer);
CopyPaletteInvertedTint(&gPlttBufferUnfaded[OBJ_PLTT_ID(0)], &gPlttBufferFaded[OBJ_PLTT_ID(0)], 0x100, 15 - tTimer);
gTimeUpdateCounter = 0;
UpdateTimeOfDay();
if (MapHasNaturalLight(gMapHeader.mapType))
{
UpdateAltBgPalettes(PALETTES_BG);
UpdatePalettesWithTime(PALETTES_ALL);
}
FillWindowPixelRect(sWindowIds[WIN_TOP_BAR],
0x00, 0,
sWindowTemplates[WIN_TOP_BAR].height * 8 - 1 - tTimer,
sWindowTemplates[WIN_TOP_BAR].width * 8, 1);
FillWindowPixelRect(sWindowIds[WIN_BOTTOM_BAR],
0x00, 0,
data[1],
sWindowTemplates[WIN_BOTTOM_BAR].width * 8, 1);
CopyWindowToVram(sWindowIds[WIN_TOP_BAR], COPYWIN_GFX);
CopyWindowToVram(sWindowIds[WIN_BOTTOM_BAR], COPYWIN_GFX);
tTimer++;
return FALSE;
}
static void QL_SlightlyDarkenSomePals(void)
{
u16 *buffer = Alloc(PLTT_SIZE);
CpuCopy16(sPalettesBackup, buffer, PLTT_SIZE);
SlightlyDarkenPalsInWeather(sPalettesBackup, sPalettesBackup, 13 * 16);
SlightlyDarkenPalsInWeather(&sPalettesBackup[OBJ_PLTT_ID(1)], &sPalettesBackup[OBJ_PLTT_ID(1)], 1 * 16);
SlightlyDarkenPalsInWeather(&sPalettesBackup[OBJ_PLTT_ID(6)], &sPalettesBackup[OBJ_PLTT_ID(6)], 4 * 16);
SlightlyDarkenPalsInWeather(&sPalettesBackup[OBJ_PLTT_ID(11)], &sPalettesBackup[OBJ_PLTT_ID(11)], 5 * 16);
CpuCopy16(sPalettesBackup, gPlttBufferUnfaded, PLTT_SIZE);
CpuCopy16(buffer, sPalettesBackup, PLTT_SIZE);
Free(buffer);
}
void QL_FinishRecordingScene(void)
{
if (gQuestLogState == QL_STATE_RECORDING)
{
TryRecordActionSequence(sQuestLogActionRecordBuffer);
RecordSceneEnd();
gQuestLogState = 0;
sQuestLogCB = NULL;
gQuestLogDefeatedWildMonRecord = NULL;
gQuestLogRecordingPointer = NULL;
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
}
}
void QuestLog_CutRecording(void)
{
if (gQuestLogPlaybackState != QL_PLAYBACK_STATE_STOPPED && gQuestLogState == QL_STATE_RECORDING)
{
TryRecordActionSequence(sQuestLogActionRecordBuffer);
QL_RecordWait(1);
RecordSceneEnd();
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
gQuestLogState = 0;
sQuestLogCB = NULL;
}
gQuestLogDefeatedWildMonRecord = NULL;
gQuestLogRecordingPointer = NULL;
}
static void SortQuestLogInSav1(void)
{
struct QuestLogScene * buffer = AllocZeroed(sizeof(gSaveBlock1Ptr->questLog));
u8 i;
u8 sceneNum = sCurrentSceneNum;
u8 count = 0;
for (i = 0; i < QUEST_LOG_SCENE_COUNT; i++)
{
if (sceneNum >= QUEST_LOG_SCENE_COUNT)
sceneNum = 0;
if (gSaveBlock1Ptr->questLog[sceneNum].startType != 0)
{
buffer[count] = gSaveBlock1Ptr->questLog[sceneNum];
count++;
}
sceneNum++;
}
sCurrentSceneNum = count % QUEST_LOG_SCENE_COUNT;
CpuCopy16(buffer, gSaveBlock1Ptr->questLog, sizeof(gSaveBlock1Ptr->questLog));
Free(buffer);
}
void SaveQuestLogData(void)
{
if (MenuHelpers_IsLinkActive() != TRUE)
{
QuestLog_CutRecording();
SortQuestLogInSav1();
}
}
void QL_UpdateObject(struct Sprite *sprite)
{
// index 0 is reserved for player, index 1 is reserved for follower
// other ObjectsEvents are at index localId + 1
struct ObjectEvent *objectEvent = &gObjectEvents[sprite->data[0]];
if (objectEvent->localId == LOCALID_PLAYER)
{
if (sMovementScripts[0][0] != MOVEMENT_ACTION_NONE)
{
ObjectEventSetHeldMovement(objectEvent, sMovementScripts[0][0]);
sMovementScripts[0][0] = MOVEMENT_ACTION_NONE;
}
if (sMovementScripts[0][1] != QL_PLAYER_GFX_NONE)
{
QuestLogUpdatePlayerSprite(sMovementScripts[0][1]);
sMovementScripts[0][1] = QL_PLAYER_GFX_NONE;
}
QL_UpdateObjectEventCurrentMovement(objectEvent, sprite);
}
else if (objectEvent->localId == OBJ_EVENT_ID_FOLLOWER)
{
if (sMovementScripts[1][0] != MOVEMENT_ACTION_NONE)
{
ObjectEventSetHeldMovement(objectEvent, sMovementScripts[1][0]);
sMovementScripts[1][0] = MOVEMENT_ACTION_NONE;
}
QL_UpdateObjectEventCurrentMovement(objectEvent, sprite);
}
else
{
if (sMovementScripts[objectEvent->localId][0] != MOVEMENT_ACTION_NONE)
{
ObjectEventSetHeldMovement(objectEvent, sMovementScripts[objectEvent->localId + 1][0]);
sMovementScripts[objectEvent->localId + 1][0] = MOVEMENT_ACTION_NONE;
}
QL_UpdateObjectEventCurrentMovement(objectEvent, sprite);
}
}
void QuestLogRecordNPCStep(u8 localId, u8 mapNum, u8 mapGroup, u8 movementActionId)
{
if (!RecordHeadAtEndOfEntryOrScriptContext2Enabled())
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_MOVEMENT;
if (localId == OBJ_EVENT_ID_FOLLOWER)
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = 1;
else
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = localId + 1;
sCurSceneActions[gQuestLogCurActionIdx].data.a.mapNum = mapNum;
sCurSceneActions[gQuestLogCurActionIdx].data.a.mapGroup = mapGroup;
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = movementActionId;
gQuestLogCurActionIdx++;
sNextActionDelay = 0;
}
}
void QuestLogRecordNPCStepWithDuration(u8 localId, u8 mapNum, u8 mapGroup, u8 movementActionId, u8 duration)
{
if (!RecordHeadAtEndOfEntry())
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_MOVEMENT;
if (localId == OBJ_EVENT_ID_FOLLOWER)
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = 1;
else
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = localId + 1;
sCurSceneActions[gQuestLogCurActionIdx].data.a.mapNum = mapNum;
sCurSceneActions[gQuestLogCurActionIdx].data.a.mapGroup = mapGroup;
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = movementActionId;
gQuestLogCurActionIdx++;
sNextActionDelay = duration;
}
}
void QuestLogRecordPlayerStep(u8 movementActionId)
{
if (!RecordHeadAtEndOfEntryOrScriptContext2Enabled())
{
if (movementActionId != sCurSceneActions[sLastQuestLogCursor].data.a.movementActionId || movementActionId > MOVEMENT_ACTION_FACE_RIGHT)
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_MOVEMENT;
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = movementActionId;
sLastQuestLogCursor = gQuestLogCurActionIdx;
gQuestLogCurActionIdx++;
sNextActionDelay = 0;
}
}
}
void QuestLogRecordPlayerStepWithDuration(u8 movementActionId, u8 duration)
{
if (!RecordHeadAtEndOfEntry())
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_MOVEMENT;
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = movementActionId;
sLastQuestLogCursor = gQuestLogCurActionIdx;
gQuestLogCurActionIdx++;
sNextActionDelay = duration;
}
}
void QuestLogRecordPlayerAvatarGfxTransition(u8 gfxState)
{
if (!RecordHeadAtEndOfEntry())
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_GFX_CHANGE;
sCurSceneActions[gQuestLogCurActionIdx].data.b.localId = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.b.gfxState = gfxState;
gQuestLogCurActionIdx++;
sNextActionDelay = 0;
}
}
void QuestLogRecordPlayerAvatarGfxTransitionWithDuration(u8 gfxState, u8 duration)
{
if (!RecordHeadAtEndOfEntry())
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_GFX_CHANGE;
sCurSceneActions[gQuestLogCurActionIdx].data.b.localId = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.b.gfxState = gfxState;
gQuestLogCurActionIdx++;
sNextActionDelay = duration;
}
}
void QL_RecordFieldInput(struct FieldInput * fieldInput)
{
if (gQuestLogCurActionIdx < sMaxActionsInScene)
{
// Retain only the following fields:
// - pressedAButton
// - checkStandardWildEncounter
// - heldDirection
// - heldDirection2
// - tookStep
// - pressedBButton
// - dpadDirection
u32 data = *(u32 *)fieldInput & 0x00FF00F3;
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_INPUT;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[0] = data;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[1] = data >> 8; // always 0
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[2] = data >> 16;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[3] = data >> 24; // always 0
gQuestLogCurActionIdx++;
if (ArePlayerFieldControlsLocked())
sNextActionDelay = 1;
else
sNextActionDelay = 0;
}
}
static void TogglePlaybackStateForOverworldLock(u8 a0)
{
switch (a0)
{
case 1:
if (gQuestLogPlaybackState == QL_PLAYBACK_STATE_RUNNING)
gQuestLogPlaybackState = QL_PLAYBACK_STATE_ACTION_END; // Message visible, overworld locked
break;
case 2:
if (gQuestLogPlaybackState == QL_PLAYBACK_STATE_ACTION_END)
gQuestLogPlaybackState = QL_PLAYBACK_STATE_RUNNING; // Overworld unlocked
break;
}
}
void QuestLog_OnEscalatorWarp(u8 direction)
{
u8 state = QL_GetPlaybackState();
switch (direction)
{
case QL_ESCALATOR_OUT: // warp out
if (state == QL_PLAYBACK_STATE_RUNNING)
gQuestLogPlaybackState = QL_PLAYBACK_STATE_ACTION_END;
else if (state == QL_PLAYBACK_STATE_RECORDING)
{
sCurSceneActions[gQuestLogCurActionIdx].duration = sNextActionDelay;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_EMPTY;
gQuestLogCurActionIdx++;
sNextActionDelay = 0;
gQuestLogPlaybackState = QL_PLAYBACK_STATE_RECORDING_NO_DELAY;
}
break;
case QL_ESCALATOR_IN: // warp in
if (state == QL_PLAYBACK_STATE_RUNNING)
gQuestLogPlaybackState = QL_PLAYBACK_STATE_RUNNING;
else if (state == QL_PLAYBACK_STATE_RECORDING)
gQuestLogPlaybackState = QL_PLAYBACK_STATE_RECORDING;
break;
}
}
static void ResetActions(u8 kind, struct QuestLogAction *actions, u16 size)
{
int i;
switch (kind)
{
default:
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
break;
case QL_PLAYBACK_STATE_RUNNING:
sCurSceneActions = actions;
sMaxActionsInScene = size / sizeof(*sCurSceneActions);
for (i = 0; i < (s32)ARRAY_COUNT(sMovementScripts); i++)
{
sMovementScripts[i][0] |= MOVEMENT_ACTION_NONE;
sMovementScripts[i][1] |= QL_PLAYER_GFX_NONE;
}
gQuestLogCurActionIdx = 0;
sLastQuestLogCursor = 0;
gQuestLogFieldInput = (struct FieldInput){};
sNextActionDelay = sCurSceneActions[gQuestLogCurActionIdx].duration;
sMovementScripts[0][0] = sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId;
sMovementScripts[0][1] = QL_PLAYER_GFX_NONE;
gQuestLogPlaybackState = QL_PLAYBACK_STATE_RUNNING;
break;
case QL_PLAYBACK_STATE_RECORDING:
sCurSceneActions = actions;
sMaxActionsInScene = size / sizeof(*sCurSceneActions);
for (i = 0; i < sMaxActionsInScene; i++)
{
sCurSceneActions[i] = (struct QuestLogAction){
.duration = 0xFFFF,
.type = QL_ACTION_SCENE_END
};
}
gQuestLogCurActionIdx = 0;
sNextActionDelay = 0;
sCurSceneActions[gQuestLogCurActionIdx].duration = 0;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_MOVEMENT;
sCurSceneActions[gQuestLogCurActionIdx].data.a.localId = 0;
switch (GetPlayerFacingDirection())
{
case DIR_NONE:
case DIR_SOUTH:
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = MOVEMENT_ACTION_FACE_DOWN;
break;
case DIR_EAST:
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = MOVEMENT_ACTION_FACE_RIGHT;
break;
case DIR_NORTH:
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = MOVEMENT_ACTION_FACE_UP;
break;
case DIR_WEST:
sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId = MOVEMENT_ACTION_FACE_LEFT;
break;
}
sLastQuestLogCursor = 0;
gQuestLogCurActionIdx++;
sCurSceneActions[gQuestLogCurActionIdx].duration = 0;
sCurSceneActions[gQuestLogCurActionIdx].type = QL_ACTION_INPUT;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[0] = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[1] = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[2] = 0;
sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[3] = 0;
gQuestLogCurActionIdx++;
gQuestLogPlaybackState = QL_PLAYBACK_STATE_RECORDING;
break;
}
}
void QL_TryRunActions(void)
{
switch (gQuestLogPlaybackState)
{
case QL_PLAYBACK_STATE_STOPPED:
case QL_PLAYBACK_STATE_ACTION_END:
case QL_PLAYBACK_STATE_RECORDING_NO_DELAY:
break;
case QL_PLAYBACK_STATE_RUNNING:
if (!RecordHeadAtEndOfEntryOrScriptContext2Enabled())
{
if (sNextActionDelay != 0)
{
sNextActionDelay--;
}
else
{
do
{
switch (sCurSceneActions[gQuestLogCurActionIdx].type)
{
case QL_ACTION_MOVEMENT:
// NPC movement action
sMovementScripts[sCurSceneActions[gQuestLogCurActionIdx].data.a.localId][0] = sCurSceneActions[gQuestLogCurActionIdx].data.a.movementActionId;
break;
case QL_ACTION_GFX_CHANGE:
// State transition
sMovementScripts[sCurSceneActions[gQuestLogCurActionIdx].data.b.localId][1] = sCurSceneActions[gQuestLogCurActionIdx].data.b.gfxState;
break;
case QL_ACTION_INPUT:
// Player input
*(u32 *)&gQuestLogFieldInput = ((sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[3] << 24)
| (sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[2] << 16)
| (sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[1] << 8)
| (sCurSceneActions[gQuestLogCurActionIdx].data.fieldInput[0] << 0));
break;
case QL_ACTION_EMPTY:
// End
gQuestLogPlaybackState = QL_PLAYBACK_STATE_ACTION_END;
break;
case QL_ACTION_WAIT:
// Nothing. The wait action uses sNextActionDelay to add a pause to playback.
// When the counter is finished and this is reached there's nothing else that needs to be done.
break;
case QL_ACTION_SCENE_END:
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
break;
}
if (gQuestLogPlaybackState == QL_PLAYBACK_STATE_STOPPED)
break;
if (++gQuestLogCurActionIdx >= sMaxActionsInScene)
{
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
break;
}
sNextActionDelay = sCurSceneActions[gQuestLogCurActionIdx].duration;
} while (gQuestLogPlaybackState != QL_PLAYBACK_STATE_ACTION_END && (sNextActionDelay == 0 || sNextActionDelay == 0xFFFF));
}
}
else if (gQuestLogCurActionIdx >= sMaxActionsInScene)
{
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
}
break;
case QL_PLAYBACK_STATE_RECORDING:
if (ArePlayerFieldControlsLocked() != TRUE)
{
sNextActionDelay++;
if (gQuestLogCurActionIdx >= sMaxActionsInScene)
gQuestLogPlaybackState = QL_PLAYBACK_STATE_STOPPED;
}
break;
}
}
void QL_AfterRecordFishActionSuccessful(void)
{
sNextActionDelay++;
}
u8 QL_GetPlaybackState(void)
{
switch (gQuestLogPlaybackState)
{
case QL_PLAYBACK_STATE_STOPPED:
default:
return QL_PLAYBACK_STATE_STOPPED;
case QL_PLAYBACK_STATE_RUNNING:
case QL_PLAYBACK_STATE_ACTION_END:
return QL_PLAYBACK_STATE_RUNNING;
case QL_PLAYBACK_STATE_RECORDING:
case QL_PLAYBACK_STATE_RECORDING_NO_DELAY:
return QL_PLAYBACK_STATE_RECORDING;
}
}
static bool8 RecordHeadAtEndOfEntryOrScriptContext2Enabled(void)
{
if (gQuestLogCurActionIdx >= sMaxActionsInScene || ArePlayerFieldControlsLocked() == TRUE)
return TRUE;
return FALSE;
}
static bool8 RecordHeadAtEndOfEntry(void)
{
if (gQuestLogCurActionIdx >= sMaxActionsInScene)
return TRUE;
return FALSE;
}
static const struct FlagOrVarRecord sDummyFlagOrVarRecord = {
.idx = 0,
.isFlag = FALSE,
.value = 0x7FFF
};
void *QuestLogGetFlagOrVarPtr(bool8 isFlag, u16 idx)
{
void *response;
if (gQuestLogCurActionIdx == 0)
return NULL;
if (gQuestLogCurActionIdx >= sMaxActionsInScene)
return NULL;
if (sFlagOrVarPlayhead >= sNumFlagsOrVars)
return NULL;
if (sFlagOrVarRecords[sFlagOrVarPlayhead].idx == idx && sFlagOrVarRecords[sFlagOrVarPlayhead].isFlag == isFlag)
{
response = &sFlagOrVarRecords[sFlagOrVarPlayhead].value;
sFlagOrVarPlayhead++;
}
else
response = NULL;
return response;
}
void QuestLogSetFlagOrVar(bool8 isFlag, u16 idx, u16 value)
{
if (gQuestLogCurActionIdx == 0)
return;
if (gQuestLogCurActionIdx >= sMaxActionsInScene)
return;
if (sFlagOrVarPlayhead >= sNumFlagsOrVars)
return;
sFlagOrVarRecords[sFlagOrVarPlayhead].idx = idx;
sFlagOrVarRecords[sFlagOrVarPlayhead].isFlag = isFlag;
sFlagOrVarRecords[sFlagOrVarPlayhead].value = value;
sFlagOrVarPlayhead++;
}