Merge branch '_RHH/master' into _RHH/upcoming

This commit is contained in:
Eduardo Quezada 2025-02-13 13:06:32 -03:00
commit 9c4fbbf274
16 changed files with 289 additions and 72 deletions

View File

@ -2422,6 +2422,12 @@
.2byte \dest
.endm
@ Hides any follower Pokémon if present, putting them into their Poké Ball; by default waits for their movement to finish.
.macro hidefollower wait=1
callnative ScrFunc_hidefollower
.2byte \wait
.endm
.macro increasedifficulty
callnative Script_IncreaseDifficulty, requests_effects=1
.endm

View File

@ -118,7 +118,6 @@ EventScript_FollowerSwap:
return
EventScript_FollowerMoveNorth:
applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkUp
applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkDown
waitmovement 0
applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceUp
@ -126,7 +125,6 @@ EventScript_FollowerMoveNorth:
return
EventScript_FollowerMoveEast:
applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkRight
applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkLeft
waitmovement 0
applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceRight
@ -134,7 +132,6 @@ EventScript_FollowerMoveEast:
return
EventScript_FollowerMoveSouth:
applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkDown
applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkUp
waitmovement 0
applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceDown
@ -142,7 +139,6 @@ EventScript_FollowerMoveSouth:
return
EventScript_FollowerMoveWest:
applymovement OBJ_EVENT_ID_FOLLOWER, Movement_WalkLeft
applymovement OBJ_EVENT_ID_PLAYER, Movement_WalkRight
waitmovement 0
applymovement OBJ_EVENT_ID_PLAYER, Common_Movement_FaceLeft

View File

@ -33,7 +33,8 @@ EventScript_PkmnCenterNurse_IllTakeYourPkmn2::
return
EventScript_PkmnCenterNurse_TakeAndHealPkmn::
applymovement VAR_0x800B, Movement_PkmnCenterNurse_Turn @ Changed from Common_Movement_WalkInPlaceFasterLeft to force the follower to enter their Poké Ball
hidefollower 0
applymovement VAR_0x800B, Movement_PkmnCenterNurse_Turn
waitmovement 0
dofieldeffect FLDEFF_POKECENTER_HEAL
.if OW_UNION_DISABLE_CHECK == FALSE && OW_FLAG_MOVE_UNION_ROOM_CHECK != 0

View File

@ -8,11 +8,13 @@ Std_MsgboxNPC:
return
Std_MsgboxSign:
setflag FLAG_SAFE_FOLLOWER_MOVEMENT
lockall
message NULL
waitmessage
waitbuttonpress
releaseall
clearflag FLAG_SAFE_FOLLOWER_MOVEMENT
return
Std_MsgboxDefault:

View File

@ -62,6 +62,31 @@
#define OW_BATTLE_ONLY_FORMS TRUE // If TRUE, loads overworld sprites for battle-only forms like Mega Evos. Requires OW_POKEMON_OBJECT_EVENTS.
#define B_FLAG_FOLLOWERS_DISABLED 0 // Enables / Disables followers by using a flag. Helpful to disable followers for a period of time.
#define OW_FOLLOWERS_SCRIPT_MOVEMENT TRUE // TRUE: Script collisions hide follower, FLAG_SAFE_FOLLOWER_MOVEMENT on by default
// FALSE: Script collisions unhandled, FLAG_SAFE_FOLLOWER_MOVEMENT off by default
// If set, the only pokemon allowed to follow you
// will be those matching species, met location,
// and/or met level;
// These accept vars, too: VAR_TEMP_1, etc
#define OW_MON_ALLOWED_SPECIES (0)
#define OW_MON_ALLOWED_MET_LVL (0)
#define OW_MON_ALLOWED_MET_LOC (0)
// Examples:
// Yellow Pikachu:
// #define OW_MON_ALLOWED_SPECIES (SPECIES_PIKACHU)
// #define OW_MON_ALLOWED_MET_LVL (0)
// #define OW_MON_ALLOWED_MET_LOC (MAPSEC_PALLET_TOWN)
// Hoenn Starter:
// #define OW_MON_ALLOWED_SPECIES (0)
// #define OW_MON_ALLOWED_MET_LVL (5)
// #define OW_MON_ALLOWED_MET_LOC (MAPSEC_ROUTE_101)
// Species set in VAR_XXXX:
// #define OW_MON_ALLOWED_SPECIES (VAR_XXXX)
// #define OW_MON_ALLOWED_MET_LVL (0)
// #define OW_MON_ALLOWED_MET_LOC (0)
// Out-of-battle Ability effects
#define OW_SYNCHRONIZE_NATURE GEN_LATEST // In Gen8+, if a Pokémon with Synchronize leads the party, wild Pokémon will always have their same Nature as opposed to the 50% chance in previous games. Gift Pokémon excluded.
// In USUM (here GEN_7), if a Pokémon with Synchronize leads the party, gift Pokémon will always have their same Nature regardless of their Egg Group.

