465 lines
16 KiB
C
465 lines
16 KiB
C
|
#include "global.h"
|
||
|
#include "event_data.h"
|
||
|
#include "event_object_movement.h"
|
||
|
#include "field_weather.h"
|
||
|
#include "fieldmap.h"
|
||
|
#include "metatile_behavior.h"
|
||
|
#include "sprite.h"
|
||
|
#include "constants/event_objects.h"
|
||
|
#include "constants/field_effects.h"
|
||
|
#include "constants/metatile_behaviors.h"
|
||
|
|
||
|
static u8 GetValidMewMoveDirection(u8);
|
||
|
static bool8 ShouldMewMoveNorth(struct ObjectEvent*, u8);
|
||
|
static bool8 ShouldMewMoveSouth(struct ObjectEvent*, u8);
|
||
|
static bool8 ShouldMewMoveEast(struct ObjectEvent*, u8);
|
||
|
static bool8 ShouldMewMoveWest(struct ObjectEvent*, u8);
|
||
|
static u8 GetRandomMewDirectionCandidate(u8);
|
||
|
static bool8 CanMewMoveToCoords(s16, s16);
|
||
|
|
||
|
static EWRAM_DATA u8 sGrassSpriteId = 0;
|
||
|
|
||
|
static s16 sPlayerToMewDeltaX;
|
||
|
static s16 sPlayerToMewDeltaY;
|
||
|
static u8 sMewDirectionCandidates[4];
|
||
|
|
||
|
extern const struct SpritePalette gSpritePalette_GeneralFieldEffect1;
|
||
|
extern const struct SpriteTemplate *const gFieldEffectObjectTemplatePointers[];
|
||
|
|
||
|
static const s16 sFarawayIslandRockCoords[4][2] =
|
||
|
{
|
||
|
{14 + MAP_OFFSET, 9 + MAP_OFFSET},
|
||
|
{18 + MAP_OFFSET, 9 + MAP_OFFSET},
|
||
|
{ 9 + MAP_OFFSET, 10 + MAP_OFFSET},
|
||
|
{13 + MAP_OFFSET, 13 + MAP_OFFSET},
|
||
|
};
|
||
|
|
||
|
static u8 GetMewObjectEventId(void)
|
||
|
{
|
||
|
u8 objectEventId;
|
||
|
TryGetObjectEventIdByLocalIdAndMap(LOCALID_FARAWAY_ISLAND_MEW, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objectEventId);
|
||
|
return objectEventId;
|
||
|
}
|
||
|
|
||
|
// When the player enters Faraway Island interior it begins a "hide and seek" minigame where Mew disappears into the grass
|
||
|
// This function returns the direction Mew will take a step, and is run every time the player takes a step
|
||
|
u32 GetMewMoveDirection(void)
|
||
|
{
|
||
|
u8 i;
|
||
|
int mewSafeFromTrap;
|
||
|
struct ObjectEvent *mew = &gObjectEvents[GetMewObjectEventId()];
|
||
|
|
||
|
sPlayerToMewDeltaX = gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x - mew->currentCoords.x;
|
||
|
sPlayerToMewDeltaY = gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y - mew->currentCoords.y;
|
||
|
for (i = 0; i < ARRAY_COUNT(sMewDirectionCandidates); i++)
|
||
|
sMewDirectionCandidates[i] = DIR_NONE;
|
||
|
|
||
|
// Player hasn't moved (just facing new direction), don't move
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x == gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x
|
||
|
&& gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y == gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y)
|
||
|
{
|
||
|
return DIR_NONE;
|
||
|
}
|
||
|
|
||
|
// Mew is invisible except for every 8th step
|
||
|
if (VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER) % 8 == 0)
|
||
|
mew->invisible = FALSE;
|
||
|
else
|
||
|
mew->invisible = TRUE;
|
||
|
|
||
|
// Mew will stay in place for 1 step after its visible
|
||
|
if (VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER) % 9 == 0)
|
||
|
return DIR_NONE;
|
||
|
|
||
|
// Below loop is for Mew to try to avoid getting trapped between the player and a rock
|
||
|
for (i = 0; i < ARRAY_COUNT(sFarawayIslandRockCoords); i++)
|
||
|
{
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x == sFarawayIslandRockCoords[i][0])
|
||
|
{
|
||
|
mewSafeFromTrap = FALSE;
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y < sFarawayIslandRockCoords[i][1])
|
||
|
{
|
||
|
if (mew->currentCoords.y <= sFarawayIslandRockCoords[i][1])
|
||
|
mewSafeFromTrap = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (mew->currentCoords.y >= sFarawayIslandRockCoords[i][1])
|
||
|
mewSafeFromTrap = TRUE;
|
||
|
}
|
||
|
|
||
|
if (!mewSafeFromTrap)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaX > 0)
|
||
|
{
|
||
|
if (mew->currentCoords.x + 1 == gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x + 1, mew->currentCoords.y))
|
||
|
return DIR_EAST;
|
||
|
}
|
||
|
}
|
||
|
else if (sPlayerToMewDeltaX < 0)
|
||
|
{
|
||
|
if (mew->currentCoords.x - 1 == gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x - 1, mew->currentCoords.y))
|
||
|
return DIR_WEST;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mew->currentCoords.x == gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaY > 0)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y - 1))
|
||
|
return DIR_NORTH;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y + 1))
|
||
|
return DIR_SOUTH;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y == sFarawayIslandRockCoords[i][1])
|
||
|
{
|
||
|
mewSafeFromTrap = FALSE;
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.x < sFarawayIslandRockCoords[i][0])
|
||
|
{
|
||
|
if (mew->currentCoords.x <= sFarawayIslandRockCoords[i][0])
|
||
|
mewSafeFromTrap = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (mew->currentCoords.x >= sFarawayIslandRockCoords[i][0])
|
||
|
mewSafeFromTrap = TRUE;
|
||
|
}
|
||
|
|
||
|
if (!mewSafeFromTrap)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaY > 0)
|
||
|
{
|
||
|
if (mew->currentCoords.y + 1 == gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y + 1))
|
||
|
return DIR_SOUTH;
|
||
|
}
|
||
|
}
|
||
|
else if (sPlayerToMewDeltaY < 0)
|
||
|
{
|
||
|
if (mew->currentCoords.y - 1 == gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y - 1))
|
||
|
return DIR_NORTH;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mew->currentCoords.y == gObjectEvents[gPlayerAvatar.objectEventId].previousCoords.y)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaX > 0)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x - 1, mew->currentCoords.y))
|
||
|
return DIR_WEST;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x + 1, mew->currentCoords.y))
|
||
|
return DIR_EAST;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if Mew can move in any direction without getting closer to the player
|
||
|
// If so load into sMewDirectionCandidates
|
||
|
// If Mew can move in two of the checked directions, choose one randomly
|
||
|
if (ShouldMewMoveNorth(mew, 0))
|
||
|
{
|
||
|
if (ShouldMewMoveEast(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else if (ShouldMewMoveWest(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else
|
||
|
return DIR_NORTH;
|
||
|
}
|
||
|
|
||
|
if (ShouldMewMoveSouth(mew, 0))
|
||
|
{
|
||
|
if (ShouldMewMoveEast(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else if (ShouldMewMoveWest(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else
|
||
|
return DIR_SOUTH;
|
||
|
}
|
||
|
|
||
|
if (ShouldMewMoveEast(mew, 0))
|
||
|
{
|
||
|
if (ShouldMewMoveNorth(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else if (ShouldMewMoveSouth(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else
|
||
|
return DIR_EAST;
|
||
|
}
|
||
|
|
||
|
if (ShouldMewMoveWest(mew, 0))
|
||
|
{
|
||
|
if (ShouldMewMoveNorth(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else if (ShouldMewMoveSouth(mew, 1))
|
||
|
return GetRandomMewDirectionCandidate(2);
|
||
|
else
|
||
|
return DIR_WEST;
|
||
|
}
|
||
|
|
||
|
// If this point is reached, Mew cannot move without getting closer to the player
|
||
|
|
||
|
// Avoid player on same Y, try move North/South
|
||
|
if (sPlayerToMewDeltaY == 0)
|
||
|
{
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y > mew->currentCoords.y)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y - 1))
|
||
|
return DIR_NORTH;
|
||
|
}
|
||
|
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y < mew->currentCoords.y)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y + 1))
|
||
|
return DIR_SOUTH;
|
||
|
}
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y - 1))
|
||
|
return DIR_NORTH;
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y + 1))
|
||
|
return DIR_SOUTH;
|
||
|
}
|
||
|
|
||
|
// Avoid player on same X, try move West/East
|
||
|
if (sPlayerToMewDeltaX == 0)
|
||
|
{
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x > mew->currentCoords.x)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x - 1, mew->currentCoords.y))
|
||
|
return DIR_WEST;
|
||
|
}
|
||
|
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x < mew->currentCoords.x)
|
||
|
{
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x + 1, mew->currentCoords.y))
|
||
|
return DIR_EAST;
|
||
|
}
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x + 1, mew->currentCoords.y))
|
||
|
return DIR_EAST;
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x - 1, mew->currentCoords.y))
|
||
|
return DIR_WEST;
|
||
|
}
|
||
|
|
||
|
// Can't avoid player on axis, move any valid direction
|
||
|
return GetValidMewMoveDirection(DIR_NONE);
|
||
|
}
|
||
|
|
||
|
// Mew can move to any Tall/Long Grass metatile the player isn't currently on
|
||
|
static bool8 CanMewMoveToCoords(s16 x, s16 y)
|
||
|
{
|
||
|
if (gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.x == x
|
||
|
&& gObjectEvents[gPlayerAvatar.objectEventId].currentCoords.y == y)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return MetatileBehavior_IsPokeGrass(MapGridGetMetatileBehaviorAt(x, y));
|
||
|
}
|
||
|
|
||
|
// Last ditch effort to move, clear move candidates and try all directions again
|
||
|
static u8 GetValidMewMoveDirection(u8 ignoredDir)
|
||
|
{
|
||
|
u8 i;
|
||
|
u8 count = 0;
|
||
|
struct ObjectEvent *mew = &gObjectEvents[GetMewObjectEventId()];
|
||
|
|
||
|
for (i = 0; i < ARRAY_COUNT(sMewDirectionCandidates); i++)
|
||
|
sMewDirectionCandidates[i] = DIR_NONE;
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y - 1) == TRUE && ignoredDir != DIR_NORTH)
|
||
|
{
|
||
|
sMewDirectionCandidates[count] = DIR_NORTH;
|
||
|
count++;
|
||
|
}
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x + 1, mew->currentCoords.y) == TRUE && ignoredDir != DIR_EAST)
|
||
|
{
|
||
|
sMewDirectionCandidates[count] = DIR_EAST;
|
||
|
count++;
|
||
|
}
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y + 1) == TRUE && ignoredDir != DIR_SOUTH)
|
||
|
{
|
||
|
sMewDirectionCandidates[count] = DIR_SOUTH;
|
||
|
count++;
|
||
|
}
|
||
|
|
||
|
if (CanMewMoveToCoords(mew->currentCoords.x - 1, mew->currentCoords.y) == TRUE && ignoredDir != DIR_WEST)
|
||
|
{
|
||
|
sMewDirectionCandidates[count] = DIR_WEST;
|
||
|
count++;
|
||
|
}
|
||
|
|
||
|
if (count > 1)
|
||
|
return sMewDirectionCandidates[VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER) % count];
|
||
|
else
|
||
|
return sMewDirectionCandidates[0];
|
||
|
}
|
||
|
|
||
|
void UpdateFarawayIslandStepCounter(void)
|
||
|
{
|
||
|
u16 steps = VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER);
|
||
|
if (gSaveBlock1Ptr->location.mapNum == MAP_NUM(FARAWAY_ISLAND_INTERIOR)
|
||
|
&& gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(FARAWAY_ISLAND_INTERIOR))
|
||
|
{
|
||
|
steps++;
|
||
|
if (steps >= 9999)
|
||
|
VarSet(VAR_FARAWAY_ISLAND_STEP_COUNTER, 0);
|
||
|
else
|
||
|
VarSet(VAR_FARAWAY_ISLAND_STEP_COUNTER, steps);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool8 ObjectEventIsFarawayIslandMew(struct ObjectEvent *objectEvent)
|
||
|
{
|
||
|
if (gSaveBlock1Ptr->location.mapNum == MAP_NUM(FARAWAY_ISLAND_INTERIOR)
|
||
|
&& gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(FARAWAY_ISLAND_INTERIOR))
|
||
|
{
|
||
|
if (objectEvent->graphicsId == OBJ_EVENT_GFX_MEW)
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
bool8 IsMewPlayingHideAndSeek(void)
|
||
|
{
|
||
|
if (gSaveBlock1Ptr->location.mapNum == MAP_NUM(FARAWAY_ISLAND_INTERIOR)
|
||
|
&& gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(FARAWAY_ISLAND_INTERIOR))
|
||
|
{
|
||
|
if (FlagGet(FLAG_CAUGHT_MEW) != TRUE && FlagGet(FLAG_HIDE_MEW) != TRUE)
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Every 4th step Mew will shake the grass it steps into
|
||
|
// Otherwise its movement leaves grass undisturbed
|
||
|
bool8 ShouldMewShakeGrass(struct ObjectEvent *objectEvent)
|
||
|
{
|
||
|
if (VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER) != 0xFFFF
|
||
|
&& VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER) % 4 == 0)
|
||
|
return TRUE;
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void SetMewAboveGrass(void)
|
||
|
{
|
||
|
s16 x;
|
||
|
s16 y;
|
||
|
struct ObjectEvent *mew = &gObjectEvents[GetMewObjectEventId()];
|
||
|
|
||
|
mew->invisible = FALSE;
|
||
|
if (gSpecialVar_0x8004 == 1)
|
||
|
{
|
||
|
// For after battle where Mew should still be present (e.g. if ran from battle)
|
||
|
mew->fixedPriority = 1;
|
||
|
gSprites[mew->spriteId].subspriteMode = SUBSPRITES_IGNORE_PRIORITY;
|
||
|
gSprites[mew->spriteId].subpriority = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Mew emerging from grass when found
|
||
|
// Also do field effect for grass shaking as it emerges
|
||
|
VarSet(VAR_FARAWAY_ISLAND_STEP_COUNTER, 0xFFFF);
|
||
|
mew->fixedPriority = 1;
|
||
|
gSprites[mew->spriteId].subspriteMode = SUBSPRITES_IGNORE_PRIORITY;
|
||
|
if (gSpecialVar_Facing != DIR_NORTH)
|
||
|
gSprites[mew->spriteId].subpriority = 1;
|
||
|
|
||
|
LoadSpritePalette(&gSpritePalette_GeneralFieldEffect1);
|
||
|
UpdateSpritePaletteWithWeather(IndexOfSpritePaletteTag(gSpritePalette_GeneralFieldEffect1.tag));
|
||
|
|
||
|
x = mew->currentCoords.x;
|
||
|
y = mew->currentCoords.y;
|
||
|
SetSpritePosToOffsetMapCoords(&x, &y, 8, 8);
|
||
|
sGrassSpriteId = CreateSpriteAtEnd(gFieldEffectObjectTemplatePointers[FLDEFFOBJ_LONG_GRASS], x, y, gSprites[mew->spriteId].subpriority - 1);
|
||
|
if (sGrassSpriteId != MAX_SPRITES)
|
||
|
{
|
||
|
struct Sprite *sprite = &gSprites[sGrassSpriteId];
|
||
|
sprite->coordOffsetEnabled = 1;
|
||
|
sprite->oam.priority = 2;
|
||
|
sprite->callback = SpriteCallbackDummy;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DestroyMewEmergingGrassSprite(void)
|
||
|
{
|
||
|
if (sGrassSpriteId != MAX_SPRITES)
|
||
|
DestroySprite(&gSprites[sGrassSpriteId]);
|
||
|
}
|
||
|
|
||
|
static bool8 ShouldMewMoveNorth(struct ObjectEvent *mew, u8 index)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaY > 0 && CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y - 1))
|
||
|
{
|
||
|
sMewDirectionCandidates[index] = DIR_NORTH;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static bool8 ShouldMewMoveEast(struct ObjectEvent *mew, u8 index)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaX < 0 && CanMewMoveToCoords(mew->currentCoords.x + 1, mew->currentCoords.y))
|
||
|
{
|
||
|
sMewDirectionCandidates[index] = DIR_EAST;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static bool8 ShouldMewMoveSouth(struct ObjectEvent *mew, u8 index)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaY < 0 && CanMewMoveToCoords(mew->currentCoords.x, mew->currentCoords.y + 1))
|
||
|
{
|
||
|
sMewDirectionCandidates[index] = DIR_SOUTH;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static bool8 ShouldMewMoveWest(struct ObjectEvent *mew, u8 index)
|
||
|
{
|
||
|
if (sPlayerToMewDeltaX > 0 && CanMewMoveToCoords(mew->currentCoords.x - 1, mew->currentCoords.y))
|
||
|
{
|
||
|
sMewDirectionCandidates[index] = DIR_WEST;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static u8 GetRandomMewDirectionCandidate(u8 numDirections)
|
||
|
{
|
||
|
return sMewDirectionCandidates[VarGet(VAR_FARAWAY_ISLAND_STEP_COUNTER) % numDirections];
|
||
|
}
|