pokeemmo/src/main.c
Martin Griffin bb781f21a1
Arbitrary trainer scripts + map script/trigger softlock prevention (#5033)
Script_RunImmediatelyUntilEffect runs a script until either a specified
effect may occur or it reaches an end.

All existing script commands and natives, and some specials, call
Script_RequestEffects which allows us to analyze them.

Any downstream script commands/natives/specials will be statically known
not to call Script_RequestEffects and treated as if they have all
effects. Manually tagging them with requests_effects=1 and calling
Script_RequestEffects will make them analyzable.

Using these, we're able to execute scripts until they either exit with
no effect, or would possibly have an effect. This allows us to:
1. Not run on frame map scripts or triggers if they would have no
   effect.
2. Immediately run triggers if they only affect flags/vars. This removes
   the lag frames when biking into the Cycling Road, for example.
3. Migrate on load/on transition/on resume/on return to field/on dive
   warp scripts onto the global script context if they would block
   (approximated via SCREFF_HARDWARE).
4. Support arbitrary control flow in trainer scripts. The trainer does
   not see the player if the script has no effect, and the trainer will
   use whichever trainerbattle command is branched to.
5. Support arbitrary scripts in trainer scripts. cant_see and
   cant_see_if_* commands have been introduced so that scripts are able
   to do something when the player interacts with the trainer even if
   that trainer wouldn't see them.
2025-01-08 10:27:00 +00:00

470 lines
11 KiB
C

#include "global.h"
#include "crt0.h"
#include "malloc.h"
#include "link.h"
#include "link_rfu.h"
#include "librfu.h"
#include "m4a.h"
#include "bg.h"
#include "rtc.h"
#include "scanline_effect.h"
#include "overworld.h"
#include "play_time.h"
#include "random.h"
#include "dma3.h"
#include "gba/flash_internal.h"
#include "load_save.h"
#include "gpu_regs.h"
#include "agb_flash.h"
#include "sound.h"
#include "battle.h"
#include "battle_controllers.h"
#include "text.h"
#include "intro.h"
#include "main.h"
#include "trainer_hill.h"
#include "test_runner.h"
#include "constants/rgb.h"
static void VBlankIntr(void);
static void HBlankIntr(void);
static void VCountIntr(void);
static void SerialIntr(void);
static void IntrDummy(void);
// Defined in the linker script so that the test build can override it.
extern void gInitialMainCB2(void);
extern void CB2_FlashNotDetectedScreen(void);
const u8 gGameVersion = GAME_VERSION;
const u8 gGameLanguage = GAME_LANGUAGE; // English
const char BuildDateTime[] = "2005 02 21 11:10";
const IntrFunc gIntrTableTemplate[] =
{
VCountIntr, // V-count interrupt
SerialIntr, // Serial interrupt
Timer3Intr, // Timer 3 interrupt
HBlankIntr, // H-blank interrupt
VBlankIntr, // V-blank interrupt
IntrDummy, // Timer 0 interrupt
IntrDummy, // Timer 1 interrupt
IntrDummy, // Timer 2 interrupt
IntrDummy, // DMA 0 interrupt
IntrDummy, // DMA 1 interrupt
IntrDummy, // DMA 2 interrupt
IntrDummy, // DMA 3 interrupt
IntrDummy, // Key interrupt
IntrDummy, // Game Pak interrupt
};
#define INTR_COUNT ((int)(sizeof(gIntrTableTemplate)/sizeof(IntrFunc)))
COMMON_DATA u16 gKeyRepeatStartDelay = 0;
COMMON_DATA bool8 gLinkTransferringData = 0;
COMMON_DATA struct Main gMain = {0};
COMMON_DATA u16 gKeyRepeatContinueDelay = 0;
COMMON_DATA bool8 gSoftResetDisabled = 0;
COMMON_DATA IntrFunc gIntrTable[INTR_COUNT] = {0};
COMMON_DATA u8 gLinkVSyncDisabled = 0;
COMMON_DATA s8 gPcmDmaCounter = 0;
COMMON_DATA void *gAgbMainLoop_sp = NULL;
static EWRAM_DATA u16 sTrainerId = 0;
//EWRAM_DATA void (**gFlashTimerIntrFunc)(void) = NULL;
static void UpdateLinkAndCallCallbacks(void);
static void InitMainCallbacks(void);
static void CallCallbacks(void);
#ifdef BUGFIX
static void SeedRngWithRtc(void);
#endif
static void ReadKeys(void);
void InitIntrHandlers(void);
static void WaitForVBlank(void);
void EnableVCountIntrAtLine150(void);
#define B_START_SELECT (B_BUTTON | START_BUTTON | SELECT_BUTTON)
void AgbMain()
{
*(vu16 *)BG_PLTT = RGB_WHITE; // Set the backdrop to white on startup
InitGpuRegManager();
REG_WAITCNT = WAITCNT_PREFETCH_ENABLE
| WAITCNT_WS0_S_1 | WAITCNT_WS0_N_3
| WAITCNT_WS1_S_1 | WAITCNT_WS1_N_3;
InitKeys();
InitIntrHandlers();
m4aSoundInit();
EnableVCountIntrAtLine150();
InitRFU();
RtcInit();
CheckForFlashMemory();
InitMainCallbacks();
InitMapMusic();
#ifdef BUGFIX
SeedRngWithRtc(); // see comment at SeedRngWithRtc definition below
#endif
ClearDma3Requests();
ResetBgs();
SetDefaultFontsPointer();
InitHeap(gHeap, HEAP_SIZE);
gSoftResetDisabled = FALSE;
if (gFlashMemoryPresent != TRUE)
SetMainCallback2((SAVE_TYPE_ERROR_SCREEN) ? CB2_FlashNotDetectedScreen : NULL);
gLinkTransferringData = FALSE;
#ifndef NDEBUG
#if (LOG_HANDLER == LOG_HANDLER_MGBA_PRINT)
(void) MgbaOpen();
#elif (LOG_HANDLER == LOG_HANDLER_AGB_PRINT)
AGBPrintfInit();
#endif
#endif
gAgbMainLoop_sp = __builtin_frame_address(0);
AgbMainLoop();
}
void AgbMainLoop(void)
{
for (;;)
{
ReadKeys();
if (gSoftResetDisabled == FALSE
&& JOY_HELD_RAW(A_BUTTON)
&& JOY_HELD_RAW(B_START_SELECT) == B_START_SELECT)
{
rfu_REQ_stopMode();
rfu_waitREQComplete();
DoSoftReset();
}
if (Overworld_SendKeysToLinkIsRunning() == TRUE)
{
gLinkTransferringData = TRUE;
UpdateLinkAndCallCallbacks();
gLinkTransferringData = FALSE;
}
else
{
gLinkTransferringData = FALSE;
UpdateLinkAndCallCallbacks();
if (Overworld_RecvKeysFromLinkIsRunning() == TRUE)
{
gMain.newKeys = 0;
ClearSpriteCopyRequests();
gLinkTransferringData = TRUE;
UpdateLinkAndCallCallbacks();
gLinkTransferringData = FALSE;
}
}
PlayTimeCounter_Update();
MapMusicMain();
WaitForVBlank();
}
}
static void UpdateLinkAndCallCallbacks(void)
{
if (!HandleLinkConnection())
CallCallbacks();
}
static void InitMainCallbacks(void)
{
gMain.vblankCounter1 = 0;
gTrainerHillVBlankCounter = NULL;
gMain.vblankCounter2 = 0;
gMain.callback1 = NULL;
SetMainCallback2(gInitialMainCB2);
gSaveBlock2Ptr = &gSaveblock2.block;
gPokemonStoragePtr = &gPokemonStorage.block;
}
static void CallCallbacks(void)
{
if (gMain.callback1)
gMain.callback1();
if (gMain.callback2)
gMain.callback2();
}
void SetMainCallback2(MainCallback callback)
{
gMain.callback2 = callback;
gMain.state = 0;
}
void StartTimer1(void)
{
REG_TM2CNT_L = 0;
REG_TM2CNT_H = TIMER_ENABLE | TIMER_COUNTUP;
REG_TM1CNT_H = TIMER_ENABLE;
}
void SeedRngAndSetTrainerId(void)
{
u32 val;
REG_TM1CNT_H = 0;
REG_TM2CNT_H = 0;
val = ((u32)REG_TM2CNT_L) << 16;
val |= REG_TM1CNT_L;
SeedRng(val);
sTrainerId = Random();
}
u16 GetGeneratedTrainerIdLower(void)
{
return sTrainerId;
}
void EnableVCountIntrAtLine150(void)
{
u16 gpuReg = (GetGpuReg(REG_OFFSET_DISPSTAT) & 0xFF) | (150 << 8);
SetGpuReg(REG_OFFSET_DISPSTAT, gpuReg | DISPSTAT_VCOUNT_INTR);
EnableInterrupts(INTR_FLAG_VCOUNT);
}
// FRLG commented this out to remove RTC, however Emerald didn't undo this!
#ifdef BUGFIX
static void SeedRngWithRtc(void)
{
#define BCD8(x) ((((x) >> 4) & 0xF) * 10 + ((x) & 0xF))
u32 seconds;
struct SiiRtcInfo rtc;
RtcGetInfo(&rtc);
seconds =
((HOURS_PER_DAY * RtcGetDayCount(&rtc) + BCD8(rtc.hour))
* MINUTES_PER_HOUR + BCD8(rtc.minute))
* SECONDS_PER_MINUTE + BCD8(rtc.second);
SeedRng(seconds);
#undef BCD8
}
#endif
void InitKeys(void)
{
gKeyRepeatContinueDelay = 5;
gKeyRepeatStartDelay = 40;
gMain.heldKeys = 0;
gMain.newKeys = 0;
gMain.newAndRepeatedKeys = 0;
gMain.heldKeysRaw = 0;
gMain.newKeysRaw = 0;
}
static void ReadKeys(void)
{
u16 keyInput = REG_KEYINPUT ^ KEYS_MASK;
gMain.newKeysRaw = keyInput & ~gMain.heldKeysRaw;
gMain.newKeys = gMain.newKeysRaw;
gMain.newAndRepeatedKeys = gMain.newKeysRaw;
// BUG: Key repeat won't work when pressing L using L=A button mode
// because it compares the raw key input with the remapped held keys.
// Note that newAndRepeatedKeys is never remapped either.
if (keyInput != 0 && gMain.heldKeys == keyInput)
{
gMain.keyRepeatCounter--;
if (gMain.keyRepeatCounter == 0)
{
gMain.newAndRepeatedKeys = keyInput;
gMain.keyRepeatCounter = gKeyRepeatContinueDelay;
}
}
else
{
// If there is no input or the input has changed, reset the counter.
gMain.keyRepeatCounter = gKeyRepeatStartDelay;
}
gMain.heldKeysRaw = keyInput;
gMain.heldKeys = gMain.heldKeysRaw;
// Remap L to A if the L=A option is enabled.
if (gSaveBlock2Ptr->optionsButtonMode == OPTIONS_BUTTON_MODE_L_EQUALS_A)
{
if (JOY_NEW(L_BUTTON))
gMain.newKeys |= A_BUTTON;
if (JOY_HELD(L_BUTTON))
gMain.heldKeys |= A_BUTTON;
}
if (JOY_NEW(gMain.watchedKeysMask))
gMain.watchedKeysPressed = TRUE;
}
void InitIntrHandlers(void)
{
int i;
for (i = 0; i < INTR_COUNT; i++)
gIntrTable[i] = gIntrTableTemplate[i];
INTR_VECTOR = IntrMain;
SetVBlankCallback(NULL);
SetHBlankCallback(NULL);
SetSerialCallback(NULL);
REG_IME = 1;
EnableInterrupts(INTR_FLAG_VBLANK);
}
void SetVBlankCallback(IntrCallback callback)
{
gMain.vblankCallback = callback;
}
void SetHBlankCallback(IntrCallback callback)
{
gMain.hblankCallback = callback;
}
void SetVCountCallback(IntrCallback callback)
{
gMain.vcountCallback = callback;
}
void RestoreSerialTimer3IntrHandlers(void)
{
gIntrTable[1] = SerialIntr;
gIntrTable[2] = Timer3Intr;
}
void SetSerialCallback(IntrCallback callback)
{
gMain.serialCallback = callback;
}
static void VBlankIntr(void)
{
if (gWirelessCommType != 0)
RfuVSync();
else if (gLinkVSyncDisabled == FALSE)
LinkVSync();
gMain.vblankCounter1++;
if (gTrainerHillVBlankCounter && *gTrainerHillVBlankCounter < 0xFFFFFFFF)
(*gTrainerHillVBlankCounter)++;
if (gMain.vblankCallback)
gMain.vblankCallback();
gMain.vblankCounter2++;
CopyBufferedValuesToGpuRegs();
ProcessDma3Requests();
gPcmDmaCounter = gSoundInfo.pcmDmaCounter;
m4aSoundMain();
TryReceiveLinkBattleData();
if (!gTestRunnerEnabled && (!gMain.inBattle || !(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_FRONTIER | BATTLE_TYPE_RECORDED))))
AdvanceRandom();
UpdateWirelessStatusIndicatorSprite();
INTR_CHECK |= INTR_FLAG_VBLANK;
gMain.intrCheck |= INTR_FLAG_VBLANK;
}
void InitFlashTimer(void)
{
SetFlashTimerIntr(2, gIntrTable + 0x7);
}
static void HBlankIntr(void)
{
if (gMain.hblankCallback)
gMain.hblankCallback();
INTR_CHECK |= INTR_FLAG_HBLANK;
gMain.intrCheck |= INTR_FLAG_HBLANK;
}
static void VCountIntr(void)
{
if (gMain.vcountCallback)
gMain.vcountCallback();
m4aSoundVSync();
INTR_CHECK |= INTR_FLAG_VCOUNT;
gMain.intrCheck |= INTR_FLAG_VCOUNT;
}
static void SerialIntr(void)
{
if (gMain.serialCallback)
gMain.serialCallback();
INTR_CHECK |= INTR_FLAG_SERIAL;
gMain.intrCheck |= INTR_FLAG_SERIAL;
}
static void IntrDummy(void)
{}
static void WaitForVBlank(void)
{
gMain.intrCheck &= ~INTR_FLAG_VBLANK;
if (gWirelessCommType != 0)
{
// Desynchronization may occur if wireless adapter is connected
// and we call VBlankIntrWait();
while (!(gMain.intrCheck & INTR_FLAG_VBLANK))
;
}
else
{
VBlankIntrWait();
}
}
void SetTrainerHillVBlankCounter(u32 *counter)
{
gTrainerHillVBlankCounter = counter;
}
void ClearTrainerHillVBlankCounter(void)
{
gTrainerHillVBlankCounter = NULL;
}
void DoSoftReset(void)
{
REG_IME = 0;
m4aSoundVSyncOff();
ScanlineEffect_Stop();
DmaStop(1);
DmaStop(2);
DmaStop(3);
SiiRtcProtect();
SoftReset(RESET_ALL);
}
void ClearPokemonCrySongs(void)
{
CpuFill16(0, gPokemonCrySongs, MAX_POKEMON_CRIES * sizeof(struct PokemonCrySong));
}