View File

@ -270,10 +270,10 @@
#define OBJ_EVENT_GFX_VAR_E (OBJ_EVENT_GFX_VARS + 0xE)
#define OBJ_EVENT_GFX_VAR_F (OBJ_EVENT_GFX_VARS + 0xF)
#define OBJ_EVENT_MON (1 << 13)
#define OBJ_EVENT_MON_SHINY (1 << 14)
#define OBJ_EVENT_MON_FEMALE (1 << 15)
#define OBJ_EVENT_MON_SPECIES_MASK (OBJ_EVENT_MON - 1)
#define OBJ_EVENT_MON (1u << 15)
#define OBJ_EVENT_MON_SHINY (1u << 14)
#define OBJ_EVENT_MON_FEMALE (1u << 13)
#define OBJ_EVENT_MON_SPECIES_MASK (~(7u << 13))
// Used to call a specific species' follower graphics. Useful for static encounters.
#define OBJ_EVENT_GFX_SPECIES(name) (SPECIES_##name + OBJ_EVENT_MON)

View File

@ -1648,6 +1648,7 @@
#define FLAG_SPECIAL_FLAG_UNUSED_0x4003 (SPECIAL_FLAGS_START + 0x3) // Unused Flag
#define FLAG_STORING_ITEMS_IN_PYRAMID_BAG (SPECIAL_FLAGS_START + 0x4)
#define FLAG_SAFE_FOLLOWER_MOVEMENT (SPECIAL_FLAGS_START + 0x5) // When set, applymovement does not put the follower inside a pokeball
// Also, scripted movements on the player will move follower(s), too
// FLAG_SPECIAL_FLAG_0x4005 - 0x407F also exist and are unused
#define SPECIAL_FLAGS_END (SPECIAL_FLAGS_START + 0x7F)
#define NUM_SPECIAL_FLAGS (SPECIAL_FLAGS_END - SPECIAL_FLAGS_START + 1)

View File

@ -178,6 +178,7 @@ u8 GetWalkInPlaceFastMovementAction(u32);
u8 GetWalkInPlaceNormalMovementAction(u32);
u8 GetWalkInPlaceSlowMovementAction(u32);
u8 GetCollisionAtCoords(struct ObjectEvent *, s16 x, s16 y, u32 dir);
u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords);
void MoveCoords(u8 direction, s16 *x, s16 *y);
bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *);
u8 ObjectEventClearHeldMovementIfFinished(struct ObjectEvent *);

View File

