diff --git a/asm/macros/movement.inc b/asm/macros/movement.inc index a43dc07ac5..b0bb1f35ee 100644 --- a/asm/macros/movement.inc +++ b/asm/macros/movement.inc @@ -162,5 +162,7 @@ create_movement_action figure_8, MOVEMENT_ACTION_FIGURE_8 create_movement_action fly_up, MOVEMENT_ACTION_FLY_UP create_movement_action fly_down, MOVEMENT_ACTION_FLY_DOWN + create_movement_action emote_double_exclamation_mark, MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK + create_movement_action emote_x, MOVEMENT_ACTION_EMOTE_X create_movement_action step_end, MOVEMENT_ACTION_STEP_END diff --git a/data/event_scripts.s b/data/event_scripts.s index a376866181..7ce6f60485 100644 --- a/data/event_scripts.s +++ b/data/event_scripts.s @@ -1005,6 +1005,13 @@ Common_EventScript_LegendaryFlewAway:: release end +EventScript_VsSeekerChargingDone:: + special VsSeekerFreezeObjectsAfterChargeComplete + waitstate + special VsSeekerResetObjectMovementAfterChargeComplete + releaseall + end + .include "data/scripts/pc_transfer.inc" .include "data/scripts/questionnaire.inc" .include "data/scripts/abnormal_weather.inc" diff --git a/data/field_effect_scripts.s b/data/field_effect_scripts.s index 49fcf798b6..43f4222596 100644 --- a/data/field_effect_scripts.s +++ b/data/field_effect_scripts.s @@ -1,3 +1,4 @@ +#include "config/item.h" .include "asm/macros.inc" .include "constants/constants.inc" @@ -72,6 +73,9 @@ gFieldEffectScriptPointers:: .4byte gFieldEffectScript_RayquazaSpotlight @ FLDEFF_RAYQUAZA_SPOTLIGHT .4byte gFieldEffectScript_DestroyDeoxysRock @ FLDEFF_DESTROY_DEOXYS_ROCK .4byte gFieldEffectScript_MoveDeoxysRock @ FLDEFF_MOVE_DEOXYS_ROCK + .4byte gFldEffScript_UseVsSeeker @ FLDEFF_USE_VS_SEEKER + .4byte gFldEffScript_XIcon @ FLDEFF_X_ICON + .4byte gFldEffScript_DoubleExclMarkIcon @ FLDEFF_DOUBLE_EXCL_MARK_ICON gFieldEffectScript_ExclamationMarkIcon1:: field_eff_callnative FldEff_ExclamationMarkIcon @@ -343,3 +347,15 @@ gFieldEffectScript_DestroyDeoxysRock:: gFieldEffectScript_MoveDeoxysRock:: field_eff_callnative FldEff_MoveDeoxysRock field_eff_end + +gFldEffScript_UseVsSeeker:: + field_eff_callnative FldEff_UseVsSeeker + field_eff_end + +gFldEffScript_XIcon:: + field_eff_callnative FldEff_XIcon + field_eff_end + +gFldEffScript_DoubleExclMarkIcon:: + field_eff_callnative FldEff_DoubleExclMarkIcon + field_eff_end diff --git a/data/specials.inc b/data/specials.inc index 34e86cad29..f05655cbd3 100644 --- a/data/specials.inc +++ b/data/specials.inc @@ -540,3 +540,5 @@ gSpecials:: def_special GetSprayId def_special GetLastUsedSprayType def_special TrySkyBattle + def_special VsSeekerResetObjectMovementAfterChargeComplete + def_special VsSeekerFreezeObjectsAfterChargeComplete \ No newline at end of file diff --git a/data/text/trainers.inc b/data/text/trainers.inc index c3e85ef504..ac3747f805 100644 --- a/data/text/trainers.inc +++ b/data/text/trainers.inc @@ -4779,3 +4779,18 @@ Route134_Text_HudsonPostBattle: .string "Our boat drifted out to sea.\p" .string "My buddy's a timid fellow, so I'm\n" .string "worried about him.$" + +VSSeeker_Text_BatteryNotChargedNeedXSteps:: + .string "The battery isn't charged enough.\p" + .string "No. of steps required to fully\n" + .string "charge the battery: {STR_VAR_1}{PAUSE_UNTIL_PRESS}$" + +VSSeeker_Text_NoTrainersWithinRange:: + .string "There are no Trainers within range\n" + .string "who can battleā€¦\p" + .string "The VS Seeker was turned off.{PAUSE_UNTIL_PRESS}$" + +VSSeeker_Text_TrainersNotReady:: + .string "The other Trainers don't appear\n" + .string "to be ready for battle.\p" + .string "Let's wait till later.{PAUSE_UNTIL_PRESS}$" diff --git a/graphics/field_effects/pics/emote_x.png b/graphics/field_effects/pics/emote_x.png new file mode 100644 index 0000000000..6d0206e1d4 Binary files /dev/null and b/graphics/field_effects/pics/emote_x.png differ diff --git a/graphics/field_effects/pics/emotion_double_exclamation.png b/graphics/field_effects/pics/emotion_double_exclamation.png new file mode 100644 index 0000000000..1494acef86 Binary files /dev/null and b/graphics/field_effects/pics/emotion_double_exclamation.png differ diff --git a/include/battle_setup.h b/include/battle_setup.h index 96aeb4b761..a2009eea41 100644 --- a/include/battle_setup.h +++ b/include/battle_setup.h @@ -70,5 +70,8 @@ u16 CountBattledRematchTeams(u16 trainerId); void DoStandardWildBattle_Debug(void); void BattleSetup_StartTrainerBattle_Debug(void); +s32 TrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId); +s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId); +u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId); #endif // GUARD_BATTLE_SETUP_H diff --git a/include/config/item.h b/include/config/item.h index 55de3fdb60..868afae1a7 100644 --- a/include/config/item.h +++ b/include/config/item.h @@ -29,4 +29,7 @@ #define VAR_LAST_REPEL_LURE_USED 0 // If this var has been assigned, last Repel/Lure used will be saved and the player will get prompted with the vanilla repel YES/NO option, unless I_REPEL_LURE_MENU is set to TRUE. #define I_REPEL_LURE_MENU TRUE // If TRUE, the player is able to choose which Repel/Lure to use once the previous one runs out. Cursor position is saved by VAR_LAST_REPEL_LURE_USED if not 0. +// Vs. Seeker +#define I_VS_SEEKER_CHARGING 0 // If this flag is assigned, the Vs Seeker will functionlity will be enabled. When the player has the Vs. Seeker, Match Call rematch functions will stop working. + #endif // GUARD_CONFIG_ITEM_H diff --git a/include/constants/event_object_movement.h b/include/constants/event_object_movement.h index af5af53403..cd971efddf 100755 --- a/include/constants/event_object_movement.h +++ b/include/constants/event_object_movement.h @@ -242,6 +242,8 @@ #define MOVEMENT_ACTION_FIGURE_8 0x9B #define MOVEMENT_ACTION_FLY_UP 0x9C #define MOVEMENT_ACTION_FLY_DOWN 0x9D +#define MOVEMENT_ACTION_EMOTE_X 0x9E +#define MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK 0x9F #define MOVEMENT_ACTION_STEP_END 0xFE #define MOVEMENT_ACTION_NONE 0xFF @@ -300,6 +302,7 @@ #define ANIM_GET_ON_OFF_POKEMON_EAST (ANIM_STD_COUNT + 3) #define ANIM_NURSE_BOW (ANIM_STD_COUNT + 0) +#define ANIM_RAISE_HAND (ANIM_STD_COUNT + 0) #define ANIM_FIELD_MOVE 0 diff --git a/include/constants/field_effects.h b/include/constants/field_effects.h index a620409479..f8efeb9b81 100644 --- a/include/constants/field_effects.h +++ b/include/constants/field_effects.h @@ -68,6 +68,9 @@ #define FLDEFF_RAYQUAZA_SPOTLIGHT 64 #define FLDEFF_DESTROY_DEOXYS_ROCK 65 #define FLDEFF_MOVE_DEOXYS_ROCK 66 +#define FLDEFF_USE_VS_SEEKER 67 +#define FLDEFF_X_ICON 68 +#define FLDEFF_DOUBLE_EXCL_MARK_ICON 69 #define FLDEFFOBJ_SHADOW_S 0 #define FLDEFFOBJ_SHADOW_M 1 diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 01269cdb5e..b48522ebc8 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -440,4 +440,7 @@ bool32 IsVirtualObjectInvisible(u8 virtualObjId); void SetVirtualObjectSpriteAnim(u8 virtualObjId, u8 animNum); bool32 IsVirtualObjectAnimating(u8 virtualObjId); +bool8 MovementAction_EmoteX_Step0(struct ObjectEvent *, struct Sprite *); +bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *, struct Sprite *); + #endif //GUARD_EVENT_OBJECT_MOVEMENT_H diff --git a/include/event_scripts.h b/include/event_scripts.h index 4e0a88f82a..00b06165cf 100644 --- a/include/event_scripts.h +++ b/include/event_scripts.h @@ -612,4 +612,9 @@ extern const u8 EventScript_TradeCenter_Chair0[]; extern const u8 EventScript_ConfirmLeaveCableClubRoom[]; extern const u8 EventScript_TerminateLink[]; +extern const u8 VSSeeker_Text_BatteryNotChargedNeedXSteps[]; +extern const u8 VSSeeker_Text_NoTrainersWithinRange[]; +extern const u8 VSSeeker_Text_TrainersNotReady[]; +extern const u8 EventScript_VsSeekerChargingDone[]; + #endif // GUARD_EVENT_SCRIPTS_H diff --git a/include/global.fieldmap.h b/include/global.fieldmap.h index 7461929c3a..b5bf29ca88 100644 --- a/include/global.fieldmap.h +++ b/include/global.fieldmap.h @@ -254,6 +254,7 @@ enum { PLAYER_AVATAR_STATE_FIELD_MOVE, PLAYER_AVATAR_STATE_FISHING, PLAYER_AVATAR_STATE_WATERING, + PLAYER_AVATAR_STATE_VSSEEKER, }; #define PLAYER_AVATAR_FLAG_ON_FOOT (1 << 0) diff --git a/include/item_use.h b/include/item_use.h index 5abe87f057..90d671bd42 100644 --- a/include/item_use.h +++ b/include/item_use.h @@ -41,6 +41,8 @@ void ItemUseInBattle_PartyMenuChooseMove(u8 taskId); void Task_UseDigEscapeRopeOnField(u8 taskId); u8 CanUseDigOrEscapeRopeOnCurMap(void); u8 CheckIfItemIsTMHMOrEvolutionStone(u16 itemId); +void FieldUseFunc_VsSeeker(u8 taskId); +void Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker(u8 taskId); enum { BALL_THROW_UNABLE_TWO_MONS, diff --git a/include/trainer_see.h b/include/trainer_see.h index ab808c720c..5518a91761 100644 --- a/include/trainer_see.h +++ b/include/trainer_see.h @@ -26,5 +26,7 @@ u8 FldEff_HeartIcon(void); u8 GetCurrentApproachingTrainerObjectEventId(void); u8 GetChosenApproachingTrainerObjectEventId(u8 arrayId); void PlayerFaceTrainerAfterBattle(void); +u8 FldEff_DoubleExclMarkIcon(void); +u8 FldEff_XIcon(void); #endif // GUARD_TRAINER_SEE_H diff --git a/include/vs_seeker.h b/include/vs_seeker.h new file mode 100644 index 0000000000..723e73bf37 --- /dev/null +++ b/include/vs_seeker.h @@ -0,0 +1,14 @@ +#ifndef GUARD_VS_SEEKER_H +#define GUARD_VS_SEEKER_H + +#include "global.h" + +void Task_InitVsSeekerAndCheckForTrainersOnScreen(u8 taskId); +bool8 UpdateVsSeekerStepCounter(void); +void MapResetTrainerRematches(u16 mapGroup, u16 mapNum); +void ClearRematchMovementByTrainerId(void); +u16 GetRematchTrainerIdVSSeeker(u16 trainerId); + +#define VSSEEKER_RECHARGE_STEPS 100 + +#endif //GUARD_VS_SEEKER_H diff --git a/src/battle_setup.c b/src/battle_setup.c index 72a41aca9c..5459c04925 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -38,6 +38,8 @@ #include "mirage_tower.h" #include "field_screen_effect.h" #include "data.h" +#include "vs_seeker.h" +#include "item.h" #include "constants/battle_frontier.h" #include "constants/battle_setup.h" #include "constants/game_stat.h" @@ -1627,7 +1629,7 @@ static const u8 *GetTrainerCantBattleSpeech(void) return ReturnEmptyStringIfNull(sTrainerCannotBattleSpeech); } -static s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) +s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) { s32 i; @@ -1640,7 +1642,7 @@ static s32 FirstBattleTrainerIdToRematchTableId(const struct RematchTrainer *tab return -1; } -static s32 TrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) +s32 TrainerIdToRematchTableId(const struct RematchTrainer *table, u16 trainerId) { s32 i, j; @@ -1686,30 +1688,42 @@ static void SetRematchIdForTrainer(const struct RematchTrainer *table, u32 table gSaveBlock1Ptr->trainerRematches[tableId] = i; } +static bool32 DoesCurrentMapMatchRematchTrainerMap(s32 i, const struct RematchTrainer *table, u16 mapGroup, u16 mapNum) +{ + return table[i].mapGroup == mapGroup && table[i].mapNum == mapNum; +} + +bool32 TrainerIsMatchCallRegistered(s32 i) +{ + return FlagGet(FLAG_MATCH_CALL_REGISTERED + i); +} + static bool32 UpdateRandomTrainerRematches(const struct RematchTrainer *table, u16 mapGroup, u16 mapNum) { s32 i; - bool32 ret = FALSE; + + if (CheckBagHasItem(ITEM_VS_SEEKER, 1) && I_VS_SEEKER_CHARGING != 0) + return FALSE; for (i = 0; i <= REMATCH_SPECIAL_TRAINER_START; i++) { - if (table[i].mapGroup == mapGroup && table[i].mapNum == mapNum && !IsRematchForbidden(i)) + if (DoesCurrentMapMatchRematchTrainerMap(i,table,mapGroup,mapNum) && !IsRematchForbidden(i)) + continue; + + if (gSaveBlock1Ptr->trainerRematches[i] != 0) { - if (gSaveBlock1Ptr->trainerRematches[i] != 0) - { - // Trainer already wants a rematch. Don't bother updating it. - ret = TRUE; - } - else if (FlagGet(FLAG_MATCH_CALL_REGISTERED + i) - && (Random() % 100) <= 30) // 31% chance of getting a rematch. - { - SetRematchIdForTrainer(table, i); - ret = TRUE; - } + // Trainer already wants a rematch. Don't bother updating it. + return TRUE; + } + else if (TrainerIsMatchCallRegistered(i) && ((Random() % 100) <= 30)) + // 31% chance of getting a rematch. + { + SetRematchIdForTrainer(table, i); + return TRUE; } } - return ret; + return FALSE; } void UpdateRematchIfDefeated(s32 rematchTableId) @@ -1772,7 +1786,7 @@ static bool8 IsTrainerReadyForRematch_(const struct RematchTrainer *table, u16 t return TRUE; } -static u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId) +u16 GetRematchTrainerIdFromTable(const struct RematchTrainer *table, u16 firstBattleTrainerId) { const struct RematchTrainer *trainerEntry; s32 i; @@ -1877,7 +1891,9 @@ static bool32 HasAtLeastFiveBadges(void) void IncrementRematchStepCounter(void) { - if (HasAtLeastFiveBadges()) + if (HasAtLeastFiveBadges() + && (I_VS_SEEKER_CHARGING != 0) + && (!CheckBagHasItem(ITEM_VS_SEEKER, 1))) { if (gSaveBlock1Ptr->trainerRematchStepCounter >= STEP_COUNTER_MAX) gSaveBlock1Ptr->trainerRematchStepCounter = STEP_COUNTER_MAX; @@ -1912,7 +1928,10 @@ bool32 IsRematchTrainerIn(u16 mapGroup, u16 mapNum) static u16 GetRematchTrainerId(u16 trainerId) { - return GetRematchTrainerIdFromTable(gRematchTable, trainerId); + if (FlagGet(I_VS_SEEKER_CHARGING) && (I_VS_SEEKER_CHARGING != 0)) + return GetRematchTrainerIdVSSeeker(trainerId); + else + return GetRematchTrainerIdFromTable(gRematchTable, trainerId); } u16 GetLastBeatenRematchTrainerId(u16 trainerId) @@ -1935,6 +1954,9 @@ bool8 IsTrainerReadyForRematch(void) static void HandleRematchVarsOnBattleEnd(void) { + if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) && (I_VS_SEEKER_CHARGING != 0)) + ClearRematchMovementByTrainerId(); + ClearTrainerWantRematchState(gRematchTable, gTrainerBattleOpponent_A); SetBattledTrainersFlags(); } diff --git a/src/data/items.h b/src/data/items.h index 670d7dd2e5..85fa2dc893 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -8723,7 +8723,11 @@ const struct Item gItems[] = .importance = 1, .pocket = POCKET_KEY_ITEMS, .type = ITEM_USE_FIELD, +#if I_VS_SEEKER_CHARGING != 0 + .fieldUseFunc = FieldUseFunc_VsSeeker, +#else .fieldUseFunc = ItemUseOutOfBattle_CannotUse, +#endif }, [ITEM_TM_CASE] = diff --git a/src/data/object_events/movement_action_func_tables.h b/src/data/object_events/movement_action_func_tables.h index 2e1b6b3b31..e16cae74fc 100755 --- a/src/data/object_events/movement_action_func_tables.h +++ b/src/data/object_events/movement_action_func_tables.h @@ -420,6 +420,8 @@ u8 (*const gMovementActionFuncs_StopLevitateAtTop[])(struct ObjectEvent *, struc u8 (*const gMovementActionFuncs_Figure8[])(struct ObjectEvent *, struct Sprite *); u8 (*const gMovementActionFuncs_FlyUp[])(struct ObjectEvent *, struct Sprite *); u8 (*const gMovementActionFuncs_FlyDown[])(struct ObjectEvent *, struct Sprite *); +u8 (*const gMovementActionFuncs_EmoteX[])(struct ObjectEvent *, struct Sprite *); +u8 (*const gMovementActionFuncs_EmoteDoubleExclMark[])(struct ObjectEvent *, struct Sprite *); u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *) = { [MOVEMENT_ACTION_FACE_DOWN] = gMovementActionFuncs_FaceDown, @@ -580,6 +582,8 @@ u8 (*const *const gMovementActionFuncs[])(struct ObjectEvent *, struct Sprite *) [MOVEMENT_ACTION_FIGURE_8] = gMovementActionFuncs_Figure8, [MOVEMENT_ACTION_FLY_UP] = gMovementActionFuncs_FlyUp, [MOVEMENT_ACTION_FLY_DOWN] = gMovementActionFuncs_FlyDown, + [MOVEMENT_ACTION_EMOTE_X] = gMovementActionFuncs_EmoteX, + [MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK] = gMovementActionFuncs_EmoteDoubleExclMark, }; u8 (*const gMovementActionFuncs_FaceDown[])(struct ObjectEvent *, struct Sprite *) = { @@ -1519,3 +1523,13 @@ u8 (*const gMovementActionFuncs_StopLevitateAtTop[])(struct ObjectEvent *, struc MovementAction_StopLevitateAtTop_Step0, MovementAction_Finish, }; + +u8 (*const gMovementActionFuncs_EmoteX[])(struct ObjectEvent *, struct Sprite *) = { + MovementAction_EmoteX_Step0, + MovementAction_Finish, +}; + +u8 (*const gMovementActionFuncs_EmoteDoubleExclMark[])(struct ObjectEvent *, struct Sprite *) = { + MovementAction_EmoteDoubleExclamationMark_Step0, + MovementAction_Finish, +}; diff --git a/src/event_object_movement.c b/src/event_object_movement.c index cdd72d668c..6cd6955031 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -8965,3 +8965,19 @@ u8 MovementAction_Fly_Finish(struct ObjectEvent *objectEvent, struct Sprite *spr { return TRUE; } + +bool8 MovementAction_EmoteX_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventGetLocalIdAndMap(objectEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]); + FieldEffectStart(FLDEFF_X_ICON); + sprite->sActionFuncId = 1; + return TRUE; +} + +bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventGetLocalIdAndMap(objectEvent, &gFieldEffectArguments[0], &gFieldEffectArguments[1], &gFieldEffectArguments[2]); + FieldEffectStart(FLDEFF_DOUBLE_EXCL_MARK_ICON); + sprite->sActionFuncId = 1; + return TRUE; +} diff --git a/src/field_control_avatar.c b/src/field_control_avatar.c index 6a5422046e..d5c252415d 100644 --- a/src/field_control_avatar.c +++ b/src/field_control_avatar.c @@ -28,6 +28,7 @@ #include "start_menu.h" #include "trainer_see.h" #include "trainer_hill.h" +#include "vs_seeker.h" #include "wild_encounter.h" #include "constants/event_bg.h" #include "constants/event_objects.h" @@ -615,6 +616,11 @@ static bool8 TryStartStepCountScript(u16 metatileBehavior) ScriptContext_SetupScript(MossdeepCity_SpaceCenter_2F_EventScript_RivalRayquazaCall); return TRUE; } + if (UpdateVsSeekerStepCounter()) + { + ScriptContext_SetupScript(EventScript_VsSeekerChargingDone); + return TRUE; + } } if (SafariZoneTakeStep() == TRUE) diff --git a/src/field_effect.c b/src/field_effect.c index cfb5021550..2d2aa174ff 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -31,6 +31,7 @@ #include "constants/metatile_behaviors.h" #include "constants/rgb.h" #include "constants/songs.h" +#include "constants/map_types.h" #define subsprite_table(ptr) {.subsprites = ptr, .subspriteCount = (sizeof ptr) / (sizeof(struct Subsprite))} @@ -231,6 +232,12 @@ static void SpriteCB_DeoxysRockFragment(struct Sprite *sprite); static void Task_MoveDeoxysRock(u8 taskId); +static void Task_FldEffUseVsSeeker(u8 taskId); +static void UseVsSeeker_StopPlayerMovement(struct Task *task); +static void UseVsSeeker_DoPlayerAnimation(struct Task *task); +static void UseVsSeeker_ResetPlayerGraphics(struct Task *task); +static void UseVsSeeker_CleanUpFieldEffect(struct Task *task); + // Static RAM declarations static u8 sActiveList[32]; @@ -3911,3 +3918,68 @@ static void Task_MoveDeoxysRock(u8 taskId) #undef tVelocityY #undef tMoveSteps #undef tObjEventId + +static void (*const sUseVsSeekerEffectFuncs[])(struct Task *task) = { + UseVsSeeker_StopPlayerMovement, + UseVsSeeker_DoPlayerAnimation, + UseVsSeeker_ResetPlayerGraphics, + UseVsSeeker_CleanUpFieldEffect +}; + +u32 FldEff_UseVsSeeker(void) +{ + CreateTask(Task_FldEffUseVsSeeker, 0xFF); + return 0; +} + +static void Task_FldEffUseVsSeeker(u8 taskId) +{ + sUseVsSeekerEffectFuncs[gTasks[taskId].data[0]](&gTasks[taskId]); +} + +static void UseVsSeeker_StopPlayerMovement(struct Task *task) +{ + LockPlayerFieldControls(); + FreezeObjectEvents(); + gPlayerAvatar.preventStep = TRUE; + task->data[0]++; +} + +static void UseVsSeeker_DoPlayerAnimation(struct Task *task) +{ + struct ObjectEvent * playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + if ((ObjectEventIsMovementOverridden(playerObj) && (!(ObjectEventClearHeldMovementIfFinished(playerObj))))) + return; + + if (gMapHeader.mapType != MAP_TYPE_UNDERWATER) + { + SetPlayerAvatarFieldMove(); + ObjectEventSetHeldMovement(playerObj, MOVEMENT_ACTION_START_ANIM_IN_DIRECTION); + } + task->data[0]++; +} + +static void UseVsSeeker_ResetPlayerGraphics(struct Task *task) { + struct ObjectEvent* playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + + if (!ObjectEventClearHeldMovementIfFinished(playerObj)) + return; + + if (gMapHeader.mapType != MAP_TYPE_UNDERWATER) + { + ObjectEventSetGraphicsId(&gObjectEvents[gPlayerAvatar.objectEventId], GetPlayerAvatarGraphicsIdByCurrentState()); + ObjectEventForceSetHeldMovement(playerObj, GetFaceDirectionMovementAction(playerObj->facingDirection)); + } + task->data[0]++; +} + +static void UseVsSeeker_CleanUpFieldEffect(struct Task *task) +{ + struct ObjectEvent * playerObj = &gObjectEvents[gPlayerAvatar.objectEventId]; + if (!ObjectEventClearHeldMovementIfFinished(playerObj)) + return; + + gPlayerAvatar.preventStep = FALSE; + FieldEffectActiveListRemove(FLDEFF_USE_VS_SEEKER); + DestroyTask(FindTaskIdByFunc(Task_FldEffUseVsSeeker)); +} diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index 0f087799e3..99cab7fd77 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -241,7 +241,8 @@ static const u8 sRivalAvatarGfxIds[][2] = [PLAYER_AVATAR_STATE_UNDERWATER] = {OBJ_EVENT_GFX_BRENDAN_UNDERWATER, OBJ_EVENT_GFX_MAY_UNDERWATER}, [PLAYER_AVATAR_STATE_FIELD_MOVE] = {OBJ_EVENT_GFX_RIVAL_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_RIVAL_MAY_FIELD_MOVE}, [PLAYER_AVATAR_STATE_FISHING] = {OBJ_EVENT_GFX_BRENDAN_FISHING, OBJ_EVENT_GFX_MAY_FISHING}, - [PLAYER_AVATAR_STATE_WATERING] = {OBJ_EVENT_GFX_BRENDAN_WATERING, OBJ_EVENT_GFX_MAY_WATERING} + [PLAYER_AVATAR_STATE_WATERING] = {OBJ_EVENT_GFX_BRENDAN_WATERING, OBJ_EVENT_GFX_MAY_WATERING}, + [PLAYER_AVATAR_STATE_VSSEEKER] = {OBJ_EVENT_GFX_RIVAL_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_RIVAL_MAY_FIELD_MOVE}, }; static const u8 sPlayerAvatarGfxIds[][2] = @@ -254,6 +255,7 @@ static const u8 sPlayerAvatarGfxIds[][2] = [PLAYER_AVATAR_STATE_FIELD_MOVE] = {OBJ_EVENT_GFX_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_MAY_FIELD_MOVE}, [PLAYER_AVATAR_STATE_FISHING] = {OBJ_EVENT_GFX_BRENDAN_FISHING, OBJ_EVENT_GFX_MAY_FISHING}, [PLAYER_AVATAR_STATE_WATERING] = {OBJ_EVENT_GFX_BRENDAN_WATERING, OBJ_EVENT_GFX_MAY_WATERING}, + [PLAYER_AVATAR_STATE_VSSEEKER] = {OBJ_EVENT_GFX_BRENDAN_FIELD_MOVE, OBJ_EVENT_GFX_MAY_FIELD_MOVE}, }; static const u8 sFRLGAvatarGfxIds[GENDER_COUNT] = diff --git a/src/item_use.c b/src/item_use.c index 11d92e7427..1b87cc0e36 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -38,11 +38,13 @@ #include "string_util.h" #include "task.h" #include "text.h" +#include "vs_seeker.h" #include "constants/event_bg.h" #include "constants/event_objects.h" #include "constants/item_effects.h" #include "constants/items.h" #include "constants/songs.h" +#include "constants/map_types.h" static void SetUpItemUseCallback(u8); static void FieldCB_UseItemOnField(void); @@ -75,6 +77,7 @@ static void Task_CloseCantUseKeyItemMessage(u8); static void SetDistanceOfClosestHiddenItem(u8, s16, s16); static void CB2_OpenPokeblockFromBag(void); static void ItemUseOnFieldCB_Honey(u8 taskId); +static bool32 IsValidLocationForVsSeeker(void); static bool32 CannotUseBagBattleItem(u16 itemId); // EWRAM variables @@ -1315,7 +1318,7 @@ void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8 taskId) } void ItemUseOutOfBattle_RotomCatalog(u8 taskId) -{ +{ if (!gTasks[taskId].tUsingRegisteredKeyItem) { gItemUseCB = ItemUseCB_RotomCatalog; @@ -1330,7 +1333,7 @@ void ItemUseOutOfBattle_RotomCatalog(u8 taskId) } void ItemUseOutOfBattle_ZygardeCube(u8 taskId) -{ +{ if (!gTasks[taskId].tUsingRegisteredKeyItem) { gItemUseCB = ItemUseCB_ZygardeCube; @@ -1379,4 +1382,68 @@ void ItemUseOutOfBattle_CannotUse(u8 taskId) DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem); } +static bool32 IsValidLocationForVsSeeker(void) +{ + u16 mapGroup = gSaveBlock1Ptr->location.mapGroup; + u16 mapNum = gSaveBlock1Ptr->location.mapNum; + u16 mapType = gMapHeader.mapType; + + typedef struct { + u16 mapGroup; + u16 mapNum; + } Location; + + u32 i; + Location validIndoorLocations[] = + { + { MAP_GROUP(MT_PYRE_SUMMIT), MAP_NUM(MT_PYRE_SUMMIT) }, + { MAP_GROUP(SAFARI_ZONE_NORTH), MAP_NUM(SAFARI_ZONE_NORTH) }, + { MAP_GROUP(SAFARI_ZONE_NORTHEAST), MAP_NUM(SAFARI_ZONE_NORTHEAST) }, + { MAP_GROUP(SAFARI_ZONE_NORTHWEST), MAP_NUM(SAFARI_ZONE_NORTHWEST) }, + { MAP_GROUP(SAFARI_ZONE_SOUTH), MAP_NUM(SAFARI_ZONE_SOUTH) }, + { MAP_GROUP(SAFARI_ZONE_SOUTHEAST), MAP_NUM(SAFARI_ZONE_SOUTHEAST) }, + { MAP_GROUP(SAFARI_ZONE_SOUTHWEST), MAP_NUM(SAFARI_ZONE_SOUTHWEST) }, + { MAP_GROUP(SKY_PILLAR_TOP), MAP_NUM(SKY_PILLAR_TOP) }, + { MAP_GROUP(SOUTHERN_ISLAND_EXTERIOR), MAP_NUM(SOUTHERN_ISLAND_EXTERIOR) }, + { MAP_GROUP(SOUTHERN_ISLAND_INTERIOR), MAP_NUM(SOUTHERN_ISLAND_INTERIOR) }, + { MAP_GROUP(RUSTBORO_CITY_GYM), MAP_NUM(RUSTBORO_CITY_GYM) }, + { MAP_GROUP(DEWFORD_TOWN_GYM), MAP_NUM(DEWFORD_TOWN_GYM) }, + { MAP_GROUP(MAUVILLE_CITY_GYM), MAP_NUM(MAUVILLE_CITY_GYM) }, + { MAP_GROUP(LAVARIDGE_TOWN_GYM_1F), MAP_NUM(LAVARIDGE_TOWN_GYM_1F) }, + { MAP_GROUP(LAVARIDGE_TOWN_GYM_B1F), MAP_NUM(LAVARIDGE_TOWN_GYM_B1F) }, + { MAP_GROUP(PETALBURG_CITY_GYM), MAP_NUM(PETALBURG_CITY_GYM) }, + { MAP_GROUP(FORTREE_CITY_GYM), MAP_NUM(FORTREE_CITY_GYM) }, + { MAP_GROUP(MOSSDEEP_CITY_GYM), MAP_NUM(MOSSDEEP_CITY_GYM) }, + { MAP_GROUP(SOOTOPOLIS_CITY_GYM_1F), MAP_NUM(SOOTOPOLIS_CITY_GYM_1F) }, + { MAP_GROUP(SOOTOPOLIS_CITY_GYM_B1F), MAP_NUM(SOOTOPOLIS_CITY_GYM_B1F) }, + }; + + if (IsMapTypeOutdoors(mapType)) + return TRUE; + + for (i = 0; i < ARRAY_COUNT(validIndoorLocations); i++) + { + if (mapNum == validIndoorLocations[i].mapNum && mapGroup == validIndoorLocations[i].mapGroup) + return TRUE; + } + + return FALSE; +} + +void FieldUseFunc_VsSeeker(u8 taskId) +{ + if (IsValidLocationForVsSeeker()) + { + sItemUseOnFieldCB = Task_InitVsSeekerAndCheckForTrainersOnScreen; + SetUpItemUseOnFieldCallback(taskId); + } + else + DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[3]); +} + +void Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker(u8 taskId) +{ + Task_CloseCantUseKeyItemMessage(taskId); +} + #undef tUsingRegisteredKeyItem diff --git a/src/overworld.c b/src/overworld.c index bf2bcf5ace..09e242b217 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -58,6 +58,7 @@ #include "tv.h" #include "scanline_effect.h" #include "wild_encounter.h" +#include "vs_seeker.h" #include "frontier_util.h" #include "constants/abilities.h" #include "constants/layouts.h" @@ -818,6 +819,10 @@ void LoadMapFromCameraTransition(u8 mapGroup, u8 mapNum) ResetCyclingRoadChallengeData(); RestartWildEncounterImmunitySteps(); TryUpdateRandomTrainerRematches(mapGroup, mapNum); + +if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(mapGroup, mapNum); + DoTimeBasedEvents(); SetSavedWeatherFromCurrMapHeader(); ChooseAmbientCrySpecies(); @@ -868,6 +873,10 @@ static void LoadMapFromWarp(bool32 a1) ResetCyclingRoadChallengeData(); RestartWildEncounterImmunitySteps(); TryUpdateRandomTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); + +if (I_VS_SEEKER_CHARGING != 0) + MapResetTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum); + if (a1 != TRUE) DoTimeBasedEvents(); SetSavedWeatherFromCurrMapHeader(); diff --git a/src/trainer_see.c b/src/trainer_see.c index 718afb19d1..ee10747cdf 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -62,6 +62,8 @@ EWRAM_DATA u8 gApproachingTrainerId = 0; static const u8 sEmotion_ExclamationMarkGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_exclamation.4bpp"); static const u8 sEmotion_QuestionMarkGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_question.4bpp"); static const u8 sEmotion_HeartGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_heart.4bpp"); +static const u8 sEmotion_DoubleExclamationMarkGfx[] = INCBIN_U8("graphics/field_effects/pics/emotion_double_exclamation.4bpp"); +static const u8 sEmotion_XGfx[] = INCBIN_U8("graphics/field_effects/pics/emote_x.4bpp"); static u8 (*const sDirectionalApproachDistanceFuncs[])(struct ObjectEvent *trainerObj, s16 range, s16 x, s16 y) = { @@ -136,6 +138,14 @@ static const struct SpriteFrameImage sSpriteImageTable_ExclamationQuestionMark[] { .data = sEmotion_QuestionMarkGfx, .size = sizeof(sEmotion_QuestionMarkGfx) + }, + { + .data = sEmotion_DoubleExclamationMarkGfx, + .size = sizeof(sEmotion_DoubleExclamationMarkGfx) + }, + { + .data = sEmotion_XGfx, + .size = sizeof(sEmotion_XGfx) } }; @@ -159,10 +169,25 @@ static const union AnimCmd sSpriteAnim_Icons2[] = ANIMCMD_END }; + +static const union AnimCmd sSpriteAnim_Icons3[] = +{ + ANIMCMD_FRAME(2, 60), + ANIMCMD_END +}; + +static const union AnimCmd sSpriteAnim_Icons4[] = +{ + ANIMCMD_FRAME(3, 60), + ANIMCMD_END +}; + static const union AnimCmd *const sSpriteAnimTable_Icons[] = { sSpriteAnim_Icons1, - sSpriteAnim_Icons2 + sSpriteAnim_Icons2, + sSpriteAnim_Icons3, + sSpriteAnim_Icons4 }; static const struct SpriteTemplate sSpriteTemplate_ExclamationQuestionMark = @@ -731,6 +756,27 @@ u8 FldEff_HeartIcon(void) return 0; } + +u8 FldEff_DoubleExclMarkIcon(void) +{ + u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); + + if (spriteId != MAX_SPRITES) + SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 2); + + return 0; +} + +u8 FldEff_XIcon(void) +{ + u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); + + if (spriteId != MAX_SPRITES) + SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 3); + + return 0; +} + static void SetIconSpriteData(struct Sprite *sprite, u16 fldEffId, u8 spriteAnimNum) { sprite->oam.priority = 1; diff --git a/src/vs_seeker.c b/src/vs_seeker.c new file mode 100644 index 0000000000..0b58b9bac3 --- /dev/null +++ b/src/vs_seeker.c @@ -0,0 +1,793 @@ +#include "global.h" +#include "task.h" +#include "event_object_movement.h" +#include "item_use.h" +#include "event_scripts.h" +#include "event_data.h" +#include "script.h" +#include "event_object_lock.h" +#include "field_specials.h" +#include "item.h" +#include "item_menu.h" +#include "field_effect.h" +#include "script_movement.h" +#include "battle.h" +#include "battle_setup.h" +#include "random.h" +#include "field_player_avatar.h" +#include "vs_seeker.h" +#include "menu.h" +#include "string_util.h" +#include "tv.h" +#include "malloc.h" +#include "field_screen_effect.h" +#include "gym_leader_rematch.h" +#include "sound.h" +#include "constants/event_object_movement.h" +#include "constants/event_objects.h" +#include "constants/items.h" +#include "constants/maps.h" +#include "constants/songs.h" +#include "constants/trainer_types.h" +#include "constants/field_effects.h" + +enum +{ + VSSEEKER_NOT_CHARGED, + VSSEEKER_NO_ONE_IN_RANGE, + VSSEEKER_CAN_USE, +}; + +typedef enum +{ + VSSEEKER_SINGLE_RESP_RAND, + VSSEEKER_SINGLE_RESP_NO, + VSSEEKER_SINGLE_RESP_YES +} VsSeekerSingleRespCode; + +typedef enum +{ + VSSEEKER_RESPONSE_NO_RESPONSE, + VSSEEKER_RESPONSE_UNFOUGHT_TRAINERS, + VSSEEKER_RESPONSE_FOUND_REMATCHES +} VsSeekerResponseCode; + +struct VsSeekerTrainerInfo +{ + const u8 *script; + u16 trainerIdx; + u8 localId; + u8 objectEventId; + s16 xCoord; + s16 yCoord; + u8 graphicsId; +}; + +struct VsSeekerStruct +{ + struct VsSeekerTrainerInfo trainerInfo[OBJECT_EVENTS_COUNT]; + u16 trainerIdxArray[OBJECT_EVENTS_COUNT]; + u8 runningBehaviourEtcArray[OBJECT_EVENTS_COUNT]; + u8 numRematchableTrainers; + u8 trainerHasNotYetBeenFought:1; + u8 trainerDoesNotWantRematch:1; + u8 trainerWantsRematch:1; + u8 responseCode:5; +}; + +// static declarations +static EWRAM_DATA struct VsSeekerStruct *sVsSeeker = NULL; + +static void VsSeekerResetInBagStepCounter(void); +static void VsSeekerResetChargingStepCounter(void); +static void Task_ResetObjectsRematchWantedState(u8 taskId); +static void ResetMovementOfRematchableTrainers(void); +static void Task_VsSeekerFrameCountdown(u8 taskId); +static void Task_VsSeeker_PlaySoundAndGetResponseCode(u8 taskId); +static void GatherNearbyTrainerInfo(void); +static void Task_VsSeeker_ShowResponseToPlayer(u8 taskId); +static bool8 CanUseVsSeeker(void); +static u8 GetVsSeekerResponseInArea(void); +static u8 GetResponseMovementTypeFromTrainerGraphicsId(u8 graphicsId); +static u16 GetTrainerFlagFromScript(const u8 * script); +static void ClearAllTrainerRematchStates(void); +static bool8 IsTrainerVisibleOnScreen(struct VsSeekerTrainerInfo * trainerInfo); +static u32 GetRematchableTrainerLocalId(void); +static void StartTrainerObjectMovementScript(struct VsSeekerTrainerInfo * trainerInfo, const u8 * script); +static u8 GetCurVsSeekerResponse(s32 vsSeekerIdx, u16 trainerIdx); +static void StartAllRespondantIdleMovements(void); +static bool8 ObjectEventIdIsSane(u8 objectEventId); +static u8 GetRandomFaceDirectionMovementType(); + +static const u8 sMovementScript_Wait48[] = { + MOVEMENT_ACTION_DELAY_16, + MOVEMENT_ACTION_DELAY_16, + MOVEMENT_ACTION_DELAY_16, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sMovementScript_TrainerUnfought[] = { + MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sMovementScript_TrainerNoRematch[] = { + MOVEMENT_ACTION_EMOTE_X, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sMovementScript_TrainerRematch[] = { + MOVEMENT_ACTION_WALK_IN_PLACE_FASTER_DOWN, + MOVEMENT_ACTION_EMOTE_DOUBLE_EXCL_MARK, + MOVEMENT_ACTION_STEP_END +}; + +static const u8 sFaceDirectionMovementTypeByFacingDirection[] = { + MOVEMENT_TYPE_FACE_DOWN, + MOVEMENT_TYPE_FACE_DOWN, + MOVEMENT_TYPE_FACE_UP, + MOVEMENT_TYPE_FACE_LEFT, + MOVEMENT_TYPE_FACE_RIGHT +}; + +void VsSeekerFreezeObjectsAfterChargeComplete(void) +{ + CreateTask(Task_ResetObjectsRematchWantedState, 80); +} + +#define tIsPlayerFrozen data[0] +#define tAreObjectsFrozen data[1] + +static void Task_ResetObjectsRematchWantedState(u8 taskId) +{ + struct Task *task = &gTasks[taskId]; + u32 i; + + if ((!task->tIsPlayerFrozen) && IsPlayerStandingStill()) + { + PlayerFreeze(); + task->tIsPlayerFrozen = TRUE; + } + + if (!task->tAreObjectsFrozen) + { + for (i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + if (!ObjectEventIdIsSane(i)) + continue; + + if (gObjectEvents[i].singleMovementActive) + return; + + FreezeObjectEvent(&gObjectEvents[i]); + } + } + + task->tAreObjectsFrozen = TRUE; + if (task->tIsPlayerFrozen) + { + DestroyTask(taskId); + StopPlayerAvatar(); + ScriptContext_Enable(); + } +} +#undef tIsPlayerFrozen +#undef tAreObjectsFrozen + +u16 VsSeekerConvertLocalIdToTableId(u16 localId) +{ + u32 localIdIndex = 0; + u32 trainerId = 0; + + for (localIdIndex = 0; localIdIndex < OBJECT_EVENTS_COUNT ; localIdIndex++) + { + if (sVsSeeker->trainerInfo[localIdIndex].localId == localId) + { + trainerId = sVsSeeker->trainerInfo[localIdIndex].trainerIdx; + return TrainerIdToRematchTableId(gRematchTable,trainerId); + } + } + return -1; +} + +void VsSeekerResetObjectMovementAfterChargeComplete(void) +{ + struct ObjectEventTemplate * templates = gSaveBlock1Ptr->objectEventTemplates; + u32 i; + u32 movementType; + u8 objEventId; + struct ObjectEvent * objectEvent; + + for (i = 0; i < gMapHeader.events->objectEventCount; i++) + { + if (templates[i].trainerType != TRAINER_TYPE_NORMAL + && templates[i].trainerType != TRAINER_TYPE_BURIED) + continue; + + if (templates[i].movementType != MOVEMENT_TYPE_ROTATE_CLOCKWISE) + continue; + + movementType = GetRandomFaceDirectionMovementType(); + TryGetObjectEventIdByLocalIdAndMap(templates[i].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objEventId); + objectEvent = &gObjectEvents[objEventId]; + + if (!ObjectEventIdIsSane(objEventId)) + continue; + + SetTrainerMovementType(objectEvent, movementType); + templates[i].movementType = movementType; + } +} + +bool8 UpdateVsSeekerStepCounter(void) +{ + u8 x = 0; + + if (!I_VS_SEEKER_CHARGING) return FALSE; + + if (CheckBagHasItem(ITEM_VS_SEEKER, 1)) + { + if ((gSaveBlock1Ptr->trainerRematchStepCounter & 0xFF) < VSSEEKER_RECHARGE_STEPS) + gSaveBlock1Ptr->trainerRematchStepCounter++; + } + + if (FlagGet(I_VS_SEEKER_CHARGING)) + { + if (((gSaveBlock1Ptr->trainerRematchStepCounter >> 8) & 0xFF) < VSSEEKER_RECHARGE_STEPS) + { + x = (((gSaveBlock1Ptr->trainerRematchStepCounter >> 8) & 0xFF) + 1); + gSaveBlock1Ptr->trainerRematchStepCounter = (gSaveBlock1Ptr->trainerRematchStepCounter & 0xFF) | (x << 8); + } + if (((gSaveBlock1Ptr->trainerRematchStepCounter >> 8) & 0xFF) == VSSEEKER_RECHARGE_STEPS) + { + FlagClear(I_VS_SEEKER_CHARGING); + VsSeekerResetChargingStepCounter(); + ClearAllTrainerRematchStates(); + return TRUE; + } + } + + return FALSE; +} + +void MapResetTrainerRematches(u16 mapGroup, u16 mapNum) +{ + if (!I_VS_SEEKER_CHARGING) return; + + FlagClear(I_VS_SEEKER_CHARGING); + VsSeekerResetChargingStepCounter(); + ClearAllTrainerRematchStates(); + ResetMovementOfRematchableTrainers(); +} + +static void ResetMovementOfRematchableTrainers(void) +{ + u32 i; + u8 movementType = 0; + + for (i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + struct ObjectEvent * objectEvent = &gObjectEvents[i]; + if (objectEvent->movementType != MOVEMENT_TYPE_ROTATE_CLOCKWISE) + continue; + + movementType = GetRandomFaceDirectionMovementType(); + + if (!objectEvent->active || gSprites[objectEvent->spriteId].data[0] != i) + continue; + + gSprites[objectEvent->spriteId].x2 = 0; + gSprites[objectEvent->spriteId].y2 = 0; + SetTrainerMovementType(objectEvent, movementType); + } +} + +static void VsSeekerResetInBagStepCounter(void) +{ + gSaveBlock1Ptr->trainerRematchStepCounter &= 0xFF00; +} + +static void VsSeekerResetChargingStepCounter(void) +{ + gSaveBlock1Ptr->trainerRematchStepCounter &= 0x00FF; +} + +void Task_InitVsSeekerAndCheckForTrainersOnScreen(u8 taskId) +{ + u32 i; + u32 respval; + + if (!I_VS_SEEKER_CHARGING) return; + + for (i = 0; i < 16; i++) + gTasks[taskId].data[i] = 0; + + sVsSeeker = AllocZeroed(sizeof(struct VsSeekerStruct)); + GatherNearbyTrainerInfo(); + respval = CanUseVsSeeker(); + if (respval == VSSEEKER_NOT_CHARGED) + { + Free(sVsSeeker); + DisplayItemMessageOnField(taskId, VSSeeker_Text_BatteryNotChargedNeedXSteps, Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker); + } + else if (respval == VSSEEKER_NO_ONE_IN_RANGE) + { + Free(sVsSeeker); + DisplayItemMessageOnField(taskId, VSSeeker_Text_NoTrainersWithinRange, Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker); + } + else if (respval == VSSEEKER_CAN_USE) + { + FieldEffectStart(FLDEFF_USE_VS_SEEKER); + gTasks[taskId].func = Task_VsSeekerFrameCountdown; + gTasks[taskId].data[0] = 15; + } +} + +static void Task_VsSeekerFrameCountdown(u8 taskId) +{ + if (--gTasks[taskId].data[0] == 0) + { + gTasks[taskId].func = Task_VsSeeker_PlaySoundAndGetResponseCode; + gTasks[taskId].data[1] = 16; + } +} + +static void Task_VsSeeker_PlaySoundAndGetResponseCode(u8 taskId) +{ + s16 * data = gTasks[taskId].data; + + if (data[2] != 2 && --data[1] == 0) + { + PlaySE(SE_CONTEST_MONS_TURN); + data[1] = 11; + data[2]++; + } + + if (!FieldEffectActiveListContains(FLDEFF_USE_VS_SEEKER)) + { + data[1] = 0; + data[2] = 0; + VsSeekerResetInBagStepCounter(); + sVsSeeker->responseCode = GetVsSeekerResponseInArea(); + ScriptMovement_StartObjectMovementScript(0xFF, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, sMovementScript_Wait48); + gTasks[taskId].func = Task_VsSeeker_ShowResponseToPlayer; + } +} + +static void GatherNearbyTrainerInfo(void) +{ + struct ObjectEventTemplate *templates = gSaveBlock1Ptr->objectEventTemplates; + u8 objectEventId = 0; + u8 vsSeekerObjectIdx = 0; + s32 objectEventIdx; + + for (objectEventIdx = 0; objectEventIdx < gMapHeader.events->objectEventCount; objectEventIdx++) + { + if (templates[objectEventIdx].trainerType != TRAINER_TYPE_NORMAL && templates[objectEventIdx].trainerType != TRAINER_TYPE_BURIED) + continue; + + sVsSeeker->trainerInfo[vsSeekerObjectIdx].script = templates[objectEventIdx].script; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].trainerIdx = GetTrainerFlagFromScript(templates[objectEventIdx].script); + sVsSeeker->trainerInfo[vsSeekerObjectIdx].localId = templates[objectEventIdx].localId; + TryGetObjectEventIdByLocalIdAndMap(templates[objectEventIdx].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objectEventId); + sVsSeeker->trainerInfo[vsSeekerObjectIdx].objectEventId = objectEventId; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].xCoord = gObjectEvents[objectEventId].currentCoords.x - 7; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].yCoord = gObjectEvents[objectEventId].currentCoords.y - 7; + sVsSeeker->trainerInfo[vsSeekerObjectIdx].graphicsId = templates[objectEventIdx].graphicsId; + vsSeekerObjectIdx++; + } + sVsSeeker->trainerInfo[vsSeekerObjectIdx].localId = 0xFF; +} + +static void Task_VsSeeker_ShowResponseToPlayer(u8 taskId) +{ + if (!ScriptMovement_IsObjectMovementFinished(0xFF, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup)) + return; + + if (sVsSeeker->responseCode == VSSEEKER_RESPONSE_NO_RESPONSE) + { + DisplayItemMessageOnField(taskId, VSSeeker_Text_TrainersNotReady, Task_ItemUse_CloseMessageBoxAndReturnToField_VsSeeker); + } + else + { + if (sVsSeeker->responseCode == VSSEEKER_RESPONSE_FOUND_REMATCHES) + StartAllRespondantIdleMovements(); + + ClearDialogWindowAndFrame(0, TRUE); + ScriptUnfreezeObjectEvents(); + UnlockPlayerFieldControls(); + DestroyTask(taskId); + } + Free(sVsSeeker); +} + +static u8 CanUseVsSeeker(void) +{ + u8 vsSeekerChargeSteps = gSaveBlock1Ptr->trainerRematchStepCounter; + + if ((vsSeekerChargeSteps == VSSEEKER_RECHARGE_STEPS) && (GetRematchableTrainerLocalId() == 0xFF)) + return VSSEEKER_NO_ONE_IN_RANGE; + + if (vsSeekerChargeSteps == VSSEEKER_RECHARGE_STEPS) + return VSSEEKER_CAN_USE; + + ConvertIntToDecimalStringN(gStringVar1, (VSSEEKER_RECHARGE_STEPS - vsSeekerChargeSteps), STR_CONV_MODE_LEFT_ALIGN, 3); + return VSSEEKER_NOT_CHARGED; +} + +static u8 GetVsSeekerResponseInArea(void) +{ + u16 trainerIdx = 0; + u8 response = 0, rematchTrainerIdx; + s32 vsSeekerIdx = 0, randomValue = 0; + + while (sVsSeeker->trainerInfo[vsSeekerIdx].localId != 0xFF) + { + if (!IsTrainerVisibleOnScreen(&sVsSeeker->trainerInfo[vsSeekerIdx])) + { + vsSeekerIdx++; + continue; + } + + trainerIdx = sVsSeeker->trainerInfo[vsSeekerIdx].trainerIdx; + if (!HasTrainerBeenFought(trainerIdx)) + { + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerUnfought); + sVsSeeker->trainerHasNotYetBeenFought = 1; + vsSeekerIdx++; + continue; + } + + rematchTrainerIdx = GetRematchTrainerIdFromTable(gRematchTable, trainerIdx); + if (rematchTrainerIdx == 0) + { + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerNoRematch); + sVsSeeker->trainerDoesNotWantRematch = 1; + } + else + { + randomValue = Random() % 100; // Even if it's overwritten below, it progresses the RNG. + response = GetCurVsSeekerResponse(vsSeekerIdx, trainerIdx); + + if (response == VSSEEKER_SINGLE_RESP_YES) + { + randomValue = 100; // Definitely yes + } + else if (response == VSSEEKER_SINGLE_RESP_NO) + { + randomValue = 0; // Definitely no + } + else if (randomValue < 30) + { + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerNoRematch); + sVsSeeker->trainerDoesNotWantRematch = 1; + } + else + { + gSaveBlock1Ptr->trainerRematches[VsSeekerConvertLocalIdToTableId(sVsSeeker->trainerInfo[vsSeekerIdx].localId)] = rematchTrainerIdx; + ShiftStillObjectEventCoords(&gObjectEvents[sVsSeeker->trainerInfo[vsSeekerIdx].objectEventId]); + StartTrainerObjectMovementScript(&sVsSeeker->trainerInfo[vsSeekerIdx], sMovementScript_TrainerRematch); + sVsSeeker->trainerIdxArray[sVsSeeker->numRematchableTrainers] = trainerIdx; + sVsSeeker->runningBehaviourEtcArray[sVsSeeker->numRematchableTrainers] = GetResponseMovementTypeFromTrainerGraphicsId(sVsSeeker->trainerInfo[vsSeekerIdx].graphicsId); + sVsSeeker->numRematchableTrainers++; + sVsSeeker->trainerWantsRematch = 1; + } + } + vsSeekerIdx++; + } + + if (sVsSeeker->trainerWantsRematch) + { + PlaySE(SE_PIN); + FlagSet(I_VS_SEEKER_CHARGING); + VsSeekerResetChargingStepCounter(); + return VSSEEKER_RESPONSE_FOUND_REMATCHES; + } + + if (sVsSeeker->trainerHasNotYetBeenFought) + return VSSEEKER_RESPONSE_UNFOUGHT_TRAINERS; + + return VSSEEKER_RESPONSE_NO_RESPONSE; +} + +void ClearRematchMovementByTrainerId(void) +{ + s32 i; + u8 objEventId = 0; + struct ObjectEventTemplate *objectEventTemplates = gSaveBlock1Ptr->objectEventTemplates; + struct ObjectEvent *objectEvent; + + int vsSeekerDataIdx = TrainerIdToRematchTableId(gRematchTable, gTrainerBattleOpponent_A); + + if (!I_VS_SEEKER_CHARGING) return; + + if (vsSeekerDataIdx == -1) + return; + + for (i = 0; i < gMapHeader.events->objectEventCount; i++) + { + if ((objectEventTemplates[i].trainerType != TRAINER_TYPE_NORMAL + && objectEventTemplates[i].trainerType != TRAINER_TYPE_BURIED) + || vsSeekerDataIdx != TrainerIdToRematchTableId(gRematchTable, GetTrainerFlagFromScript(objectEventTemplates[i].script))) + continue; + + TryGetObjectEventIdByLocalIdAndMap(objectEventTemplates[i].localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, &objEventId); + objectEvent = &gObjectEvents[objEventId]; + GetRandomFaceDirectionMovementType(&objectEventTemplates[i]); + TryOverrideTemplateCoordsForObjectEvent(objectEvent, sFaceDirectionMovementTypeByFacingDirection[objectEvent->facingDirection]); + + if (gSelectedObjectEvent == objEventId) + objectEvent->movementType = sFaceDirectionMovementTypeByFacingDirection[objectEvent->facingDirection]; + else + objectEvent->movementType = MOVEMENT_TYPE_FACE_DOWN; + } +} + +static u32 GetGameProgressFlags() +{ + const u32 gameProgressFlags[] = { + FLAG_VISITED_LAVARIDGE_TOWN, + FLAG_VISITED_FORTREE_CITY, + FLAG_SYS_GAME_CLEAR, + FLAG_DEFEATED_METEOR_FALLS_STEVEN + }; + u32 i = 0, numGameProgressFlags = 0; + u32 maxGameProgressFlags = ARRAY_COUNT(gameProgressFlags); + + for (i = 0; i < maxGameProgressFlags; i++) + { + if (FlagGet(gameProgressFlags[i])) + numGameProgressFlags++; + } + + return numGameProgressFlags; +} + +u16 GetRematchTrainerIdVSSeeker(u16 trainerId) +{ + u32 tableId = FirstBattleTrainerIdToRematchTableId(gRematchTable, trainerId); + u32 rematchTrainerIdx = GetGameProgressFlags(); + + if (!I_VS_SEEKER_CHARGING) return 0; + + while (!HasTrainerBeenFought(gRematchTable[tableId].trainerIds[rematchTrainerIdx-1])) + { + if (rematchTrainerIdx== 0) + break; + + rematchTrainerIdx--; + } + + return gRematchTable[tableId].trainerIds[rematchTrainerIdx]; +} + +static bool8 ObjectEventIdIsSane(u8 objectEventId) +{ + struct ObjectEvent *objectEvent = &gObjectEvents[objectEventId]; + + if (objectEvent->active && gMapHeader.events->objectEventCount >= objectEvent->localId && gSprites[objectEvent->spriteId].data[0] == objectEventId) + return TRUE; + return FALSE; +} + +static u8 GetRandomFaceDirectionMovementType() +{ + u16 randomFacingDirection = Random() % 4; + + switch (randomFacingDirection) + { + case 0: + return MOVEMENT_TYPE_FACE_UP; + case 1: + return MOVEMENT_TYPE_FACE_DOWN; + case 2: + return MOVEMENT_TYPE_FACE_LEFT; + case 3: + return MOVEMENT_TYPE_FACE_RIGHT; + default: + return MOVEMENT_TYPE_FACE_DOWN; + } +} + +static bool32 IsRegularLandTrainer(u8 graphicsId) +{ + u32 i; + u16 regularTrainersOnLand[] = + { + OBJ_EVENT_GFX_AQUA_MEMBER_F, + OBJ_EVENT_GFX_AQUA_MEMBER_M, + OBJ_EVENT_GFX_BEAUTY, + OBJ_EVENT_GFX_BLACK_BELT, + OBJ_EVENT_GFX_BOY_1, + OBJ_EVENT_GFX_BOY_2, + OBJ_EVENT_GFX_BOY_3, + OBJ_EVENT_GFX_BUG_CATCHER, + OBJ_EVENT_GFX_CAMPER, + OBJ_EVENT_GFX_CYCLING_TRIATHLETE_F, + OBJ_EVENT_GFX_CYCLING_TRIATHLETE_M, + OBJ_EVENT_GFX_EXPERT_F, + OBJ_EVENT_GFX_EXPERT_M, + OBJ_EVENT_GFX_FAT_MAN, + OBJ_EVENT_GFX_FISHERMAN, + OBJ_EVENT_GFX_GENTLEMAN, + OBJ_EVENT_GFX_GIRL_1, + OBJ_EVENT_GFX_GIRL_2, + OBJ_EVENT_GFX_GIRL_3, + OBJ_EVENT_GFX_HEX_MANIAC, + OBJ_EVENT_GFX_HIKER, + OBJ_EVENT_GFX_LASS, + OBJ_EVENT_GFX_LITTLE_BOY, + OBJ_EVENT_GFX_LITTLE_GIRL, + OBJ_EVENT_GFX_MAGMA_MEMBER_F, + OBJ_EVENT_GFX_MAGMA_MEMBER_M, + OBJ_EVENT_GFX_MAN_3, + OBJ_EVENT_GFX_MAN_4, + OBJ_EVENT_GFX_MAN_5, + OBJ_EVENT_GFX_MANIAC, + OBJ_EVENT_GFX_NINJA_BOY, + OBJ_EVENT_GFX_PICNICKER, + OBJ_EVENT_GFX_POKEFAN_F, + OBJ_EVENT_GFX_POKEFAN_M, + OBJ_EVENT_GFX_PSYCHIC_M, + OBJ_EVENT_GFX_RICH_BOY, + OBJ_EVENT_GFX_RUNNING_TRIATHLETE_F, + OBJ_EVENT_GFX_RUNNING_TRIATHLETE_M, + OBJ_EVENT_GFX_SAILOR, + OBJ_EVENT_GFX_SCHOOL_KID_M, + OBJ_EVENT_GFX_TUBER_F, + OBJ_EVENT_GFX_TUBER_M, + OBJ_EVENT_GFX_TWIN, + OBJ_EVENT_GFX_WOMAN_1, + OBJ_EVENT_GFX_WOMAN_2, + OBJ_EVENT_GFX_WOMAN_4, + OBJ_EVENT_GFX_WOMAN_5, + OBJ_EVENT_GFX_YOUNGSTER + }; + + for (i = 0; i < ARRAY_COUNT(regularTrainersOnLand); i++) + { + if (graphicsId == regularTrainersOnLand[i]) + return TRUE; + } + return FALSE; +} + +static bool32 IsRegularWaterTrainer(u8 graphicsId) +{ + u32 i; + u16 regularTrainersInWater[] = + { + OBJ_EVENT_GFX_SWIMMER_F, + OBJ_EVENT_GFX_SWIMMER_M, + OBJ_EVENT_GFX_TUBER_M_SWIMMING + }; + + for (i = 0; i < ARRAY_COUNT(regularTrainersInWater); i++) + { + if (graphicsId == regularTrainersInWater[i]) + return TRUE; + } + return FALSE; +} + +static u8 GetResponseMovementTypeFromTrainerGraphicsId(u8 graphicsId) +{ + if (IsRegularLandTrainer(graphicsId) || IsRegularWaterTrainer(graphicsId)) + return MOVEMENT_TYPE_ROTATE_CLOCKWISE; + + return MOVEMENT_TYPE_FACE_DOWN; +} + +static u16 GetTrainerFlagFromScript(const u8 *script) + /* + * The trainer flag is a little-endian short located +2 from + * the script pointer, assuming the trainerbattle command is + * first in the script. Because scripts are unaligned, and + * because the ARM processor requires shorts to be 16-bit + * aligned, this function needs to perform explicit bitwise + * operations to get the correct flag. + * + * 5c XX YY ZZ ... + * -- -- + */ +{ + u16 trainerFlag; + + script += 2; + trainerFlag = script[0]; + trainerFlag |= script[1] << 8; + return trainerFlag; +} + +static void ClearAllTrainerRematchStates(void) +{ + u32 i; + + if (!CheckBagHasItem(ITEM_VS_SEEKER, 1)) + return; + + for (i = 0; i < ARRAY_COUNT(gSaveBlock1Ptr->trainerRematches); i++) + gSaveBlock1Ptr->trainerRematches[i] = 0; +} + +static bool8 IsTrainerVisibleOnScreen(struct VsSeekerTrainerInfo * trainerInfo) +{ + s16 x; + s16 y; + + PlayerGetDestCoords(&x, &y); + x -= 7; + y -= 7; + + if ( x - 7 <= trainerInfo->xCoord + && x + 7 >= trainerInfo->xCoord + && y - 5 <= trainerInfo->yCoord + && y + 5 >= trainerInfo->yCoord + && ObjectEventIdIsSane(trainerInfo->objectEventId) == 1) + return TRUE; + return FALSE; +} + +static u32 GetRematchableTrainerLocalId(void) +{ + u32 i; + + for (i = 0; sVsSeeker->trainerInfo[i].localId != 0xFF; i++) + { + if (IsTrainerVisibleOnScreen(&sVsSeeker->trainerInfo[i]) == 1) + { + if (HasTrainerBeenFought(sVsSeeker->trainerInfo[i].trainerIdx) != 1 || GetRematchTrainerIdFromTable(gRematchTable, sVsSeeker->trainerInfo[i].trainerIdx)) + return sVsSeeker->trainerInfo[i].localId; + } + } + + return 0xFF; +} + +static void StartTrainerObjectMovementScript(struct VsSeekerTrainerInfo * trainerInfo, const u8 * script) +{ + UnfreezeObjectEvent(&gObjectEvents[trainerInfo->objectEventId]); + ScriptMovement_StartObjectMovementScript(trainerInfo->localId, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup, script); +} + +static u8 GetCurVsSeekerResponse(s32 vsSeekerIdx, u16 trainerIdx) +{ + s32 i; + s32 j; + + for (i = 0; i < vsSeekerIdx; i++) + { + if (IsTrainerVisibleOnScreen(&sVsSeeker->trainerInfo[i]) != 1 || sVsSeeker->trainerInfo[i].trainerIdx != trainerIdx) + continue; + + for (j = 0; j < sVsSeeker->numRematchableTrainers; j++) + { + if (sVsSeeker->trainerIdxArray[j] == sVsSeeker->trainerInfo[i].trainerIdx) + return VSSEEKER_SINGLE_RESP_YES; + } + return VSSEEKER_SINGLE_RESP_NO; + } + return VSSEEKER_SINGLE_RESP_RAND; +} + +static void StartAllRespondantIdleMovements(void) +{ + s32 i; + s32 j; + + for (i = 0; i < sVsSeeker->numRematchableTrainers; i++) + { + for (j = 0; sVsSeeker->trainerInfo[j].localId != 0xFF; j++) + { + if (sVsSeeker->trainerInfo[j].trainerIdx == sVsSeeker->trainerIdxArray[i]) + { + struct ObjectEvent *objectEvent = &gObjectEvents[sVsSeeker->trainerInfo[j].objectEventId]; + + if (ObjectEventIdIsSane(sVsSeeker->trainerInfo[j].objectEventId) == 1) + SetTrainerMovementType(objectEvent, sVsSeeker->runningBehaviourEtcArray[i]); + TryOverrideTemplateCoordsForObjectEvent(objectEvent, sVsSeeker->runningBehaviourEtcArray[i]); + gSaveBlock1Ptr->trainerRematches[VsSeekerConvertLocalIdToTableId(sVsSeeker->trainerInfo[j].localId)] = GetRematchTrainerIdFromTable(gRematchTable, sVsSeeker->trainerInfo[j].trainerIdx); + } + } + } +}