Port pokefirered's Vs. Seeker to pokeemerald (#3256)

* First version of Vs. Seeker

* Update movement.inc

98f7e9978d (r1306721924)

* Update field_effect_scripts.s

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722004

* Update field_effect_scripts.s

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722024

* Update item_use.h

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722401

* Update movement_action_func_tables.h

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722828

* Update event_object_movement.c

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306722887

* Update overworld.c

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306723396

* Update vs_seeker.h

https://github.com/rh-hideout/pokeemerald-expansion/pull/3256/files#r1306724158

* Update vs_seeker.c

Addressed some cleanup comments from SBird

* Update UpdateRandomTrainerRematches
Fixed typo in ClearAllTrainerRematchStates
Fixed types in GetRematchableTrainerLocalId

* Updated UseVsSeekerEffect_2

* Updated UseVsSeekerEffect_3

* Updated UseVsSeekerEffect_4

* Fixed bug that allowed Vs Seeker to be used indoors in correct places
Moved VsSeeker function declarations into header

* Refactored FieldUseFunc_VsSeeker

* Added curly braces to else case in FieldUseFunc_VsSeeker

* renamed data[x] in Task_ResetObjectsRematchWantedState

* Refactored Task_ResetObjectsRematchWantedState

* Refactored VsSeekerResetObjectMovementAfterChargeComplete

* Refactored ResetMovementOfRematchableTrainers

* Refactored GatherNearbyTrainerInfo

* Refactored Task_VsSeeker_3

* CanUseVsSeeker

* Refactored GetVsSeekerResponseInArea

* GetCurVsSeekerResponse refactored

* Cleaned up GetTrainerFlagFromScript

* Gave sensible names to Task_VsSeeker

* Fixed two bugs where player would not have the right gfx state after using VsSeeker on a Bike or Underwater

* Renamed UseVsSeeker Functions

* Added I_VS_SEEKER_CHARGING to make Vs. Seeker broken until flag is assigned
Removed extra VsSeeker animation code

* Addressed PR feedback

* Fixed issue with building non-modern

* Refactored GetRunningBehaviorFromGraphicsId and renamed to GetResponseMovementTypeFromTrainerGraphicsId

* Addresses Lunos's PR feedback: https://github.com/rh-hideout/pokeemerald-expansion/pull/3256\#pullrequestreview-1623547850
Removed the check to see if a map was not indoors to improve readability
Made IsValidLocationForVsSeeker into a static function

* Added changes in response to Jasper's feedback
https://github.com/rh-hideout/pokeemerald-expansion/pull/3256\#pullrequestreview-1725276522

* Updated with Edu's discord feedback https://discord.com/channels/419213663107416084/1135040810082123907/1176872015085453392

* Removed ifdef tags around the repo unless needed
b5dc744ced
This commit is contained in:
psf 2023-11-26 09:58:43 -08:00 committed by GitHub
parent 493478e94b
commit fe16a2cdee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1155 additions and 23 deletions

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -540,3 +540,5 @@ gSpecials::
def_special GetSprayId
def_special GetLastUsedSprayType
def_special TrySkyBattle
def_special VsSeekerResetObjectMovementAfterChargeComplete
def_special VsSeekerFreezeObjectsAfterChargeComplete

View file

@ -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}$"

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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

14
include/vs_seeker.h Normal file
View file

@ -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

View file

@ -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();
}

View file

@ -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] =

View file

@ -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,
};

View file

@ -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;
}

View file

@ -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)

View file

@ -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));
}

View file

@ -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] =

View file

@ -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

View file

@ -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();

View file

@ -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;

793
src/vs_seeker.c Normal file
View file

@ -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);
}
}
}
}