@ -12614,7 +12614,6 @@ static void Cmd_forcerandomswitch(void)
{
CMD_ARGS(const u8 *failInstr);
s32 i;
s32 battler1PartyId = 0;
s32 battler2PartyId = 0;
@ -12749,7 +12748,7 @@ static void Cmd_forcerandomswitch(void)
battler1PartyId = gBattlerPartyIndexes[gBattlerTarget];
}
for (i = firstMonId; i < lastMonId; i++)
for (u32 i = firstMonId; i < lastMonId; i++)
{
if (GetMonData(&party[i], MON_DATA_SPECIES) != SPECIES_NONE
&& !GetMonData(&party[i], MON_DATA_IS_EGG)
@ -12780,12 +12779,12 @@ static void Cmd_forcerandomswitch(void)
|| (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK && gBattleTypeFlags & BATTLE_TYPE_BATTLE_TOWER)
|| (gBattleTypeFlags & BATTLE_TYPE_RECORDED_LINK && gBattleTypeFlags & BATTLE_TYPE_MULTI))
{
SwitchPartyOrderLinkMulti(gBattlerTarget, i, 0);
SwitchPartyOrderLinkMulti(BATTLE_PARTNER(gBattlerTarget), i, 1);
SwitchPartyOrderLinkMulti(gBattlerTarget, gBattleStruct->monToSwitchIntoId[gBattlerTarget], 0);
SwitchPartyOrderLinkMulti(BATTLE_PARTNER(gBattlerTarget), gBattleStruct->monToSwitchIntoId[gBattlerTarget], 1);
}
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
SwitchPartyOrderInGameMulti(gBattlerTarget, i);
SwitchPartyOrderInGameMulti(gBattlerTarget, gBattleStruct->monToSwitchIntoId[gBattlerTarget]);
}
}
else

View File

@ -3545,6 +3545,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
.priority = 0,
.category = DAMAGE_CATEGORY_SPECIAL,
.healingMove = B_HEAL_BLOCKING >= GEN_6,
.ignoresSubstitute = B_UPDATED_MOVE_FLAGS >= GEN_5,
.contestEffect = CONTEST_EFFECT_STARTLE_PREV_MONS,
.contestCategory = CONTEST_CATEGORY_SMART,
.contestComboStarterId = 0,

View File

