mirror of
https://github.com/Ninjdai1/pokeemerald.git
synced 2024-12-27 20:24:18 +01:00
471 lines
11 KiB
C
471 lines
11 KiB
C
#include "global.h"
|
|
#include "script.h"
|
|
#include "event_data.h"
|
|
#include "mystery_gift.h"
|
|
#include "util.h"
|
|
#include "constants/event_objects.h"
|
|
#include "constants/map_scripts.h"
|
|
|
|
#define RAM_SCRIPT_MAGIC 51
|
|
|
|
enum {
|
|
SCRIPT_MODE_STOPPED,
|
|
SCRIPT_MODE_BYTECODE,
|
|
SCRIPT_MODE_NATIVE,
|
|
};
|
|
|
|
enum {
|
|
CONTEXT_RUNNING,
|
|
CONTEXT_WAITING,
|
|
CONTEXT_SHUTDOWN,
|
|
};
|
|
|
|
extern const u8 *gRamScriptRetAddr;
|
|
|
|
static u8 sGlobalScriptContextStatus;
|
|
static struct ScriptContext sGlobalScriptContext;
|
|
static struct ScriptContext sImmediateScriptContext;
|
|
static bool8 sLockFieldControls;
|
|
|
|
extern ScrCmdFunc gScriptCmdTable[];
|
|
extern ScrCmdFunc gScriptCmdTableEnd[];
|
|
extern void *gNullScriptPtr;
|
|
|
|
void InitScriptContext(struct ScriptContext *ctx, void *cmdTable, void *cmdTableEnd)
|
|
{
|
|
s32 i;
|
|
|
|
ctx->mode = SCRIPT_MODE_STOPPED;
|
|
ctx->scriptPtr = NULL;
|
|
ctx->stackDepth = 0;
|
|
ctx->nativePtr = NULL;
|
|
ctx->cmdTable = cmdTable;
|
|
ctx->cmdTableEnd = cmdTableEnd;
|
|
|
|
for (i = 0; i < (int)ARRAY_COUNT(ctx->data); i++)
|
|
ctx->data[i] = 0;
|
|
|
|
for (i = 0; i < (int)ARRAY_COUNT(ctx->stack); i++)
|
|
ctx->stack[i] = NULL;
|
|
}
|
|
|
|
u8 SetupBytecodeScript(struct ScriptContext *ctx, const u8 *ptr)
|
|
{
|
|
ctx->scriptPtr = ptr;
|
|
ctx->mode = SCRIPT_MODE_BYTECODE;
|
|
return 1;
|
|
}
|
|
|
|
void SetupNativeScript(struct ScriptContext *ctx, bool8 (*ptr)(void))
|
|
{
|
|
ctx->mode = SCRIPT_MODE_NATIVE;
|
|
ctx->nativePtr = ptr;
|
|
}
|
|
|
|
void StopScript(struct ScriptContext *ctx)
|
|
{
|
|
ctx->mode = SCRIPT_MODE_STOPPED;
|
|
ctx->scriptPtr = NULL;
|
|
}
|
|
|
|
bool8 RunScriptCommand(struct ScriptContext *ctx)
|
|
{
|
|
if (ctx->mode == SCRIPT_MODE_STOPPED)
|
|
return FALSE;
|
|
|
|
switch (ctx->mode)
|
|
{
|
|
case SCRIPT_MODE_STOPPED:
|
|
return FALSE;
|
|
case SCRIPT_MODE_NATIVE:
|
|
// Try to call a function in C
|
|
// Continue to bytecode if no function or it returns TRUE
|
|
if (ctx->nativePtr)
|
|
{
|
|
if (ctx->nativePtr() == TRUE)
|
|
ctx->mode = SCRIPT_MODE_BYTECODE;
|
|
return TRUE;
|
|
}
|
|
ctx->mode = SCRIPT_MODE_BYTECODE;
|
|
// fallthrough
|
|
case SCRIPT_MODE_BYTECODE:
|
|
while (1)
|
|
{
|
|
u8 cmdCode;
|
|
ScrCmdFunc *func;
|
|
|
|
if (!ctx->scriptPtr)
|
|
{
|
|
ctx->mode = SCRIPT_MODE_STOPPED;
|
|
return FALSE;
|
|
}
|
|
|
|
if (ctx->scriptPtr == gNullScriptPtr)
|
|
{
|
|
while (1)
|
|
asm("svc 2"); // HALT
|
|
}
|
|
|
|
cmdCode = *(ctx->scriptPtr);
|
|
ctx->scriptPtr++;
|
|
func = &ctx->cmdTable[cmdCode];
|
|
|
|
if (func >= ctx->cmdTableEnd)
|
|
{
|
|
ctx->mode = SCRIPT_MODE_STOPPED;
|
|
return FALSE;
|
|
}
|
|
|
|
if ((*func)(ctx) == TRUE)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool8 ScriptPush(struct ScriptContext *ctx, const u8 *ptr)
|
|
{
|
|
if (ctx->stackDepth + 1 >= (int)ARRAY_COUNT(ctx->stack))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
ctx->stack[ctx->stackDepth] = ptr;
|
|
ctx->stackDepth++;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static const u8 *ScriptPop(struct ScriptContext *ctx)
|
|
{
|
|
if (ctx->stackDepth == 0)
|
|
return NULL;
|
|
|
|
ctx->stackDepth--;
|
|
return ctx->stack[ctx->stackDepth];
|
|
}
|
|
|
|
void ScriptJump(struct ScriptContext *ctx, const u8 *ptr)
|
|
{
|
|
ctx->scriptPtr = ptr;
|
|
}
|
|
|
|
void ScriptCall(struct ScriptContext *ctx, const u8 *ptr)
|
|
{
|
|
ScriptPush(ctx, ctx->scriptPtr);
|
|
ctx->scriptPtr = ptr;
|
|
}
|
|
|
|
void ScriptReturn(struct ScriptContext *ctx)
|
|
{
|
|
ctx->scriptPtr = ScriptPop(ctx);
|
|
}
|
|
|
|
u16 ScriptReadHalfword(struct ScriptContext *ctx)
|
|
{
|
|
u16 value = *(ctx->scriptPtr++);
|
|
value |= *(ctx->scriptPtr++) << 8;
|
|
return value;
|
|
}
|
|
|
|
u32 ScriptReadWord(struct ScriptContext *ctx)
|
|
{
|
|
u32 value0 = *(ctx->scriptPtr++);
|
|
u32 value1 = *(ctx->scriptPtr++);
|
|
u32 value2 = *(ctx->scriptPtr++);
|
|
u32 value3 = *(ctx->scriptPtr++);
|
|
return (((((value3 << 8) + value2) << 8) + value1) << 8) + value0;
|
|
}
|
|
|
|
void LockPlayerFieldControls(void)
|
|
{
|
|
sLockFieldControls = TRUE;
|
|
}
|
|
|
|
void UnlockPlayerFieldControls(void)
|
|
{
|
|
sLockFieldControls = FALSE;
|
|
}
|
|
|
|
bool8 ArePlayerFieldControlsLocked(void)
|
|
{
|
|
return sLockFieldControls;
|
|
}
|
|
|
|
// The ScriptContext_* functions work with the primary script context,
|
|
// which yields control back to native code should the script make a wait call.
|
|
|
|
// Checks if the global script context is able to be run right now.
|
|
bool8 ScriptContext_IsEnabled(void)
|
|
{
|
|
if (sGlobalScriptContextStatus == CONTEXT_RUNNING)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
// Re-initializes the global script context to zero.
|
|
void ScriptContext_Init(void)
|
|
{
|
|
InitScriptContext(&sGlobalScriptContext, gScriptCmdTable, gScriptCmdTableEnd);
|
|
sGlobalScriptContextStatus = CONTEXT_SHUTDOWN;
|
|
}
|
|
|
|
// Runs the script until the script makes a wait* call, then returns true if
|
|
// there's more script to run, or false if the script has hit the end.
|
|
// This function also returns false if the context is finished
|
|
// or waiting (after a call to _Stop)
|
|
bool8 ScriptContext_RunScript(void)
|
|
{
|
|
if (sGlobalScriptContextStatus == CONTEXT_SHUTDOWN)
|
|
return FALSE;
|
|
|
|
if (sGlobalScriptContextStatus == CONTEXT_WAITING)
|
|
return FALSE;
|
|
|
|
LockPlayerFieldControls();
|
|
|
|
if (!RunScriptCommand(&sGlobalScriptContext))
|
|
{
|
|
sGlobalScriptContextStatus = CONTEXT_SHUTDOWN;
|
|
UnlockPlayerFieldControls();
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Sets up a new script in the global context and enables the context
|
|
void ScriptContext_SetupScript(const u8 *ptr)
|
|
{
|
|
InitScriptContext(&sGlobalScriptContext, gScriptCmdTable, gScriptCmdTableEnd);
|
|
SetupBytecodeScript(&sGlobalScriptContext, ptr);
|
|
LockPlayerFieldControls();
|
|
sGlobalScriptContextStatus = CONTEXT_RUNNING;
|
|
}
|
|
|
|
// Puts the script into waiting mode; usually called from a wait* script command.
|
|
void ScriptContext_Stop(void)
|
|
{
|
|
sGlobalScriptContextStatus = CONTEXT_WAITING;
|
|
}
|
|
|
|
// Puts the script into running mode.
|
|
void ScriptContext_Enable(void)
|
|
{
|
|
sGlobalScriptContextStatus = CONTEXT_RUNNING;
|
|
LockPlayerFieldControls();
|
|
}
|
|
|
|
// Sets up and runs a script in its own context immediately. The script will be
|
|
// finished when this function returns. Used mainly by all of the map header
|
|
// scripts (except the frame table scripts).
|
|
void RunScriptImmediately(const u8 *ptr)
|
|
{
|
|
InitScriptContext(&sImmediateScriptContext, gScriptCmdTable, gScriptCmdTableEnd);
|
|
SetupBytecodeScript(&sImmediateScriptContext, ptr);
|
|
while (RunScriptCommand(&sImmediateScriptContext) == TRUE);
|
|
}
|
|
|
|
u8 *MapHeaderGetScriptTable(u8 tag)
|
|
{
|
|
const u8 *mapScripts = gMapHeader.mapScripts;
|
|
|
|
if (!mapScripts)
|
|
return NULL;
|
|
|
|
while (1)
|
|
{
|
|
if (!*mapScripts)
|
|
return NULL;
|
|
if (*mapScripts == tag)
|
|
{
|
|
mapScripts++;
|
|
return T2_READ_PTR(mapScripts);
|
|
}
|
|
mapScripts += 5;
|
|
}
|
|
}
|
|
|
|
void MapHeaderRunScriptType(u8 tag)
|
|
{
|
|
u8 *ptr = MapHeaderGetScriptTable(tag);
|
|
if (ptr)
|
|
RunScriptImmediately(ptr);
|
|
}
|
|
|
|
u8 *MapHeaderCheckScriptTable(u8 tag)
|
|
{
|
|
u8 *ptr = MapHeaderGetScriptTable(tag);
|
|
|
|
if (!ptr)
|
|
return NULL;
|
|
|
|
while (1)
|
|
{
|
|
u16 varIndex1;
|
|
u16 varIndex2;
|
|
|
|
// Read first var (or .2byte terminal value)
|
|
varIndex1 = T1_READ_16(ptr);
|
|
if (!varIndex1)
|
|
return NULL; // Reached end of table
|
|
ptr += 2;
|
|
|
|
// Read second var
|
|
varIndex2 = T1_READ_16(ptr);
|
|
ptr += 2;
|
|
|
|
// Run map script if vars are equal
|
|
if (VarGet(varIndex1) == VarGet(varIndex2))
|
|
return T2_READ_PTR(ptr);
|
|
ptr += 4;
|
|
}
|
|
}
|
|
|
|
void RunOnLoadMapScript(void)
|
|
{
|
|
MapHeaderRunScriptType(MAP_SCRIPT_ON_LOAD);
|
|
}
|
|
|
|
void RunOnTransitionMapScript(void)
|
|
{
|
|
MapHeaderRunScriptType(MAP_SCRIPT_ON_TRANSITION);
|
|
}
|
|
|
|
void RunOnResumeMapScript(void)
|
|
{
|
|
MapHeaderRunScriptType(MAP_SCRIPT_ON_RESUME);
|
|
}
|
|
|
|
void RunOnReturnToFieldMapScript(void)
|
|
{
|
|
MapHeaderRunScriptType(MAP_SCRIPT_ON_RETURN_TO_FIELD);
|
|
}
|
|
|
|
void RunOnDiveWarpMapScript(void)
|
|
{
|
|
MapHeaderRunScriptType(MAP_SCRIPT_ON_DIVE_WARP);
|
|
}
|
|
|
|
bool8 TryRunOnFrameMapScript(void)
|
|
{
|
|
u8 *ptr = MapHeaderCheckScriptTable(MAP_SCRIPT_ON_FRAME_TABLE);
|
|
|
|
if (!ptr)
|
|
return FALSE;
|
|
|
|
ScriptContext_SetupScript(ptr);
|
|
return TRUE;
|
|
}
|
|
|
|
void TryRunOnWarpIntoMapScript(void)
|
|
{
|
|
u8 *ptr = MapHeaderCheckScriptTable(MAP_SCRIPT_ON_WARP_INTO_MAP_TABLE);
|
|
if (ptr)
|
|
RunScriptImmediately(ptr);
|
|
}
|
|
|
|
u32 CalculateRamScriptChecksum(void)
|
|
{
|
|
return CalcCRC16WithTable((u8 *)(&gSaveBlock1Ptr->ramScript.data), sizeof(gSaveBlock1Ptr->ramScript.data));
|
|
}
|
|
|
|
void ClearRamScript(void)
|
|
{
|
|
CpuFill32(0, &gSaveBlock1Ptr->ramScript, sizeof(struct RamScript));
|
|
}
|
|
|
|
bool8 InitRamScript(const u8 *script, u16 scriptSize, u8 mapGroup, u8 mapNum, u8 objectId)
|
|
{
|
|
struct RamScriptData *scriptData = &gSaveBlock1Ptr->ramScript.data;
|
|
|
|
ClearRamScript();
|
|
|
|
if (scriptSize > sizeof(scriptData->script))
|
|
return FALSE;
|
|
|
|
scriptData->magic = RAM_SCRIPT_MAGIC;
|
|
scriptData->mapGroup = mapGroup;
|
|
scriptData->mapNum = mapNum;
|
|
scriptData->objectId = objectId;
|
|
memcpy(scriptData->script, script, scriptSize);
|
|
gSaveBlock1Ptr->ramScript.checksum = CalculateRamScriptChecksum();
|
|
return TRUE;
|
|
}
|
|
|
|
const u8 *GetRamScript(u8 objectId, const u8 *script)
|
|
{
|
|
struct RamScriptData *scriptData = &gSaveBlock1Ptr->ramScript.data;
|
|
gRamScriptRetAddr = NULL;
|
|
if (scriptData->magic != RAM_SCRIPT_MAGIC)
|
|
return script;
|
|
if (scriptData->mapGroup != gSaveBlock1Ptr->location.mapGroup)
|
|
return script;
|
|
if (scriptData->mapNum != gSaveBlock1Ptr->location.mapNum)
|
|
return script;
|
|
if (scriptData->objectId != objectId)
|
|
return script;
|
|
if (CalculateRamScriptChecksum() != gSaveBlock1Ptr->ramScript.checksum)
|
|
{
|
|
ClearRamScript();
|
|
return script;
|
|
}
|
|
else
|
|
{
|
|
gRamScriptRetAddr = script;
|
|
return scriptData->script;
|
|
}
|
|
}
|
|
|
|
#define NO_OBJECT OBJ_EVENT_ID_PLAYER
|
|
|
|
bool32 ValidateSavedRamScript(void)
|
|
{
|
|
struct RamScriptData *scriptData = &gSaveBlock1Ptr->ramScript.data;
|
|
if (scriptData->magic != RAM_SCRIPT_MAGIC)
|
|
return FALSE;
|
|
if (scriptData->mapGroup != MAP_GROUP(UNDEFINED))
|
|
return FALSE;
|
|
if (scriptData->mapNum != MAP_NUM(UNDEFINED))
|
|
return FALSE;
|
|
if (scriptData->objectId != NO_OBJECT)
|
|
return FALSE;
|
|
if (CalculateRamScriptChecksum() != gSaveBlock1Ptr->ramScript.checksum)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
u8 *GetSavedRamScriptIfValid(void)
|
|
{
|
|
struct RamScriptData *scriptData = &gSaveBlock1Ptr->ramScript.data;
|
|
if (!ValidateSavedWonderCard())
|
|
return NULL;
|
|
if (scriptData->magic != RAM_SCRIPT_MAGIC)
|
|
return NULL;
|
|
if (scriptData->mapGroup != MAP_GROUP(UNDEFINED))
|
|
return NULL;
|
|
if (scriptData->mapNum != MAP_NUM(UNDEFINED))
|
|
return NULL;
|
|
if (scriptData->objectId != NO_OBJECT)
|
|
return NULL;
|
|
if (CalculateRamScriptChecksum() != gSaveBlock1Ptr->ramScript.checksum)
|
|
{
|
|
ClearRamScript();
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return scriptData->script;
|
|
}
|
|
}
|
|
|
|
void InitRamScript_NoObjectEvent(u8 *script, u16 scriptSize)
|
|
{
|
|
if (scriptSize > sizeof(gSaveBlock1Ptr->ramScript.data.script))
|
|
scriptSize = sizeof(gSaveBlock1Ptr->ramScript.data.script);
|
|
InitRamScript(script, scriptSize, MAP_GROUP(UNDEFINED), MAP_NUM(UNDEFINED), NO_OBJECT);
|
|
}
|