@ -381,7 +381,7 @@ static const bool8 sMovementTypeHasRange[NUM_MOVEMENT_TYPES] = {
[MOVEMENT_TYPE_COPY_PLAYER_CLOCKWISE_IN_GRASS] = TRUE,
};
const u8 gInitialMovementTypeFacingDirections[] = {
const u8 gInitialMovementTypeFacingDirections[NUM_MOVEMENT_TYPES] = {
[MOVEMENT_TYPE_NONE] = DIR_SOUTH,
[MOVEMENT_TYPE_LOOK_AROUND] = DIR_SOUTH,
[MOVEMENT_TYPE_WANDER_AROUND] = DIR_SOUTH,
@ -463,6 +463,7 @@ const u8 gInitialMovementTypeFacingDirections[] = {
[MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_UP] = DIR_NORTH,
[MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_LEFT] = DIR_WEST,
[MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT] = DIR_EAST,
[MOVEMENT_TYPE_FOLLOW_PLAYER] = DIR_SOUTH,
};
#include "data/object_events/object_event_graphics_info_pointers.h"
@ -1925,6 +1926,14 @@ struct Pokemon *GetFirstLiveMon(void)
u32 i;
for (i = 0; i < PARTY_SIZE; i++)
{
struct Pokemon *mon = &gPlayerParty[i];
if ((OW_MON_ALLOWED_SPECIES && GetMonData(mon, MON_DATA_SPECIES_OR_EGG) != VarGet(OW_MON_ALLOWED_SPECIES))
|| (OW_MON_ALLOWED_MET_LVL && GetMonData(mon, MON_DATA_MET_LEVEL) != VarGet(OW_MON_ALLOWED_MET_LVL))
|| (OW_MON_ALLOWED_MET_LOC && GetMonData(mon, MON_DATA_MET_LOCATION) != VarGet(OW_MON_ALLOWED_MET_LOC)))
{
continue;
}
if (gPlayerParty[i].hp > 0 && !(gPlayerParty[i].box.isEgg || gPlayerParty[i].box.isBadEgg))
return &gPlayerParty[i];
}
@ -5424,6 +5433,7 @@ bool8 MovementType_FollowPlayer_Moving(struct ObjectEvent *objectEvent, struct S
objectEvent->movementActionId = MOVEMENT_ACTION_NONE;
sprite->sActionFuncId = 0;
objectEvent->singleMovementActive = FALSE;
objectEvent->facingDirectionLocked = FALSE;
if (sprite->sTypeFuncId) // restore nonzero state
sprite->sTypeFuncId = 1;
}
@ -5494,7 +5504,8 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri
{
// Animate exiting pokeball
// Player is jumping, but follower is invisible
if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2)
// don't emerge if player is jumping or moving via script
if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2 || ArePlayerFieldControlsLocked())
{
sprite->sTypeFuncId = 0; // return to shadowing state
return FALSE;
@ -5515,6 +5526,15 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri
// Follow player
direction = GetDirectionToFace(x, y, targetX, targetY);
// During a script, if player sidesteps or backsteps,
// mirror player's direction instead
if (ArePlayerFieldControlsLocked()
&& gObjectEvents[gPlayerAvatar.objectEventId].facingDirection != gObjectEvents[gPlayerAvatar.objectEventId].movementDirection)
{
direction = gObjectEvents[gPlayerAvatar.objectEventId].movementDirection;
objectEvent->facingDirectionLocked = TRUE;
}
MoveCoords(direction, &x, &y);
GetCollisionAtCoords(objectEvent, x, y, direction); // Sets directionOverwrite for stairs
if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE)
@ -5522,53 +5542,28 @@ bool8 FollowablePlayerMovement_Step(struct ObjectEvent *objectEvent, struct Spri
// InitJumpRegular will set the proper speed
ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction));
}
else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH))
else if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT)
{
// Set follow speed according to player's speed
if (playerAction >= MOVEMENT_ACTION_RUN_DOWN_SLOW && playerAction <= MOVEMENT_ACTION_RUN_RIGHT_SLOW)
if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH)) // on sideways stairs
objectEvent->movementActionId = GetWalkNormalMovementAction(direction);
else
objectEvent->movementActionId = GetWalkFastMovementAction(direction);
}
else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2)
{
ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction));
}
else
{
if (playerAction >= MOVEMENT_ACTION_WALK_SLOW_DOWN && playerAction <= MOVEMENT_ACTION_WALK_SLOW_RIGHT)
{
ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction));
}
else
{
objectEvent->movementActionId = GetWalkNormalMovementAction(direction);
if (OW_FOLLOWERS_BOBBING == TRUE)
sprite->y2 = -1;
}
}
sprite->sActionFuncId = 0;
if (GetLedgeJumpDirection(x, y, direction) != DIR_NONE)
{
// InitJumpRegular will set the proper speed
ObjectEventSetSingleMovement(objectEvent, sprite, GetJump2MovementAction(direction));
}
else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH))
{
// Set follow speed according to player's speed
ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkFastMovementAction(direction));
}
else if (PlayerGetCopyableMovement() == COPY_MOVE_JUMP2)
{
// If *player* jumps, make step take twice as long
ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkSlowMovementAction(direction));
}
else if (gSprites[gPlayerAvatar.spriteId].data[4] == MOVE_SPEED_FAST_1)
{
objectEvent->movementActionId = GetWalkFastMovementAction(direction);
}
else
{
ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkNormalMovementAction(direction));
objectEvent->movementActionId = GetWalkNormalMovementAction(direction);
if (OW_FOLLOWERS_BOBBING == TRUE)
sprite->y2 = -1;
}
sprite->sActionFuncId = 0;
objectEvent->singleMovementActive = TRUE;
sprite->sTypeFuncId = 2;
return TRUE;
@ -6232,13 +6227,19 @@ static bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent,
return FALSE;
}
static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 x, s16 y)
u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords)
{
u8 i;
struct ObjectEvent *curObject;
if (objectEvent->localId == OBJ_EVENT_ID_FOLLOWER)
return FALSE; // follower cannot collide with other objects, but they can collide with it
return OBJECT_EVENTS_COUNT; // follower cannot collide with other objects, but they can collide with it
if (addCoords)
{
x += objectEvent->currentCoords.x;
y += objectEvent->currentCoords.y;
}
for (i = 0; i < OBJECT_EVENTS_COUNT; i++)
{
@ -6249,11 +6250,16 @@ static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16
if ((curObject->currentCoords.x == x && curObject->currentCoords.y == y) || (curObject->previousCoords.x == x && curObject->previousCoords.y == y))
{
if (AreElevationsCompatible(objectEvent->currentElevation, curObject->currentElevation))
return TRUE;
return i;
}
}
}
return FALSE;
return OBJECT_EVENTS_COUNT;
}
static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *objectEvent, s16 x, s16 y)
{
return (GetObjectObjectCollidesWith(objectEvent, x, y, FALSE) < OBJECT_EVENTS_COUNT);
}
bool8 IsBerryTreeSparkling(u8 localId, u8 mapNum, u8 mapGroup)
@ -6405,6 +6411,19 @@ static u8 TryUpdateMovementActionOnStairs(struct ObjectEvent *objectEvent, u8 mo
}
}
static const u8 sActionIdToCopyableMovement[] = {
[MOVEMENT_ACTION_FACE_DOWN ... MOVEMENT_ACTION_FACE_RIGHT] = COPY_MOVE_FACE,
[MOVEMENT_ACTION_WALK_SLOW_DOWN ... MOVEMENT_ACTION_WALK_NORMAL_RIGHT] = COPY_MOVE_WALK,
[MOVEMENT_ACTION_JUMP_2_DOWN ... MOVEMENT_ACTION_JUMP_2_RIGHT] = COPY_MOVE_JUMP2,
[MOVEMENT_ACTION_WALK_FAST_DOWN ... MOVEMENT_ACTION_WALK_FAST_RIGHT] = COPY_MOVE_WALK,
[MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN ... MOVEMENT_ACTION_PLAYER_RUN_RIGHT] = COPY_MOVE_WALK,
// Not a typo; follower needs to take an action with a duration == JUMP's,
// and JUMP2 here will lead to WALK_SLOW later
[MOVEMENT_ACTION_JUMP_DOWN ... MOVEMENT_ACTION_JUMP_RIGHT] = COPY_MOVE_JUMP2,
[MOVEMENT_ACTION_NONE] = COPY_MOVE_NONE,
};
bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementActionId)
{
if (ObjectEventIsMovementOverridden(objectEvent))
@ -6417,6 +6436,16 @@ bool8 ObjectEventSetHeldMovement(struct ObjectEvent *objectEvent, u8 movementAct
objectEvent->heldMovementActive = TRUE;
objectEvent->heldMovementFinished = FALSE;
gSprites[objectEvent->spriteId].sActionFuncId = 0;
// When player is moved via script, set copyable movement
// for any followers via a lookup table
if (ArePlayerFieldControlsLocked()
&& objectEvent->isPlayer
&& FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT))
{
objectEvent->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId];
}
return FALSE;
}
@ -6440,6 +6469,15 @@ void ObjectEventClearHeldMovement(struct ObjectEvent *objectEvent)
objectEvent->heldMovementFinished = FALSE;
gSprites[objectEvent->spriteId].sTypeFuncId = 0;
gSprites[objectEvent->spriteId].sActionFuncId = 0;
// When player is moved via script, set copyable movement
// for any followers via a lookup table
if (ArePlayerFieldControlsLocked()
&& objectEvent->isPlayer
&& FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT))
{
objectEvent->playerCopyableMovement = sActionIdToCopyableMovement[objectEvent->movementActionId];
}
}
u8 ObjectEventCheckHeldMovementStatus(struct ObjectEvent *objectEvent)

View File

@ -1255,6 +1255,21 @@ bool8 ScrCmd_fadeinbgm(struct ScriptContext *ctx)
return FALSE;
}
struct ObjectEvent *ScriptHideFollower(void)
{
struct ObjectEvent *obj = GetFollowerObject();
if (obj == NULL || obj->invisible)
return NULL;
ClearObjectEventMovement(obj, &gSprites[obj->spriteId]);
gSprites[obj->spriteId].animCmdIndex = 0; // Reset start frame of animation
// Note: ScriptMovement_ returns TRUE on error
if (ScriptMovement_StartObjectMovementScript(obj->localId, obj->mapGroup, obj->mapNum, EnterPokeballMovement))
return NULL;
return obj;
}
bool8 ScrCmd_applymovement(struct ScriptContext *ctx)
{
u16 localId = VarGet(ScriptReadHalfword(ctx));
@ -1274,17 +1289,11 @@ bool8 ScrCmd_applymovement(struct ScriptContext *ctx)
gObjectEvents[GetObjectEventIdByLocalId(localId)].directionOverwrite = DIR_NONE;
ScriptMovement_StartObjectMovementScript(localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, movementScript);
sMovingNpcId = localId;
objEvent = GetFollowerObject();
// Force follower into pokeball
if (localId != OBJ_EVENT_ID_FOLLOWER
&& !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)
&& (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd)
&& (objEvent = GetFollowerObject())
&& !objEvent->invisible)
&& !FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT)
&& (movementScript < Common_Movement_FollowerSafeStart || movementScript > Common_Movement_FollowerSafeEnd))
{
ClearObjectEventMovement(objEvent, &gSprites[objEvent->spriteId]);
gSprites[objEvent->spriteId].animCmdIndex = 0; // Reset start frame of animation
ScriptMovement_StartObjectMovementScript(OBJ_EVENT_ID_FOLLOWER, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, EnterPokeballMovement);
ScriptHideFollower();
}
return FALSE;
}
@ -1546,8 +1555,11 @@ bool8 ScrCmd_lockall(struct ScriptContext *ctx)
}
else
{
struct ObjectEvent *followerObj = GetFollowerObject();
FreezeObjects_WaitForPlayer();
SetupNativeScript(ctx, IsFreezePlayerFinished);
if (FlagGet(FLAG_SAFE_FOLLOWER_MOVEMENT) && followerObj) // Unfreeze follower object (conditionally)
UnfreezeObjectEvent(followerObj);
return TRUE;
}
}
@ -3100,6 +3112,28 @@ bool8 Scrcmd_getobjectfacingdirection(struct ScriptContext *ctx)
return FALSE;
}
bool8 ScrFunc_hidefollower(struct ScriptContext *ctx)
{
bool16 wait = VarGet(ScriptReadHalfword(ctx));
struct ObjectEvent *obj;
if ((obj = ScriptHideFollower()) != NULL && wait)
{
sMovingNpcId = obj->localId;
sMovingNpcMapGroup = obj->mapGroup;
sMovingNpcMapNum = obj->mapNum;
SetupNativeScript(ctx, WaitForMovementFinish);
}
// Just in case, prevent `applymovement`
// from hiding the follower again
if (obj)
FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT);
// execute next script command with no delay
return TRUE;
}
void Script_EndTrainerCanSeeIf(struct ScriptContext *ctx)
{
u8 condition = ScriptReadByte(ctx);

View File

@ -6,6 +6,7 @@
#include "trainer_see.h"
#include "util.h"
#include "constants/event_objects.h"
#include "constants/flags.h"
#include "constants/map_scripts.h"
#include "field_message_box.h"
@ -259,6 +260,8 @@ void ScriptContext_SetupScript(const u8 *ptr)
InitScriptContext(&sGlobalScriptContext, gScriptCmdTable, gScriptCmdTableEnd);
SetupBytecodeScript(&sGlobalScriptContext, ptr);
LockPlayerFieldControls();
if (OW_FOLLOWERS_SCRIPT_MOVEMENT)
FlagSet(FLAG_SAFE_FOLLOWER_MOVEMENT);
sGlobalScriptContextStatus = CONTEXT_RUNNING;
}

View File

@ -1,6 +1,7 @@
#include "global.h"
#include "script_movement.h"
#include "event_object_movement.h"
#include "event_scripts.h"
#include "task.h"
#include "util.h"
#include "constants/event_objects.h"
@ -205,13 +206,32 @@ static void ScriptMovement_MoveObjects(u8 taskId)
}
}
// from event_object_movement
#define sTypeFuncId data[1]
#define sTimer data[5]
static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, const u8 *movementScript)
{
u8 nextMoveActionId;
struct ObjectEvent *obj = &gObjectEvents[objEventId];
if (ObjectEventIsHeldMovementActive(&gObjectEvents[objEventId])
&& !ObjectEventClearHeldMovementIfFinished(&gObjectEvents[objEventId]))
if (ObjectEventIsHeldMovementActive(obj) && !ObjectEventClearHeldMovementIfFinished(obj))
{
// If, while undergoing scripted movement,
// a non-player object collides with an active follower pokemon,
// put that follower into a pokeball
// (sTimer helps limit this expensive check to once per step)
if (OW_FOLLOWERS_SCRIPT_MOVEMENT && gSprites[obj->spriteId].sTimer == 1
&& (objEventId = GetObjectObjectCollidesWith(obj, 0, 0, TRUE)) < OBJECT_EVENTS_COUNT
// switch `obj` to follower
&& ((obj = &gObjectEvents[objEventId])->movementType == MOVEMENT_TYPE_FOLLOW_PLAYER)
&& gSprites[obj->spriteId].sTypeFuncId != 0)
{
ClearObjectEventMovement(obj, &gSprites[obj->spriteId]);
ScriptMovement_StartObjectMovementScript(obj->localId, obj->mapNum, obj->mapGroup, EnterPokeballMovement);
}
return;
}
nextMoveActionId = *movementScript;
if (nextMoveActionId == MOVEMENT_ACTION_STEP_END)
@ -229,3 +249,5 @@ static void ScriptMovement_TakeStep(u8 taskId, u8 moveScrId, u8 objEventId, cons
}
}
#undef sTypeFuncId
#undef sTimer

View File

@ -55,6 +55,62 @@ SINGLE_BATTLE_TEST("Dream Eater fails if Heal Block applies")
}
}
TO_DO_BATTLE_TEST("Dream Eater works on targets with Comatose");
TO_DO_BATTLE_TEST("Dream Eater fails if the target is behind a Substitute (Gen 1-4)");
TO_DO_BATTLE_TEST("Dream Eater works if the target is behind a Substitute (Gen 5+)");
SINGLE_BATTLE_TEST("Dream Eater works on targets with Comatose")
{
s16 damage;
s16 healed;
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_KOMALA) { Ability(ABILITY_COMATOSE); }
} WHEN {
TURN { MOVE(player, MOVE_DREAM_EATER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player);
HP_BAR(opponent, captureDamage: &damage);
HP_BAR(player, captureDamage: &healed);
} THEN {
EXPECT_MUL_EQ(damage, Q_4_12(-1.0/2.0), healed);
}
}
#if B_UPDATED_MOVE_FLAGS < GEN_5
SINGLE_BATTLE_TEST("Dream Eater fails if the target is behind a Substitute (Gen 1-4)")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE);
ASSUME(!gMovesInfo[MOVE_DREAM_EATER].ignoresSubstitute);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_YAWN); MOVE(player, MOVE_SUBSTITUTE); }
TURN { }
TURN { MOVE(opponent, MOVE_DREAM_EATER); }
} SCENE {
MESSAGE("The opposing Wobbuffet used Dream Eater!");
MESSAGE("Wobbuffet wasn't affected!");
}
}
#else
SINGLE_BATTLE_TEST("Dream Eater works if the target is behind a Substitute (Gen 5+)")
{
s16 damage;
s16 healed;
GIVEN {
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
ASSUME(gMovesInfo[MOVE_SUBSTITUTE].effect == EFFECT_SUBSTITUTE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
} WHEN {
TURN { MOVE(opponent, MOVE_YAWN); MOVE(player, MOVE_SUBSTITUTE); }
TURN { }
TURN { MOVE(opponent, MOVE_DREAM_EATER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, opponent);
HP_BAR(player, captureDamage: &damage);
HP_BAR(opponent, captureDamage: &healed);
} THEN {
EXPECT_MUL_EQ(damage, Q_4_12(-1.0/2.0), healed);
}
}
#endif

View File

@ -1,9 +1,6 @@
#include "global.h"
#include "test/battle.h"
TO_DO_BATTLE_TEST("Reflect Type fails if the user is Terastallized");
TO_DO_BATTLE_TEST("Reflect Type succeeds against a Terastallized target and copies its Tera type");
SINGLE_BATTLE_TEST("Reflect Type does not affect any of Arceus' forms")
{
u32 j;
@ -86,7 +83,7 @@ SINGLE_BATTLE_TEST("Reflect Type does not affect any of Silvally's forms")
}
}
SINGLE_BATTLE_TEST("Reflect Type does not affect Pokémon with no types")
SINGLE_BATTLE_TEST("Reflect Type fails if the target has no types")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_ARCANINE].types[0] == TYPE_FIRE);
@ -151,7 +148,7 @@ SINGLE_BATTLE_TEST("Reflect Type copies a target's pure type")
}
}
SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's types[0] and types[1] if the target only has a 3rd type")
SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's 1st and 2nd types if the target only has a 3rd type")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC);
@ -184,3 +181,38 @@ SINGLE_BATTLE_TEST("Reflect Type defaults to Normal type for the user's types[0]
EXPECT_EQ(player->types[2], TYPE_GRASS);
}
}
SINGLE_BATTLE_TEST("Reflect Type fails if the user is Terastallized")
{
GIVEN {
PLAYER(SPECIES_ARCANINE) { TeraType(TYPE_NORMAL); }
OPPONENT(SPECIES_POLIWRATH);
} WHEN {
TURN { MOVE(player, MOVE_REFLECT_TYPE, gimmick: GIMMICK_TERA); }
} SCENE {
MESSAGE("Arcanine used Reflect Type!");
MESSAGE("But it failed!");
} THEN {
EXPECT_EQ(player->types[0], TYPE_FIRE);
EXPECT_EQ(player->types[1], TYPE_FIRE);
EXPECT_EQ(player->types[2], TYPE_MYSTERY);
}
}
SINGLE_BATTLE_TEST("Reflect Type succeeds against a Terastallized target and copies its Tera type")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[0] != TYPE_NORMAL);
ASSUME(gSpeciesInfo[SPECIES_POLIWRATH].types[1] != TYPE_NORMAL);
PLAYER(SPECIES_ARCANINE) { TeraType(TYPE_NORMAL); }
OPPONENT(SPECIES_POLIWRATH);
} WHEN {
TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_REFLECT_TYPE); }
} SCENE {
MESSAGE("The opposing Poliwrath used Reflect Type!");
} THEN {
EXPECT_EQ(opponent->types[0], TYPE_NORMAL);
EXPECT_EQ(opponent->types[1], TYPE_NORMAL);
EXPECT_EQ(opponent->types[2], TYPE_NORMAL);
}
}