diff --git a/asm/macros/event.inc b/asm/macros/event.inc index a7f4cbf04994..cfdff070d26d 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2815,6 +2815,21 @@ .byte \status .endm + @ Makes the player and selected object (if there is one) face one another. + .macro facetogether + callnative ScriptFaceEachOther + .endm + + @ Starts an Overworld Wild Encounter with a selected object, if possible. + .macro tryoverworldwildencounter + callnative StartWildBattleWithOWE + .endm + + @ Start a scripted approach for an overworld wild encounter towards the player. + .macro overworldwildencounterapproach + callnative OWEApproachForBattle + .endm + @ FRLG diff --git a/data/event_scripts.s b/data/event_scripts.s index ae21d4b67cab..1594cc0dbbf7 100644 --- a/data/event_scripts.s +++ b/data/event_scripts.s @@ -1736,3 +1736,4 @@ EventScript_PalletTown_PlayersHouse_2F_TurnOnPC:: .include "data/scripts/dexnav.inc" .include "data/scripts/battle_frontier.inc" .include "data/scripts/apricorn_tree.inc" + .include "data/scripts/wild_encounter.inc" diff --git a/data/field_effect_scripts.s b/data/field_effect_scripts.s index e50a5db3a385..a35fa8a8d4e4 100644 --- a/data/field_effect_scripts.s +++ b/data/field_effect_scripts.s @@ -87,6 +87,7 @@ gFieldEffectScriptPointers:: .4byte gFldEffScript_SmileyFaceIcon @ FLDEFF_SMILEY_FACE_ICON .4byte gFieldEffectScript_HallOfFameRecordFrlg @ FLDEFF_HALL_OF_FAME_RECORD_FRLG .4byte gFldEffScript_PhotoFlash @ FLDEFF_PHOTO_FLASH + .4byte gFieldEffectScript_OWE_SpawnAnim @ FLDEFF_OW_ENCOUNTER_SPAWN_ANIM gFieldEffectScript_ExclamationMarkIcon1:: field_eff_callnative FldEff_ExclamationMarkIcon @@ -415,3 +416,7 @@ gFieldEffectScript_HallOfFameRecordFrlg:: gFldEffScript_PhotoFlash:: field_eff_callnative FldEff_PhotoFlash field_eff_end + +gFieldEffectScript_OWE_SpawnAnim:: + field_eff_callnative FldEff_OWE_SpawnAnim + field_eff_end diff --git a/data/scripts/wild_encounter.inc b/data/scripts/wild_encounter.inc new file mode 100644 index 000000000000..eddf3340b5fb --- /dev/null +++ b/data/scripts/wild_encounter.inc @@ -0,0 +1,11 @@ +InteractWithOverworldWildEncounter:: + lock + overworldwildencounterapproach + applymovement VAR_LAST_TALKED, Common_Movement_ExclamationMark + facetogether + playmoncry VAR_0x8004, CRY_MODE_DOUBLES + waitmoncry + tryoverworldwildencounter + waitstate + end + diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 178063bd22a5..f55624c65a53 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -47,6 +47,7 @@ - [Struct Pokemon Generation](tutorials/mon_generation.md) - [How to use FRLG](tutorials/how_to_frlg.md) - [How to delete vanilla maps](tutorials/how_to_delete_vanilla_maps.md) + - [How to use Overworld Wild Encounters](tutorials/how_to_overworld_wild_encounters.md) - [Changelog](./CHANGELOG.md) - [1.14.x]() - [Version 1.14.3](changelogs/1.14.x/1.14.3.md) diff --git a/docs/tutorials/how_to_overworld_wild_encounters.md b/docs/tutorials/how_to_overworld_wild_encounters.md new file mode 100644 index 000000000000..210ff9c131fc --- /dev/null +++ b/docs/tutorials/how_to_overworld_wild_encounters.md @@ -0,0 +1,102 @@ +# Overworld Wild Encounters Tutorial +## OWE Spawning +Overworld Wild Encounters (OWEs), refer to the wild encounters that can be seen as object events in the overworld, prior to engaging in battle with them. They use either the `WILD_AREA_LAND` or `WILD_AREA_WATER` encounter tables by default. OWEs come in two types, Generated or Manual. + +### Generated OWEs +Generated OWEs are spawned automatically when `WE_OW_ENCOUNTERS` is set to `TRUE`, being spawned on a random encounter tile near the player, with the encounter table used dependant on it. These are considered low priority OWEs, and automatically populate a level, species, gender and shinyness exactly how a vanilla wild encounter would, or can even be a special spawn, but more on those later. +> Setting `OW_GFX_COMPRESS` to `FALSE` will free more space in VRAM, allowing for more large OWEs to spawn, and reducing the chance of running into a tiles error when trying to spawn Generated OWEs. + +### Manual OWEs +Manual OWEs are created by the developer as any other object event would be and are defined by having the `.trainerType` set to `TRAINER_TYPE_OW_WILD_ENCOUNTER`. These can be fully customised by the developer, with the level, species, gender and shinyness all able to be specified. The level can be set by filling the desired value in the `trainerRange_berryTreeId`. The latter three are specified by the `graphicsId` of the object, for example; +- `OBJ_EVENT_GFX_SPECIES(BULBASAUR)` will produce a male, non-shiny Bulbasaur. +- `OBJ_EVENT_GFX_SPECIES_SHINY(CHARMANDER)` will produce a male, shiny Charmander. +- `OBJ_EVENT_GFX_SPECIES_FEMALE(SQUIRTLE)` will produce a female, non-shiny Squirtle. +- `OBJ_EVENT_GFX_SPECIES_SHINY_FEMALE(PIKACHU)` will produce a female, shiny Pikachu. + +However, Manual OWEs do not have to be defined fully, leaving any of the level, species, gender, shinyness or script unspecified will revert to default behaviours. If left blank, the level or species will be generated from the relevant encounter table. Leaving the shinyness blank will revert to default shiny odds, although this can still be affected by `P_FLAG_FORCE_SHINY` and `P_FLAG_FORCE_NO_SHINY`. Setting the `OBJ_EVENT_MON` bit of the `graphicsId`, but not the `OBJ_EVENT_MON_FEMALE` will result in a male encounter, setting both will result in a female encounter, as seen above, but setting neither will randomise the gender based on species. A species can be defined with a random gender by just using the species define. A specific script can be specified, but if not the default OWE encounter script will be used. +Assuming the following `graphicsId` have `.trainerType` set to `TRAINER_TYPE_OW_WILD_ENCOUNTER`; +- `SPECIES_EEVEE` will result in an Eevee with a randomised level, gender and shinyness, using the default encounter script. +- `OBJ_EVENT_GFX_SPECIES(NONE)` will result in a male randomised species of randomised level, gender and shinyness, using the default encounter script. +- `OBJ_EVENT_GFX_SPECIES_SHINY_FEMALE(NONE)` will result in a female, shiny randomised species with randomised level and gender, using the default encounter script. + +As level and species are potentially taken from the Wild Encounter Header, there is an `assertf` to let developers know when an invalid value is used. If the resultant level is invalid, it will be set to `MIN_LEVEL` (1). If the species is invalid, a replacement object will be created using `OBJ_EVENT_GFX_BOY_1`, this will not be an OWE of any kind. + +No matter how much of a Manual OWE is defined, it is considered a high priority OWE, and treated as a regular object event in all ways other than ones outlined above. They will always spawn, regardless of level of abilties of player Pokémon. However, they cannot be special spawns. + +> Flags are set when removed. + +### Special Spawns +Special spawns can be one of three types, in decreasing priority: A Roamer, Feebas, or Mass Outbreak Encounter. Generated OWEs can have any of these, however, Manual OWEs can only have the Feebas Special Spawn. These work exactly as they would normally; +- If a Roamer is on the route and is able to spawn, then it may appear where a Generated OWE would. +- If any OWE spawns on a tile where a Feebas special fishing spot is, it may appear is a Feebas (only if `WE_OWE_FEEBAS_SPOTS` is TRUE). +- If a Generated OWE spawns on a route that has a mass outbreak occuring, it may spawn as an encounter from that mass outbreak. +> OWE_MAX_ROAMERS + +### Restricted Despawning +There are three configs that can be used to restrict the despawning of Generated OWEs. + +- If `WE_OWE_PREVENT_SHINY_DESPAWN` is set to `TRUE`, any Generated OWE that spawns as shiny will not be despawned if they go outside of the viewable area. They can still be despawned off-screen if the player goes to another map. This config also ensures that shiny Generated OWEs will never be despawned and replaced if `WE_OWE_SPAWN_REPLACEMENT` is `TRUE`. +- If `WE_OWE_PREVENT_FEEBAS_DESPAWN` is set to `TRUE`, any Generated OWE that is a Feebas spawned from a Feebas fishing spot will follow the same rules as `WE_OWE_PREVENT_SHINY_DESPAWN` above. +- If `WE_OWE_DESPAWN_ON_ENTER_TOWN` is set to `TRUE`, all Generated OWEs will be instantly despawned upon the player crossing a map connection into a map with a map type of either `MAP_TYPE_TOWN` or `MAP_TYPE_CITY`, like what happens in Scarlet/Violet. + +None of these three configs can prevent the forceful despawning of OWEs to free up limited resources, as is explained in the next section. + + +## High Priority and Low Priority OWEs +Low Priority OWEs may not be spawned or even be destroyed in certain situations. There are palettes and object tiles checks to prevent these from spawning if it would fail, as well as similar checks for number of event objects, palettes and object tiles. These checks will despawn the oldest of Low Priority OWEs when other objects event are attempting to be spawned and Low Priority OWEs are using these resources. Low Priority OWEs may also be destroyed by NPC object events colliding with them due to their movements or the OWE being in the way of a trainer interaction. High priority OWEs are treated as regular objects and will not be destroyed in the ways outlined above, but may cause the destruction of Generated OWEs and will not face spawning restrictions. +These despawn conditions will overwrite the restrictive despawns mentioned above. + +## Encountering an OWE +Collision between Player and OWE or Interacting with one. Can also interact with an OWE in the water even when the player is not. +### Encounter Types +## Repel and Lure Behaviours + +## OWE Behaviour Types +The behaviors that these OWEs have is set up to be customizable for each individual species. By default, every species is set to `OWE_IGNORE_PLAYER`. To add a different behavior to a species, find their species struct in their `gen_X_families.h` file in the `src/data/pokemon/species_info` folder and add a `.overworldEncounterBehavior = ` to it. `` should be replaced with the behavior you want them to use. For example, if I wanted to add the `OWE_FLEE_PLAYER_NORMAL` behavior to Mudkip I would open `gen_3_families.h`, find the struct for `SPECIES_MUDKIP`, and add `.overworldEncounterBehavior = OWE_FLEE_PLAYER_NORMAL` to the end of it like so: +```diff + .eggMoveLearnset = sMudkipEggMoveLearnset, + .evolutions = EVOLUTION({EVO_LEVEL, 16, SPECIES_MARSHTOMP}), ++ .overworldEncounterBehavior = OWE_FLEE_PLAYER_NORMAL + }, + + [SPECIES_MARSHTOMP] = + { + .baseHP = 70, +``` + +The behaviors themselves are defined in `src/data/pokemon/wild_encounter_overworld_behavior.h`. These are the customizable parameters: +- `movementType` is the movement type you want the object event to have. More on these in the next section. +- `viewDistance` is the number of tiles away the mon is able to notice the player in the cardinal directions (similar to the sight distance of trainers). +- `viewWidth` is the total width of the area in which the mon will notice the player. For example, if `viewWidth` is set to `3`, the mon will be able to detect the player if they are within 1 tile of either side of the line of sight. +- `activeDistance` is the max distance away from the mon that the player can be before the mon loses track of them and goes back to wandering. +- `idleSpeed` is the speed at which the mon will take a step while wandering (player is not noticed). This must be one of the values in `enum SpeedOWE`. +- `activeSpeed` is the speed at which the mon will take a step while active (player has been noticed). This must be one of the values in `enum SpeedOWE`. + +If any of these parameters are not defined, they will be automatically assigned the value of `0`. + +A small number of premade behaviors have been provided and are ready to use. You can add as many new custom behaviors as you like, but make sure to add each behavior to `enum OverworldWildEncounterBehaviors`, making sure that `OWE_SPECIES_BEHAVIOR_COUNT` is always at the end. + +The same behavior can be used for multiple different species. + +## OWE Movements +While OWE objects can be given any movement type, there are several different custom movement types that were made specifically for OWEs: +- The basic one is `MOVEMENT_TYPE_WANDER_AROUND_OWE`. All of the other OWE movement types start with this behavior and differentiate into other behavior when they notice the player. Similar to `MOVEMENT_TYPE_WANDER_AROUND`, the object will take steps in random directions at random intervals. The main difference here is that the object will never turn more than 90 degrees from their current facing direction when taking a step. OWEs with this movement type will completely ignore the player until they are directly collided/interacted with. +- `MOVEMENT_TYPE_CHASE_PLAYER_OWE` will switch the OWE to chasing down the player when it notices the player. The OWE is still blocked by collision, but does have the intelligence to walk around small obstacles. They will go back to wandering if the player goes outside of their sight radius. +- `MOVEMENT_TYPE_FLEE_PLAYER_OWE` will switch the OWE to fleeing from the player when it notices the player. They use the same pathfinding logic as `MOVEMENT_TYPE_CHASE_PLAYER_OWE`, but in the opposite direction from the player. They will go back to wandering if the player goes outside of their sight radius. If `WE_OWE_FLEE_DESPAWN` is set to `TRUE`, the fleeing OWE will despawn if it is unable to take a step for a short amount of time (ie, if they are cornered). +- `MOVEMENT_TYPE_WATCH_PLAYER_OWE` will switch the OWE to stand in place and always face in the direction of the player's location when the player is noticed by it. They will go back to wandering if the player goes outside of their sight radius. +- `MOVEMENT_TYPE_APPROACH_PLAYER_OWE` will switch the OWE to approach the player as if curious when it notices the player. The OWE will try to keep a one tile gap between itself and the player. They may also occasionally do an excited hop. They will go back to wandering if the player goes outside of their sight radius. +- `MOVEMENT_TYPE_DESPAWN_OWE` will make the OWE do a very brief animation of surprise and then instantly despawn when it notices the player. + +### Restricted Movements +### How Data is Saved +``` +struct ObjectEvent +{ + … + u8 trainerRange_berryTreeId; // Also stores level for Overworld Encounters. + … + u8 directionSequenceIndex; // Also stores roamer status for Overworld Encounters. + u8 playerCopyableMovement; // COPY_MOVE_* Also stores age for Overworld Encounters. + … +} +``` diff --git a/graphics/field_effects/pics/shiny_sparkle.png b/graphics/field_effects/pics/shiny_sparkle.png new file mode 100644 index 000000000000..537a5d6a5f2e Binary files /dev/null and b/graphics/field_effects/pics/shiny_sparkle.png differ diff --git a/include/battle_pyramid.h b/include/battle_pyramid.h index d55d8d2e9399..4b0a1bbfe678 100644 --- a/include/battle_pyramid.h +++ b/include/battle_pyramid.h @@ -7,7 +7,7 @@ void CallBattlePyramidFunction(void); u16 LocalIdToPyramidTrainerId(u8 localId); bool8 GetBattlePyramidTrainerFlag(u8 eventId); void MarkApproachingPyramidTrainersAsBattled(void); -void GenerateBattlePyramidWildMon(void); +void GenerateBattlePyramidWildMon(u32 forceSpecies); u8 GetPyramidRunMultiplier(void); u8 CurrentBattlePyramidLocation(void); bool8 InBattlePyramid_(void); diff --git a/include/config/overworld.h b/include/config/overworld.h index 1fe629d19d44..3d49352b5f0e 100644 --- a/include/config/overworld.h +++ b/include/config/overworld.h @@ -49,14 +49,14 @@ // Overworld Pokémon #define OW_POKEMON_OBJECT_EVENTS TRUE // Adds Object Event fields for every species. Can be used for NPCs using the OBJ_EVENT_GFX_SPECIES macro (eg. OBJ_EVENT_GFX_SPECIES(BULBASAUR)) #define OW_SUBSTITUTE_PLACEHOLDER TRUE // Use a substitute OW for Pokémon that are missing overworld sprites -#define OW_LARGE_OW_SUPPORT TRUE // If true, adds a small amount of overhead to OW code so that large (48x48, 64x64) OWs will display correctly under bridges, etc. +#define OW_LARGE_OW_SUPPORT TRUE // If TRUE, adds a small amount of overhead to OW code so that large (48x48, 64x64) OWs will display correctly under bridges, etc. #define OW_PKMN_OBJECTS_SHARE_PALETTES FALSE // [WIP!! NOT ALL PALETTES HAVE BEEN ADJUSTED FOR THIS!!] If TRUE, follower palettes are taken from battle sprites. #define OW_GFX_COMPRESS TRUE // Adds support for compressed OW graphics, (Also compresses pokemon follower graphics). // IMPORTANT: Gfx are loaded into VRAM to avoid continous decompression. If you require more VRAM or want to use a lot of overworld Pokémon at once, you should disable this config. // Compressed gfx are incompatible with non-power-of-two sprite sizes: // (You should not use 48x48 sprites/tables for compressed gfx) // 16x32, 32x32, 64x64 etc are fine -#define OW_MON_WANDER_WALK TRUE // If true, OW pokemon with MOVEMENT_TYPE_WANDER will walk-in-place in between steps. +#define OW_MON_WANDER_WALK TRUE // If TRUE, OW Pokémon with MOVEMENT_TYPE_WANDER will walk-in-place in between steps. // Follower Pokémon #define OW_FOLLOWERS_ENABLED FALSE // Enables follower Pokémon, HGSS style. Requires OW_POKEMON_OBJECT_EVENTS. Note that additional scripting may be required for them to be fully supported! #define OW_FOLLOWERS_BOBBING TRUE // If TRUE, follower Pokémon will bob up and down during their idle & walking animations @@ -148,4 +148,15 @@ // Trainer Rematches #define OW_REMATCH_BADGE_COUNT 5 // Number of badges necessary before the match call or vs seeker features allow rematches +// Ambient Cries +// Constants +#define OW_AMBIENT_CRIES_NONE 0 // Do not play ambient cries. +#define OW_AMBIENT_CRIES_VANILLA 1 // Play ambient cries taken from encounter tables, as in vanilla. +#define OW_AMBIENT_CRIES_OWE_PRIORITY 2 // Play ambient cries based on active Overworld Wild Encounters, reverting to vanilla cries if none are present. +#define OW_AMBIENT_CRIES_OWE_ONLY 3 // Play ambient cries based on active Overworld Wild Encounters only. + // Overworld Wild Encounters will play ambient cries based on their location relative to the player. + +// Configuration +#define OW_AMBIENT_CRIES OW_AMBIENT_CRIES_VANILLA // Selects how ambient cries are played, if at all. As in vanilla, no matter what is chosen, cries will not play if the player is not on a map with Land or Water encounter tables. + #endif // GUARD_CONFIG_OVERWORLD_H diff --git a/include/config/wild_encounter.h b/include/config/wild_encounter.h new file mode 100644 index 000000000000..9dcaca345e08 --- /dev/null +++ b/include/config/wild_encounter.h @@ -0,0 +1,29 @@ +#ifndef GUARD_CONFIG_WILD_ENCOUNTER_OW_H +#define GUARD_CONFIG_WILD_ENCOUNTER_OW_H + +// Vanilla +#define WE_VANILLA_RANDOM TRUE // If TRUE, Pokémon can randomly spawn on tiles that can trigger wild encounters, as in vanilla. + +// Overworld Wild Encounters (OWEs) +#define WE_OW_ENCOUNTERS FALSE // If TRUE, OW Pokémon can spawn as Overworld Wild Encounters on the current map. Requires OW_POKEMON_OBJECT_EVENTS. + // If WE_OW_ENCOUNTERS is TRUE, it is recommended that OW_GFX_COMPRESS be set to FALSE to prevent VRAM issues. +#define WE_OWE_FLAG_DISABLED 0 // Replace 0 with a flag to use it to enable/disable generated OWEs. +#define WE_OWE_BATTLE_PIKE TRUE // If TRUE, OWEs can spawn in the Battle Pike, if FALSE random encounters will be enabled instead. Requires WE_OW_ENCOUNTERS to be TRUE. +#define WE_OWE_BATTLE_PYRAMID TRUE // If TRUE, OWEs can spawn in the Battle Pyramid, if FALSE random encounters will be enabled instead. Requires WE_OW_ENCOUNTERS to be TRUE. +#define WE_OWE_RESTRICT_METATILE TRUE // If TRUE, OWEs will stay within tiles with the same encounter metatile behavior as the one it is currently on, if any. +#define WE_OWE_RESTRICT_MAP TRUE // If TRUE, OWEs will stay within their current map bounds. +#define WE_OWE_UNRESTRICT_SIGHT FALSE // If TRUE, OWEs will ignore all movement restrictions when they can see the player. +#define WE_OWE_SPAWN_REPLACEMENT FALSE // If TRUE, the oldest OWE objects will despawn after a short time and be replaced with a new spawn if possible. +#define WE_OWE_FLEE_DESPAWN TRUE // If TRUE, a fleeing OWE will despawn if it is unable to take a step for a short time. +#define WE_OWE_SHINY_SPARKLE FALSE // If TRUE, shiny OWEs will spawn with a sparkle animation and play the shiny sound effect. +#define WE_OWE_FEEBAS_SPOTS FALSE // If TRUE, any spot that could result in a Feebas fishing encounter can spawn a Feebas OWE. +#define WE_OWE_DESPAWN_SOUND FALSE // If TRUE, plays SE_FLEE when an OWE despawns. +#define WE_OWE_APPROACH_FOR_BATTLE TRUE // If TRUE, OWEs will take steps to be right next to the player before the battle starts. +#define WE_OWE_PREVENT_SHINY_DESPAWN FALSE // If TRUE, shiny OWEs will not be despawned when off-screen if on the same map as the player, or be replaced if WE_OWE_SPAWN_REPLACEMENT is TRUE. +#define WE_OWE_PREVENT_FEEBAS_DESPAWN FALSE // If TRUE, Feebas OWEs spawned from special Feebas fishing spots (when WE_OWE_FEEBAS_SPOTS is TRUE) will not be despawned when off-screen if on the same map as the player, or be replaced if WE_OWE_SPAWN_REPLACEMENT is TRUE. +#define WE_OWE_DESPAWN_ON_ENTER_TOWN TRUE // If TRUE, despawns all OWEs upon entering a city (MAP_TYPE_CITY) or town (MAP_TYPE_TOWN). +#define WE_OWE_REPEL_DEXNAV_COLLISION FALSE // If TRUE, OWEs can still be triggered by a collision if a Repel or the DexNav is active. + +// Should move others from config/overworld.h here? + +#endif // GUARD_CONFIG_WILD_ENCOUNTER_OW_H diff --git a/include/constants/event_object_movement.h b/include/constants/event_object_movement.h index b74eefacfd6b..7daa4720caa8 100755 --- a/include/constants/event_object_movement.h +++ b/include/constants/event_object_movement.h @@ -85,7 +85,13 @@ #define MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT 0x50 #define MOVEMENT_TYPE_FOLLOW_PLAYER 0x51 #define MOVEMENT_TYPE_WANDER_AROUND_SLOWER 0x52 -#define NUM_MOVEMENT_TYPES 0x53 +#define MOVEMENT_TYPE_WANDER_AROUND_OWE 0x53 +#define MOVEMENT_TYPE_CHASE_PLAYER_OWE 0x54 +#define MOVEMENT_TYPE_FLEE_PLAYER_OWE 0x55 +#define MOVEMENT_TYPE_WATCH_PLAYER_OWE 0x56 +#define MOVEMENT_TYPE_APPROACH_PLAYER_OWE 0x57 +#define MOVEMENT_TYPE_DESPAWN_OWE 0x58 +#define NUM_MOVEMENT_TYPES 0x59 #define MOVEMENT_ACTION_FACE_DOWN 0x0 #define MOVEMENT_ACTION_FACE_UP 0x1 diff --git a/include/constants/event_objects.h b/include/constants/event_objects.h index fdaef552277b..71b0fc3764a0 100644 --- a/include/constants/event_objects.h +++ b/include/constants/event_objects.h @@ -492,28 +492,26 @@ #define OBJ_KIND_NORMAL 0 #define OBJ_KIND_CLONE 255 // Exclusive to FRLG -// Special object event local ids -// Used for link player OWs in CreateLinkPlayerSprite -#define OBJ_EVENT_ID_DYNAMIC_BASE 0xF0 // Each object event template gets an ID that can be used to refer to it in scripts and elsewhere. // This is referred to as the "local id" (and it's really just 1 + its index in the templates array). // There are a few special IDs reserved for objects that don't have templates in the map data -- one for the player // in regular offline play, five for linked players while playing Berry Blender, and one for an invisible object that // can be spawned for the camera to track instead of the player. Additionally, the value 0 is reserved as an "empty" indicator. -#define LOCALID_NONE 0 -#define LOCALID_CAMERA 127 -#define LOCALID_BERRY_BLENDER_PLAYER_END 240 // This will use 5 (MAX_RFU_PLAYERS) IDs ending at 240, i.e. 236-240 -#define LOCALID_FOLLOWING_POKEMON 254 -#define LOCALID_PLAYER 255 -#define OBJ_EVENT_ID_FOLLOWER 0xFE -#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD +#define LOCALID_NONE 0 +#define LOCALID_CAMERA 127 +#define LOCALID_BERRY_BLENDER_PLAYER_END 240 // This will use 5 (MAX_RFU_PLAYERS) IDs ending at 240, i.e. 236-240 +#define LOCALID_OW_ENCOUNTER_END 252 // This will use 4 (OWE_SPAWNS_MAX) IDs ending at 252, i.e. 249-252 +#define LOCALID_FOLLOWING_POKEMON 254 +#define LOCALID_PLAYER 255 +#define OBJ_EVENT_ID_FOLLOWER 0xFE +#define OBJ_EVENT_ID_NPC_FOLLOWER 0xFD // Aliases for old names. "object event id" normally refers to an index into gObjectEvents, which these are not. // Used for link player OWs in CreateLinkPlayerSprite -#define OBJ_EVENT_ID_DYNAMIC_BASE 0xF0 #define OBJ_EVENT_ID_CAMERA LOCALID_CAMERA #define OBJ_EVENT_ID_PLAYER LOCALID_PLAYER +#define OBJ_EVENT_ID_DYNAMIC_BASE 0xF0 // Moved from src/event_object_movement.c so that they're accesible from other files. #define OBJ_EVENT_PAL_TAG_BRENDAN 0x1100 diff --git a/include/constants/field_effects.h b/include/constants/field_effects.h index 7cf774b91f30..dd3fb7136257 100644 --- a/include/constants/field_effects.h +++ b/include/constants/field_effects.h @@ -83,6 +83,7 @@ #define FLDEFF_SMILEY_FACE_ICON 78 #define FLDEFF_HALL_OF_FAME_RECORD_FRLG 79 #define FLDEFF_PHOTO_FLASH 80 +#define FLDEFF_OW_ENCOUNTER_SPAWN_ANIM 81 #define FLDEFFOBJ_SHADOW_S 0 #define FLDEFFOBJ_SHADOW_M 1 @@ -129,6 +130,7 @@ #define FLDEFFOBJ_ROCK_CLIMB_DUST 42 #define FLDEFFOBJ_ORAS_DOWSE_BRENDAN 43 #define FLDEFFOBJ_ORAS_DOWSE_MAY 44 +#define FLDEFFOBJ_SHINY_SPARKLE 45 #define FLDEFF_PAL_TAG_CUT_GRASS 0x1000 #define FLDEFF_PAL_TAG_SECRET_POWER_TREE 0x1003 diff --git a/include/constants/global.h b/include/constants/global.h index 93f88bcf1875..8c0300c23328 100644 --- a/include/constants/global.h +++ b/include/constants/global.h @@ -28,6 +28,7 @@ #include "config/overworld.h" #include "config/pokemon.h" #include "config/summary_screen.h" +#include "config/wild_encounter.h" // Invalid Versions show as "----------" in Gen 4 and Gen 5's summary screen. // In Gens 6 and 7, invalid versions instead show "a distant land" in the summary screen. diff --git a/include/constants/trainer_types.h b/include/constants/trainer_types.h index 8886cf44237a..c21f3c042a69 100644 --- a/include/constants/trainer_types.h +++ b/include/constants/trainer_types.h @@ -5,5 +5,6 @@ #define TRAINER_TYPE_NORMAL 1 #define TRAINER_TYPE_SEE_ALL_DIRECTIONS 2 #define TRAINER_TYPE_BURIED 3 +#define TRAINER_TYPE_OW_WILD_ENCOUNTER 0xFF #endif // GUARD_CONSTANTS_TRAINER_TYPES_H diff --git a/include/constants/wild_encounter.h b/include/constants/wild_encounter.h index 364dc7b35a22..ae79e452c5ea 100644 --- a/include/constants/wild_encounter.h +++ b/include/constants/wild_encounter.h @@ -9,4 +9,7 @@ #define NUM_ALTERING_CAVE_TABLES 9 +#define WILD_CHECK_REPEL (1 << 0) +#define WILD_CHECK_KEEN_EYE (1 << 1) + #endif // GUARD_CONSTANTS_WILD_ENCOUNTER_H diff --git a/include/event_object_movement.h b/include/event_object_movement.h index 0b584c862987..56404456cd6c 100644 --- a/include/event_object_movement.h +++ b/include/event_object_movement.h @@ -124,6 +124,11 @@ extern const u8 gReflectionEffectPaletteMap[]; extern const struct SpriteFrameImage *const gBerryTreePicTablePointers[]; extern const u8 *const gBerryTreePaletteSlotTablePointers[]; +extern const struct SpritePalette gSpritePalette_GeneralFieldEffect0; +extern const struct SpritePalette gSpritePalette_GeneralFieldEffect1; + +extern const enum Direction gStandardDirections[]; + void ResetObjectEvents(void); u8 GetMoveDirectionAnimNum(enum Direction direction); u8 GetObjectEventIdByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroupId); @@ -183,17 +188,20 @@ void InitObjectEventPalettes(u8 reflectionType); void UpdateObjectEventCurrentMovement(struct ObjectEvent *objectEvent, struct Sprite *sprite, bool8 (*callback)(struct ObjectEvent *, struct Sprite *)); bool8 ObjectEventFaceOppositeDirection(struct ObjectEvent *objectEvent, enum Direction direction); enum Direction GetOppositeDirection(enum Direction direction); +enum Direction GetNinetyDegreeDirection(enum Direction direction, bool32 clockwise); u8 GetWalkInPlaceFasterMovementAction(u32); u8 GetWalkInPlaceFastMovementAction(u32); u8 GetWalkInPlaceNormalMovementAction(u32); u8 GetWalkInPlaceSlowMovementAction(u32); enum Collision GetCollisionAtCoords(struct ObjectEvent *objectEvent, s16 x, s16 y, enum Direction dir); +bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent, s16 x, s16 y, enum Direction direction); u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, bool32 addCoords); void MoveCoords(enum Direction direction, s16 *x, s16 *y); bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent); u8 ObjectEventClearHeldMovementIfFinished(struct ObjectEvent *objectEvent); u8 GetObjectEventIdByPosition(u16 x, u16 y, u8 elevation); void SetTrainerMovementType(struct ObjectEvent *objectEvent, u8 movementType); +u8 GetCollisionInDirection(struct ObjectEvent *, enum Direction); u8 GetTrainerFacingDirectionMovementType(enum Direction direction); const u8 *GetObjectEventScriptPointerByObjectEventId(u8 objectEventId); u8 GetCollisionFlagsAtCoords(struct ObjectEvent *objectEvent, s16 x, s16 y, enum Direction direction); @@ -261,11 +269,17 @@ u8 GetObjectEventBerryTreeId(u8 objectEventId); void SetBerryTreeJustPicked(u8 mapId, u8 mapNumber, u8 mapGroup); bool8 IsBerryTreeSparkling(u8 localId, u8 mapNum, u8 mapGroup); const struct ObjectEventTemplate *GetObjectEventTemplateByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup); +u16 LoadSheetGraphicsInfo(const struct ObjectEventGraphicsInfo *info, u16 uuid, struct Sprite *sprite); u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY); bool8 GetFollowerInfo(u32 *species, bool32 *shiny, bool32 *female); const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female); +u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female); u16 GetObjectEventFlagIdByLocalIdAndMap(u8 localId, u8 mapNum, u8 mapGroup); void CopyObjectGraphicsInfoToSpriteTemplate(u16 graphicsId, void (*callback)(struct Sprite *), struct SpriteTemplate *spriteTemplate, const struct SubspriteTable **subspriteTables); +bool8 AreElevationsCompatible(u8, u8); +enum Direction DetermineObjectEventDirectionFromObject(struct ObjectEvent *objectOne, struct ObjectEvent *objectTwo); +void ObjectEventsTurnToEachOther(struct ObjectEvent *objectOne, struct ObjectEvent *objectTwo); +void UpdateObjectEventCoords(struct ObjectEvent *objectEvent, s16 dx, s16 dy); void MovementType_None(struct Sprite *sprite); void MovementType_LookAround(struct Sprite *sprite); @@ -522,4 +536,37 @@ bool8 PlayerIsUnderWaterfall(struct ObjectEvent *objectEvent); u8 GetObjectEventApricornTreeId(u8 objectEventId); +// Overworld Wild Encounter +bool8 MovementAction_OverworldEncounterSpawn(enum SpawnDespawnTypeOWE spawnAnimType, struct ObjectEvent *objEvent); + +void MovementType_OverworldWildEncounter_WanderAround(struct Sprite *sprite); +void MovementType_OverworldWildEncounter_ChasePlayer(struct Sprite *sprite); +void MovementType_OverworldWildEncounter_FleePlayer(struct Sprite *sprite); +void MovementType_OverworldWildEncounter_WatchPlayer(struct Sprite *sprite); +void MovementType_OverworldWildEncounter_ApproachPlayer(struct Sprite *sprite); +void MovementType_OverworldWildEncounter_Despawn(struct Sprite *sprite); + +u8 MovementType_OverworldWildEncounter_WanderAround_Step2(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_WanderAround_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_WanderAround_Step4(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_WanderAround_Step5(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_Common_Step7(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_ChasePlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_Common_Step9(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_ChasePlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_ChasePlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_Common_Step12(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_FleePlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_FleePlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_FleePlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_WatchPlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_WatchPlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_WatchPlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_ApproachPlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_ApproachPlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_ApproachPlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_Despawn_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_Despawn_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite); +u8 MovementType_OverworldWildEncounter_Despawn_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite); + #endif //GUARD_EVENT_OBJECT_MOVEMENT_H diff --git a/include/field_effect.h b/include/field_effect.h index 0bfe15308b20..91dacd0af2b5 100644 --- a/include/field_effect.h +++ b/include/field_effect.h @@ -1,6 +1,8 @@ #ifndef GUARD_FIELD_EFFECTS_H #define GUARD_FIELD_EFFECTS_H +#include "field_weather.h" + extern const struct SpritePalette gNewGameBirchObjectPaletteInfo; extern const struct SpriteTemplate gNewGameBirchObjectTemplate; extern const struct OamData gNewGameBirchOamAttributes; @@ -23,6 +25,7 @@ void MultiplyInvertedPaletteRGBComponents(u16 i, u8 r, u8 g, u8 b); void FieldEffectActiveListAdd(u8 id); void FieldEffectScript_LoadTiles(u8 **script); void FieldEffectScript_LoadFadedPalette(u8 **script); +void FieldEffect_LoadFadedPalette(struct SpritePalette *palette, enum ColorMapType colorMap); void FieldEffectScript_LoadPalette(u8 **script); void FieldEffectScript_CallNative(u8 **script, u32 *val); void FieldEffectFreeGraphicsResources(struct Sprite *sprite); diff --git a/include/fieldmap.h b/include/fieldmap.h index 2112aff35235..705eb8f8c544 100644 --- a/include/fieldmap.h +++ b/include/fieldmap.h @@ -62,6 +62,8 @@ void CopySecondaryTilesetToVram(struct MapLayout const *mapLayout); const struct MapHeader *const GetMapHeaderFromConnection(const struct MapConnection *connection); const struct MapConnection *GetMapConnectionAtPos(s16 x, s16 y); void MapGridSetMetatileImpassabilityAt(int x, int y, bool32 impassable); +bool32 AreCoordsInsideMap(u8 mapGroup, u8 mapNum, s16 x, s16 y); +bool32 AreCoordsInsidePlayerMap(s16 x, s16 y); // field_region_map.c void FieldInitRegionMap(MainCallback callback); diff --git a/include/pokemon.h b/include/pokemon.h index 1f903b5a89ef..a76666cd8381 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -3,6 +3,7 @@ #include "contest_effect.h" #include "sprite.h" +#include "wild_encounter_ow.h" #include "constants/battle.h" #include "constants/cries.h" #include "constants/egg_ids.h" @@ -517,6 +518,7 @@ struct SpeciesInfo /*0xC4*/ #endif //P_GENDER_DIFFERENCES #endif //OW_PKMN_OBJECTS_SHARE_PALETTES #endif //OW_POKEMON_OBJECT_EVENTS + enum OverworldWildEncounterBehaviors overworldEncounterBehavior; }; struct EggData @@ -730,6 +732,7 @@ void ZeroMonData(struct Pokemon *mon); void ZeroPlayerPartyMons(void); void ZeroEnemyPartyMons(void); u32 GetMonPersonality(u16 species, u8 gender, u8 nature, u8 unownLetter); +bool32 ComputePlayerShinyOdds(u32 personality, u32 value); void CreateMon(struct Pokemon *mon, u16 species, u8 level, u32 personality, struct OriginalTrainerId); void CreateRandomMon(struct Pokemon *mon, u16 species, u8 level); void CreateRandomMonWithIVs(struct Pokemon *mon, u16 species, u8 level, u8 fixedIv); @@ -941,5 +944,11 @@ bool32 IsSpeciesOfType(u32 species, enum Type type); struct BoxPokemon *GetSelectedBoxMonFromPcOrParty(void); u32 GiveScriptedMonToPlayer(struct Pokemon *mon, u8 slot); void ChangePokemonNicknameWithCallback(void (*callback)(void)); +u32 OWE_GetMovementTypeFromSpecies(u32 speciesId); +u32 OWE_GetViewDistanceFromSpecies(u32 speciesId); +u32 OWE_GetViewWidthFromSpecies(u32 speciesId); +u32 OWE_GetViewActiveDistanceFromSpecies(u32 speciesId); +enum SpeedOWE OWE_GetIdleSpeedFromSpecies(u32 speciesId); +enum SpeedOWE OWE_GetActiveSpeedFromSpecies(u32 speciesId); #endif // GUARD_POKEMON_H diff --git a/include/sprite.h b/include/sprite.h index b584e5a96bfb..ddd6b109870a 100644 --- a/include/sprite.h +++ b/include/sprite.h @@ -307,6 +307,7 @@ u16 LoadSpriteSheet(const struct SpriteSheet *sheet); u16 LoadSpriteSheetByTemplate(const struct SpriteTemplate *template, u32 frame, s32 offset); void LoadSpriteSheets(const struct SpriteSheet *sheets); s16 AllocSpriteTiles(u16 tileCount); +bool32 CanAllocSpriteTiles(u16 tileCount); void FreeSpriteTilesByTag(u16 tag); void FreeSpriteTileRanges(void); u16 GetSpriteTileStartByTag(u16 tag); @@ -339,5 +340,6 @@ void SetupSpritesForTextPrinting(u8 *spriteIds, const u32 **spriteSrc, u32 numSp u32 *GetSrcPtrFromSprite(struct Sprite *sprite); u32 GetSpriteWidth(struct Sprite *sprite); u32 GetSpriteHeight(struct Sprite *sprite); +u32 CountFreePaletteSlots(void); #endif //GUARD_SPRITE_H diff --git a/include/wild_encounter.h b/include/wild_encounter.h index 1c1f55f3b3b7..6b376c970b2c 100644 --- a/include/wild_encounter.h +++ b/include/wild_encounter.h @@ -3,6 +3,7 @@ #include "rtc.h" #include "constants/wild_encounter.h" +#include "wild_encounter_ow.h" #define HEADER_NONE 0xFFFF @@ -45,10 +46,14 @@ struct WildPokemonHeader extern const struct WildPokemonHeader gWildMonHeaders[]; +extern const struct WildPokemonHeader gBattlePikeWildMonHeaders[]; +extern const struct WildPokemonHeader gBattlePyramidWildMonHeaders[]; +extern const struct WildPokemon gWildFeebas; extern bool8 gIsFishingEncounter; extern bool8 gIsSurfingEncounter; extern u8 gChainFishingDexNavStreak; +u8 ChooseWildMonLevel(const struct WildPokemon *wildPokemon, u8 wildMonIndex, enum WildPokemonArea area); void DisableWildEncounters(bool8 disabled); bool8 StandardWildEncounter(u16 curMetatileBehavior, u16 prevMetatileBehavior); bool8 SweetScentWildEncounter(void); @@ -57,11 +62,18 @@ void FishingWildEncounter(u8 rod); u16 GetLocalWildMon(bool8 *isWaterMon); u16 GetLocalWaterMon(void); bool8 UpdateRepelCounter(void); +bool8 IsWildLevelAllowedByRepel(u8 wildLevel); +bool8 IsAbilityAllowingEncounter(u8 level); bool8 TryDoDoubleWildBattle(void); bool8 StandardWildEncounter_Debug(void); u32 CalculateChainFishingShinyRolls(void); void CreateWildMon(u16 species, u8 level); +bool8 TryGenerateWildMon(const struct WildPokemonInfo *wildMonInfo, enum WildPokemonArea area, u8 flags); +bool8 SetUpMassOutbreakEncounter(u8 flags); +bool8 DoMassOutbreakEncounterTest(void); +bool8 AreLegendariesInSootopolisPreventingEncounters(void); u16 GetCurrentMapWildMonHeaderId(void); +bool8 CheckFeebasAtCoords(s16 x, s16 y); u32 ChooseWildMonIndex_Land(void); u32 ChooseWildMonIndex_Water(void); u32 ChooseWildMonIndex_Rocks(void); diff --git a/include/wild_encounter_ow.h b/include/wild_encounter_ow.h new file mode 100644 index 000000000000..965ca66495ad --- /dev/null +++ b/include/wild_encounter_ow.h @@ -0,0 +1,93 @@ +#ifndef GUARD_WILD_ENCOUNTER_OW_H +#define GUARD_WILD_ENCOUNTER_OW_H + +#define OWE_APPROACH_DISTANCE 2 +#define OWE_APPROACH_JUMP_TIMER_MIN 16 +#define OWE_APPROACH_JUMP_TIMER_MAX 64 +#define OWE_FLEE_COLLISION_TIME 6 // If a fleeing mon is unable to take a step for this many tries it will despawn. (Multiply this value by 16 to get number of frames.) +#define OWE_DESPAWN_FRAMES 30 // Number of frames before a mon despawns after noticing the player (OWE_BEHAVIOR_DESPAWN) + +enum SpawnDespawnTypeOWE +{ + OWE_SPAWN_ANIM_GRASS, + OWE_SPAWN_ANIM_LONG_GRASS, + OWE_SPAWN_ANIM_WATER, + OWE_SPAWN_ANIM_UNDERWATER, + OWE_SPAWN_ANIM_CAVE, + OWE_SPAWN_ANIM_SHINY +}; + +enum TypeOWE +{ + OWE_ANY, + OWE_GENERATED, + OWE_MANUAL +}; + +// OWE_SPEED_FASTER seems to visually bug out sometimes. +enum SpeedOWE +{ + OWE_SPEED_NORMAL, + OWE_SPEED_SLOW, + OWE_SPEED_FAST, + OWE_SPEED_FASTER +}; + +struct BehaviorOWE +{ + u32 movementType:8; + u32 viewDistance:4; + u32 viewWidth:4; + u32 activeDistance:4; + u32 padding:12; + enum SpeedOWE idleSpeed; + enum SpeedOWE activeSpeed; +}; + +enum __attribute__((packed)) OverworldWildEncounterBehaviors +{ + OWE_IGNORE_PLAYER, + OWE_CHASE_PLAYER_SLOW, + OWE_FLEE_PLAYER_NORMAL, + OWE_WATCH_PLAYER_NORMAL, + OWE_APPROACH_PLAYER_SLOW, + OWE_DESPAWN_ON_NOTICE, + OWE_SPECIES_BEHAVIOR_COUNT +}; + +void OverworldWildEncounters_CB(void); +bool32 IsOverworldWildEncounter(struct ObjectEvent *owe, enum TypeOWE oweType); +void StartWildBattleWithOWE(void); +void SetInstantOWESpawnTimer(void); +void SetMinimumOWESpawnTimer(void); +void TryTriggerOverworldWilEncounter(struct ObjectEvent *obstacle, struct ObjectEvent *collider); +bool32 ShouldRunDefaultOWEScript(u32 objectEventId); +void OnOverworldWildEncounterSpawn(struct ObjectEvent *owe); +void OnOverworldWildEncounterDespawn(struct ObjectEvent *owe); +bool32 IsOWEDespawnExempt(struct ObjectEvent *owe); +bool32 DespawnOWEDueToNPCCollision(struct ObjectEvent *curObject, struct ObjectEvent *owe); +u32 DespawnOWEDueToTrainerSight(u32 collision, s32 x, s32 y); +void DespwnAllOverworldWildEncounters(enum TypeOWE oweType, u32 flags); +bool32 TryAndDespawnOldestGeneratedOWE_Object(u32 localId, u8 *objectEventId); +void TryAndDespawnOldestGeneratedOWE_Palette(void); +void DespawnOWEOnBattleStart(void); +void TryDespawnOWEsCrossingMapConnection(void); +void RestoreSavedOWEBehaviorState(struct ObjectEvent *owe, struct Sprite *sprite); +void SetSavedOWEMovementState(struct ObjectEvent *owe); +void ClearSavedOWEMovementState(struct ObjectEvent *owe); +bool32 CheckRestrictedOWEMovement(struct ObjectEvent *owe, enum Direction direction); +bool32 CanAwareOWESeePlayer(struct ObjectEvent *owe); +bool32 IsPlayerInsideOWEActiveDistance(struct ObjectEvent *owe); +bool32 IsOWENextToPlayer(struct ObjectEvent *owe); +enum Direction DirectionOfOWEToPlayerFromCollision(struct ObjectEvent *owe); +u32 GetApproachingOWEDistanceToPlayer(struct ObjectEvent *owe, bool32 *equalDistances); +u32 GetOWEWalkMovementActionInDirectionWithSpeed(enum Direction direction, u32 speed); +void OWEApproachForBattle(void); +void PlayAmbientOWECry(void); +u32 GetNumberOfActiveOWEs(enum TypeOWE oweType); +const struct ObjectEventTemplate TryGetObjectEventTemplateForOWE(const struct ObjectEventTemplate *template); +struct SpritePalette GetOWESpawnDespawnAnimFldEffPalette(enum SpawnDespawnTypeOWE spawnAnim); + +extern const u8 InteractWithOverworldWildEncounter[]; + +#endif // GUARD_WILD_ENCOUNTER_OW_H \ No newline at end of file diff --git a/spritesheet_rules.mk b/spritesheet_rules.mk index 2088feb76351..dd91eafff295 100644 --- a/spritesheet_rules.mk +++ b/spritesheet_rules.mk @@ -1169,6 +1169,9 @@ $(FLDEFFGFXDIR)/secret_power_tree.4bpp: %.4bpp: %.png $(FLDEFFGFXDIR)/record_mix_lights.4bpp: %.4bpp: %.png $(GFX) $< $@ -mwidth 4 -mheight 1 +$(FLDEFFGFXDIR)/shiny_sparkle.4bpp: %.4bpp: %.png + $(GFX) $< $@ -mwidth 2 -mheight 4 + $(POKEMONGFXDIR)/question_mark/overworld.4bpp: %.4bpp: %.png $(GFX) $< $@ -mwidth 4 -mheight 4 diff --git a/src/battle_pyramid.c b/src/battle_pyramid.c index f777be99cf35..b85742236607 100644 --- a/src/battle_pyramid.c +++ b/src/battle_pyramid.c @@ -1391,7 +1391,7 @@ static bool32 CheckBattlePyramidEvoRequirement(u16 species, const u16 *evoItems, } extern u32 GetTotalBaseStat(u32 species); -void GenerateBattlePyramidWildMon(void) +void GenerateBattlePyramidWildMon(u32 forceSpecies) { u8 name[POKEMON_NAME_LENGTH + 1]; int i, j; @@ -1399,7 +1399,7 @@ void GenerateBattlePyramidWildMon(void) u32 lvl = gSaveBlock2Ptr->frontier.lvlMode; u16 round = (gSaveBlock2Ptr->frontier.pyramidWinStreaks[lvl] / 7) % TOTAL_PYRAMID_ROUNDS; const struct BattlePyramidRequirement *reqs = &sBattlePyramidRequirementsByRound[round]; - u16 species; + u32 species = forceSpecies; u32 bstLim; u16 *moves = NULL; u16 *abilities = NULL; @@ -1419,7 +1419,8 @@ void GenerateBattlePyramidWildMon(void) while (1) { - species = Random() % NUM_SPECIES; + if (!forceSpecies) + species = Random() % NUM_SPECIES; // check if base species if (GET_BASE_SPECIES_ID(species) != species) @@ -1563,7 +1564,7 @@ void GenerateBattlePyramidWildMon(void) CalculateMonStats(&gEnemyParty[0]); } #else -void GenerateBattlePyramidWildMon(void) +void GenerateBattlePyramidWildMon(u32 forceSpecies) { u8 name[POKEMON_NAME_LENGTH + 1]; int i; diff --git a/src/battle_setup.c b/src/battle_setup.c index 79c393553215..765a088f53fc 100644 --- a/src/battle_setup.c +++ b/src/battle_setup.c @@ -45,6 +45,7 @@ #include "item.h" #include "script.h" #include "field_name_box.h" +#include "wild_encounter_ow.h" #include "constants/battle_frontier.h" #include "constants/battle_setup.h" #include "constants/event_objects.h" @@ -259,6 +260,7 @@ static void Task_BattleStart(u8 taskId) SetMainCallback2(CB2_InitBattle); RestartWildEncounterImmunitySteps(); ClearPoisonStepCounter(); + DespawnOWEOnBattleStart(); DestroyTask(taskId); } break; diff --git a/src/data/field_effects/field_effect_object_template_pointers.h b/src/data/field_effects/field_effect_object_template_pointers.h index 369b5821a9b3..4abed5e03dfc 100755 --- a/src/data/field_effects/field_effect_object_template_pointers.h +++ b/src/data/field_effects/field_effect_object_template_pointers.h @@ -34,6 +34,7 @@ extern const struct SpriteTemplate gFieldEffectObjectTemplate_AshPuff; extern const struct SpriteTemplate gFieldEffectObjectTemplate_AshLaunch; extern const struct SpriteTemplate gFieldEffectObjectTemplate_Bubbles; extern const struct SpriteTemplate gFieldEffectObjectTemplate_SmallSparkle; +extern const struct SpriteTemplate gFieldEffectObjectTemplate_ShinySparkle; extern const struct SpriteTemplate gFieldEffectObjectTemplate_Rayquaza; extern const struct SpriteTemplate gFieldEffectObjectTemplate_BallLight; extern const struct SpriteTemplate gFieldEffectObjectTemplate_SlitherTracks; @@ -91,4 +92,5 @@ const struct SpriteTemplate *const gFieldEffectObjectTemplatePointers[] = { [FLDEFFOBJ_ROCK_CLIMB_DUST] = &gFieldEffectObjectTemplate_RockClimbDust, [FLDEFFOBJ_ORAS_DOWSE_BRENDAN] = &gFieldEffectObjectTemplate_ORASDowsingBrendan, [FLDEFFOBJ_ORAS_DOWSE_MAY] = &gFieldEffectObjectTemplate_ORASDowsingMay, + [FLDEFFOBJ_SHINY_SPARKLE] = &gFieldEffectObjectTemplate_ShinySparkle, }; diff --git a/src/data/field_effects/field_effect_objects.h b/src/data/field_effects/field_effect_objects.h index e496ed0dc7c7..8adaf0f2dbd2 100755 --- a/src/data/field_effects/field_effect_objects.h +++ b/src/data/field_effects/field_effect_objects.h @@ -1398,3 +1398,42 @@ const struct SpriteTemplate gFieldEffectObjectTemplate_RockClimbDust = { }; const struct SpritePalette gSpritePalette_BigDust = {gFieldEffectPal_DustCloud, FLDEFF_PAL_TAG_DUST_CLOUD}; + +static const struct SpriteFrameImage sPicTable_ShinySparkle[] = { + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 0), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 1), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 2), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 3), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 4), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 5), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 6), + overworld_frame(gFieldEffectObjectPic_ShinySparkle, 2, 4, 7), +}; + +static const union AnimCmd sAnim_ShinySparkle[] = +{ + ANIMCMD_FRAME(0, 4), + ANIMCMD_FRAME(1, 4), + ANIMCMD_FRAME(2, 4), + ANIMCMD_FRAME(3, 6), + ANIMCMD_FRAME(4, 6), + ANIMCMD_FRAME(5, 4), + ANIMCMD_FRAME(6, 4), + ANIMCMD_FRAME(7, 4), + ANIMCMD_END, +}; + +static const union AnimCmd *const sAnimTable_ShinySparkle[] = +{ + sAnim_ShinySparkle, +}; + +const struct SpriteTemplate gFieldEffectObjectTemplate_ShinySparkle = { + .tileTag = TAG_NONE, + .paletteTag = FLDEFF_PAL_TAG_GENERAL_0, + .oam = &gObjectEventBaseOam_16x32, + .anims = sAnimTable_ShinySparkle, + .images = sPicTable_ShinySparkle, + .affineAnims = gDummySpriteAffineAnimTable, + .callback = UpdateBubblesFieldEffect, +}; diff --git a/src/data/object_events/movement_action_func_tables.h b/src/data/object_events/movement_action_func_tables.h index 113194efcc8e..d30749147b85 100755 --- a/src/data/object_events/movement_action_func_tables.h +++ b/src/data/object_events/movement_action_func_tables.h @@ -1758,3 +1758,72 @@ u8 (*const gMovementActionFuncs_SpinRight[])(struct ObjectEvent *, struct Sprite MovementAction_SpinRight_Step1, MovementAction_PauseSpriteAnim, }; + +#define OWE_WANDER_AROUND_COMMON_STEPS \ + MovementType_WanderAround_Step0, \ + MovementType_WanderAround_Step1, \ + MovementType_OverworldWildEncounter_WanderAround_Step2, \ + MovementType_OverworldWildEncounter_WanderAround_Step3, \ + MovementType_OverworldWildEncounter_WanderAround_Step4, \ + MovementType_OverworldWildEncounter_WanderAround_Step5, \ + MovementType_WanderAround_Step6 + +u8 (*const gMovementTypeFuncs_WanderAround_OverworldWildEncounter[])(struct ObjectEvent *, struct Sprite *) = +{ + OWE_WANDER_AROUND_COMMON_STEPS, +}; + +u8 (*const gMovementTypeFuncs_ChasePlayer_OverworldWildEncounter[])(struct ObjectEvent *, struct Sprite *) = +{ + OWE_WANDER_AROUND_COMMON_STEPS, + MovementType_OverworldWildEncounter_Common_Step7, + MovementType_OverworldWildEncounter_ChasePlayer_Step8, + MovementType_OverworldWildEncounter_Common_Step9, + MovementType_OverworldWildEncounter_ChasePlayer_Step10, + MovementType_OverworldWildEncounter_ChasePlayer_Step11, + MovementType_OverworldWildEncounter_Common_Step12, +}; + +u8 (*const gMovementTypeFuncs_FleePlayer_OverworldWildEncounter[])(struct ObjectEvent *, struct Sprite *) = +{ + OWE_WANDER_AROUND_COMMON_STEPS, + MovementType_OverworldWildEncounter_Common_Step7, + MovementType_OverworldWildEncounter_FleePlayer_Step8, + MovementType_OverworldWildEncounter_Common_Step9, + MovementType_OverworldWildEncounter_FleePlayer_Step10, + MovementType_OverworldWildEncounter_FleePlayer_Step11, + MovementType_OverworldWildEncounter_Common_Step12, +}; + +u8 (*const gMovementTypeFuncs_WatchPlayer_OverworldWildEncounter[])(struct ObjectEvent *, struct Sprite *) = +{ + OWE_WANDER_AROUND_COMMON_STEPS, + MovementType_OverworldWildEncounter_Common_Step7, + MovementType_OverworldWildEncounter_WatchPlayer_Step8, + MovementType_OverworldWildEncounter_Common_Step9, + MovementType_OverworldWildEncounter_WatchPlayer_Step10, + MovementType_OverworldWildEncounter_WatchPlayer_Step11, + MovementType_OverworldWildEncounter_Common_Step12, +}; + +u8 (*const gMovementTypeFuncs_ApproachPlayer_OverworldWildEncounter[])(struct ObjectEvent *, struct Sprite *) = +{ + OWE_WANDER_AROUND_COMMON_STEPS, + MovementType_OverworldWildEncounter_Common_Step7, + MovementType_OverworldWildEncounter_ApproachPlayer_Step8, + MovementType_OverworldWildEncounter_Common_Step9, + MovementType_OverworldWildEncounter_ApproachPlayer_Step10, + MovementType_OverworldWildEncounter_ApproachPlayer_Step11, + MovementType_OverworldWildEncounter_Common_Step12, +}; + +u8 (*const gMovementTypeFuncs_Despawn_OverworldWildEncounter[])(struct ObjectEvent *, struct Sprite *) = +{ + OWE_WANDER_AROUND_COMMON_STEPS, + MovementType_OverworldWildEncounter_Common_Step7, + MovementType_OverworldWildEncounter_Despawn_Step8, + MovementType_OverworldWildEncounter_Common_Step9, + MovementType_OverworldWildEncounter_Despawn_Step10, + MovementType_OverworldWildEncounter_Despawn_Step11, + MovementType_OverworldWildEncounter_Common_Step12, +}; diff --git a/src/data/object_events/object_event_graphics.h b/src/data/object_events/object_event_graphics.h index 26417c94389a..8c814b1365e6 100755 --- a/src/data/object_events/object_event_graphics.h +++ b/src/data/object_events/object_event_graphics.h @@ -468,6 +468,8 @@ const u16 gFieldEffectObjectPalette_CaveDust[] = INCBIN_U16("graphics/field_effe const u32 gObjectEventPic_ApricornTree[] = INCBIN_U32("graphics/object_events/pics/misc/apricorn_tree.4bpp"); +const u32 gFieldEffectObjectPic_ShinySparkle[] = INCBIN_U32("graphics/field_effects/pics/shiny_sparkle.4bpp"); + #if IS_FRLG const u16 gObjectEventPic_RedNormal[] = INCBIN_U16("graphics/object_events/pics/people/red/red_normal.4bpp"); diff --git a/src/data/pokemon/wild_encounter_ow_behavior.h b/src/data/pokemon/wild_encounter_ow_behavior.h new file mode 100644 index 000000000000..ea2bc0de2cdf --- /dev/null +++ b/src/data/pokemon/wild_encounter_ow_behavior.h @@ -0,0 +1,68 @@ +#ifndef GUARD_WILD_ENCOUNTER_OW_SPECIES_BEHAVIOR_H +#define GUARD_WILD_ENCOUNTER_OW_SPECIES_BEHAVIOR_H + +#include "wild_encounter_ow.h" +#include "constants/event_object_movement.h" + +static const struct BehaviorOWE sOWESpeciesBehavior[OWE_SPECIES_BEHAVIOR_COUNT] = +{ + [OWE_IGNORE_PLAYER] = + { + .movementType = MOVEMENT_TYPE_WANDER_AROUND_OWE, + .viewDistance = 4, + .viewWidth = 3, + .activeDistance = 5, + .idleSpeed = OWE_SPEED_NORMAL, + .activeSpeed = OWE_SPEED_NORMAL, + }, + + [OWE_CHASE_PLAYER_SLOW] = + { + .movementType = MOVEMENT_TYPE_CHASE_PLAYER_OWE, + .viewDistance = 4, + .viewWidth = 3, + .activeDistance = 5, + .idleSpeed = OWE_SPEED_SLOW, + .activeSpeed = OWE_SPEED_SLOW, + }, + + [OWE_FLEE_PLAYER_NORMAL] = + { + .movementType = MOVEMENT_TYPE_FLEE_PLAYER_OWE, + .viewDistance = 4, + .viewWidth = 3, + .activeDistance = 5, + .idleSpeed = OWE_SPEED_NORMAL, + .activeSpeed = OWE_SPEED_NORMAL, + }, + + [OWE_WATCH_PLAYER_NORMAL] = + { + .movementType = MOVEMENT_TYPE_WATCH_PLAYER_OWE, + .viewDistance = 4, + .viewWidth = 3, + .activeDistance = 5, + .idleSpeed = OWE_SPEED_NORMAL, + }, + + [OWE_APPROACH_PLAYER_SLOW] = + { + .movementType = MOVEMENT_TYPE_APPROACH_PLAYER_OWE, + .viewDistance = 4, + .viewWidth = 3, + .activeDistance = 5, + .idleSpeed = OWE_SPEED_NORMAL, + .activeSpeed = OWE_SPEED_SLOW, + }, + + [OWE_DESPAWN_ON_NOTICE] = + { + .movementType = MOVEMENT_TYPE_DESPAWN_OWE, + .viewDistance = 4, + .viewWidth = 3, + .activeDistance = 5, + .idleSpeed = OWE_SPEED_NORMAL, + } +}; + +#endif // GUARD_WILD_ENCOUNTER_OW_SPECIES_BEHAVIOR_H diff --git a/src/dexnav.c b/src/dexnav.c index e823bed1efaf..ed2e6516b222 100644 --- a/src/dexnav.c +++ b/src/dexnav.c @@ -1845,6 +1845,7 @@ static void CB1_DexNavSearchCallback(void) static void Task_DexNavExitAndSearch(u8 taskId) { + DespwnAllOverworldWildEncounters(OWE_GENERATED, 0); DexNavGuiFreeResources(); DestroyTask(taskId); SetMainCallback1(CB1_DexNavSearchCallback); diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 7f78782ca59b..ecac396ad84f 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -40,6 +40,7 @@ #include "trainer_hill.h" #include "util.h" #include "wild_encounter.h" +#include "wild_encounter_ow.h" #include "constants/event_object_movement.h" #include "constants/abilities.h" #include "constants/battle.h" @@ -59,7 +60,8 @@ #define SPECIAL_LOCALIDS_START (min(LOCALID_CAMERA, \ min(LOCALID_PLAYER, \ - LOCALID_BERRY_BLENDER_PLAYER_END - MAX_RFU_PLAYERS + 1))) + min(LOCALID_BERRY_BLENDER_PLAYER_END - MAX_RFU_PLAYERS + 1, \ + LOCALID_OW_ENCOUNTER_END - OWE_SPAWNS_MAX + 1)))) // The object event templates on a map cannot use the special IDs listed above or they can behave unexpectedly. // For more details on these special IDs see their definitions in 'include/constants/event_objects.h'. @@ -129,13 +131,11 @@ static bool8 ObjectEventExecSingleMovementAction(struct ObjectEvent *, struct Sp static bool32 UpdateMonMoveInPlace(struct ObjectEvent *, struct Sprite *); static void SetMovementDelay(struct Sprite *, s16); static bool8 WaitForMovementDelay(struct Sprite *); -static u8 GetCollisionInDirection(struct ObjectEvent *, enum Direction); static enum Direction GetCopyDirection(u8, enum Direction, enum Direction); static void TryEnableObjectEventAnim(struct ObjectEvent *, struct Sprite *); static void ObjectEventExecHeldMovementAction(struct ObjectEvent *, struct Sprite *); static void UpdateObjectEventSpriteAnimPause(struct ObjectEvent *, struct Sprite *); static bool8 IsCoordOutsideObjectEventMovementRange(struct ObjectEvent *, s16, s16); -static bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *, s16, s16, enum Direction); static bool8 DoesObjectCollideWithObjectAt(struct ObjectEvent *, s16, s16); static void UpdateObjectEventOffscreen(struct ObjectEvent *, struct Sprite *); static void UpdateObjectEventSpriteVisibility(struct ObjectEvent *, struct Sprite *); @@ -211,10 +211,8 @@ static u8 DoJumpSpriteMovement(struct Sprite *); static u8 DoJumpSpecialSpriteMovement(struct Sprite *); static void CreateLevitateMovementTask(struct ObjectEvent *); static void DestroyLevitateMovementTask(u8); -static u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female); const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 shiny, bool32 female); static bool8 NpcTakeStep(struct Sprite *); -static bool8 AreElevationsCompatible(u8, u8); static void CopyObjectGraphicsInfoToSpriteTemplate_WithMovementType(u16 graphicsId, u16 movementType, struct SpriteTemplate *spriteTemplate, const struct SubspriteTable **subspriteTables); static u16 GetGraphicsIdForMon(u32 species, bool32 shiny, bool32 female); @@ -346,6 +344,12 @@ static void (*const sMovementTypeCallbacks[])(struct Sprite *) = [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_LEFT] = MovementType_WalkSlowlyInPlace, [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT] = MovementType_WalkSlowlyInPlace, [MOVEMENT_TYPE_FOLLOW_PLAYER] = MovementType_FollowPlayer, + [MOVEMENT_TYPE_WANDER_AROUND_OWE] = MovementType_OverworldWildEncounter_WanderAround, + [MOVEMENT_TYPE_CHASE_PLAYER_OWE] = MovementType_OverworldWildEncounter_ChasePlayer, + [MOVEMENT_TYPE_FLEE_PLAYER_OWE] = MovementType_OverworldWildEncounter_FleePlayer, + [MOVEMENT_TYPE_WATCH_PLAYER_OWE] = MovementType_OverworldWildEncounter_WatchPlayer, + [MOVEMENT_TYPE_APPROACH_PLAYER_OWE] = MovementType_OverworldWildEncounter_ApproachPlayer, + [MOVEMENT_TYPE_DESPAWN_OWE] = MovementType_OverworldWildEncounter_Despawn, }; static const bool8 sMovementTypeHasRange[NUM_MOVEMENT_TYPES] = { @@ -475,6 +479,12 @@ const u8 gInitialMovementTypeFacingDirections[NUM_MOVEMENT_TYPES] = { [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_LEFT] = DIR_WEST, [MOVEMENT_TYPE_WALK_SLOWLY_IN_PLACE_RIGHT] = DIR_EAST, [MOVEMENT_TYPE_FOLLOW_PLAYER] = DIR_SOUTH, + [MOVEMENT_TYPE_WANDER_AROUND_OWE] = DIR_SOUTH, + [MOVEMENT_TYPE_CHASE_PLAYER_OWE] = DIR_SOUTH, + [MOVEMENT_TYPE_FLEE_PLAYER_OWE] = DIR_SOUTH, + [MOVEMENT_TYPE_WATCH_PLAYER_OWE] = DIR_SOUTH, + [MOVEMENT_TYPE_APPROACH_PLAYER_OWE] = DIR_SOUTH, + [MOVEMENT_TYPE_DESPAWN_OWE] = DIR_SOUTH, }; #include "data/object_events/object_event_graphics_info_pointers.h" @@ -771,6 +781,7 @@ static const u16 *const sObjectPaletteTagSets[] = { static const s16 sMovementDelaysMedium[] = {32, 64, 96, 128}; static const s16 sMovementDelaysLong[] = {32, 64, 128, 192}; // Unused static const s16 sMovementDelaysShort[] = {32, 48, 64, 80}; +static const s16 sMovementDelaysOWE[] = {64, 80, 96, 128}; #include "data/object_events/movement_type_func_tables.h" @@ -1285,6 +1296,19 @@ static const u8 sOppositeDirections[] = { DIR_SOUTHEAST, DIR_SOUTHWEST, }; +// Should this and above be enum Direction? +static const u8 sRotate90Direction[][2] = +{ + [DIR_NONE] = { DIR_NONE, DIR_NONE }, + [DIR_SOUTH] = { DIR_EAST, DIR_WEST }, + [DIR_NORTH] = { DIR_WEST, DIR_EAST }, + [DIR_WEST] = { DIR_SOUTH, DIR_NORTH }, + [DIR_EAST] = { DIR_NORTH, DIR_SOUTH }, + [DIR_SOUTHWEST] = { DIR_SOUTHEAST, DIR_NORTHWEST }, + [DIR_SOUTHEAST] = { DIR_NORTHEAST, DIR_SOUTHWEST }, + [DIR_NORTHWEST] = { DIR_SOUTHWEST, DIR_NORTHEAST }, + [DIR_NORTHEAST] = { DIR_NORTHWEST, DIR_SOUTHEAST }, +}; // Takes the player's original and current facing direction to get the direction that should be considered to copy. // Note that this means an NPC who copies the player's movement changes how they copy them based on how @@ -1660,7 +1684,7 @@ static bool8 GetAvailableObjectEventId(u16 localId, u8 mapNum, u8 mapGroup, u8 * return TRUE; } if (i >= OBJECT_EVENTS_COUNT) - return TRUE; + return TryAndDespawnOldestGeneratedOWE_Object(localId, objectEventId); *objectEventId = i; for (; i < OBJECT_EVENTS_COUNT; i++) { @@ -1672,6 +1696,7 @@ static bool8 GetAvailableObjectEventId(u16 localId, u8 mapNum, u8 mapGroup, u8 * void RemoveObjectEvent(struct ObjectEvent *objectEvent) { + OnOverworldWildEncounterDespawn(objectEvent); objectEvent->active = FALSE; RemoveObjectEventInternal(objectEvent); // zero potential species info @@ -1894,17 +1919,19 @@ static u8 TrySetupObjectEventSprite(const struct ObjectEventTemplate *objectEven u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemplate, u8 mapNum, u8 mapGroup, s16 cameraX, s16 cameraY) { u8 objectEventId; - u16 graphicsId = objectEventTemplate->graphicsId; struct SpriteTemplate spriteTemplate; struct SpriteFrameImage spriteFrameImage; const struct ObjectEventGraphicsInfo *graphicsInfo; const struct SubspriteTable *subspriteTables = NULL; + // May be a good idea to move the if check contained by this function to outside it for clarity. + const struct ObjectEventTemplate objectEventTemplateLocal = TryGetObjectEventTemplateForOWE(objectEventTemplate); + u16 graphicsId = objectEventTemplateLocal.graphicsId; graphicsInfo = GetObjectEventGraphicsInfo(graphicsId); - CopyObjectGraphicsInfoToSpriteTemplate_WithMovementType(graphicsId, objectEventTemplate->movementType, &spriteTemplate, &subspriteTables); + CopyObjectGraphicsInfoToSpriteTemplate_WithMovementType(graphicsId, objectEventTemplateLocal.movementType, &spriteTemplate, &subspriteTables); spriteFrameImage.size = graphicsInfo->size; spriteTemplate.images = &spriteFrameImage; - objectEventId = TrySetupObjectEventSprite(objectEventTemplate, &spriteTemplate, mapNum, mapGroup, cameraX, cameraY); + objectEventId = TrySetupObjectEventSprite(&objectEventTemplateLocal, &spriteTemplate, mapNum, mapGroup, cameraX, cameraY); if (objectEventId == OBJECT_EVENTS_COUNT) return OBJECT_EVENTS_COUNT; @@ -1912,6 +1939,7 @@ u8 TrySpawnObjectEventTemplate(const struct ObjectEventTemplate *objectEventTemp if (subspriteTables) SetSubspriteTables(&gSprites[gObjectEvents[objectEventId].spriteId], subspriteTables); + OnOverworldWildEncounterSpawn(&gObjectEvents[objectEventId]); return objectEventId; } @@ -2178,7 +2206,7 @@ const struct ObjectEventGraphicsInfo *SpeciesToGraphicsInfo(u32 species, bool32 } // Find, or load, the palette for the specified pokemon info -static u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female) +u32 LoadDynamicFollowerPalette(u32 species, bool32 shiny, bool32 female) { u32 paletteNum; // Use standalone palette, unless entry is OOB or NULL (fallback to front-sprite-based) @@ -2959,6 +2987,10 @@ static void RemoveObjectEventIfOutsideView(struct ObjectEvent *objectEvent) if (objectEvent->initialCoords.x >= left && objectEvent->initialCoords.x <= right && objectEvent->initialCoords.y >= top && objectEvent->initialCoords.y <= bottom) return; + + if (IsOWEDespawnExempt(objectEvent)) + return; + RemoveObjectEvent(objectEvent); } @@ -3041,6 +3073,7 @@ static void SpawnObjectEventOnReturnToField(u8 objectEventId, s16 x, s16 y) ResetObjectEventFldEffData(objectEvent); SetObjectSubpriorityByElevation(objectEvent->previousElevation, sprite, 1); + RestoreSavedOWEBehaviorState(objectEvent, sprite); } } @@ -3475,6 +3508,19 @@ void ShiftStillObjectEventCoords(struct ObjectEvent *objectEvent) ShiftObjectEventCoords(objectEvent, objectEvent->currentCoords.x, objectEvent->currentCoords.y); } +void UpdateObjectEventCoords(struct ObjectEvent *objectEvent, s16 dx, s16 dy) +{ + if (objectEvent->active) + { + objectEvent->initialCoords.x -= dx; + objectEvent->initialCoords.y -= dy; + objectEvent->currentCoords.x -= dx; + objectEvent->currentCoords.y -= dy; + objectEvent->previousCoords.x -= dx; + objectEvent->previousCoords.y -= dy; + } +} + void UpdateObjectEventCoordsForCameraUpdate(void) { u8 i; @@ -3487,15 +3533,7 @@ void UpdateObjectEventCoordsForCameraUpdate(void) dy = gCamera.y; for (i = 0; i < OBJECT_EVENTS_COUNT; i++) { - if (gObjectEvents[i].active) - { - gObjectEvents[i].initialCoords.x -= dx; - gObjectEvents[i].initialCoords.y -= dy; - gObjectEvents[i].currentCoords.x -= dx; - gObjectEvents[i].currentCoords.y -= dy; - gObjectEvents[i].previousCoords.x -= dx; - gObjectEvents[i].previousCoords.y -= dy; - } + UpdateObjectEventCoords(&gObjectEvents[i], dx, dy); } } } @@ -6596,7 +6634,7 @@ static bool8 IsCoordOutsideObjectEventMovementRange(struct ObjectEvent *objectEv return FALSE; } -static bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent, s16 x, s16 y, enum Direction direction) +bool8 IsMetatileDirectionallyImpassable(struct ObjectEvent *objectEvent, s16 x, s16 y, enum Direction direction) { if (gOppositeDirectionBlockedMetatileFuncs[direction - 1](objectEvent->currentMetatileBehavior) || gDirectionBlockedMetatileFuncs[direction - 1](MapGridGetMetatileBehaviorAt(x, y))) @@ -6630,7 +6668,13 @@ u32 GetObjectObjectCollidesWith(struct ObjectEvent *objectEvent, s16 x, s16 y, b if ((curObject->currentCoords.x == x && curObject->currentCoords.y == y) || (curObject->previousCoords.x == x && curObject->previousCoords.y == y)) { if (AreElevationsCompatible(objectEvent->currentElevation, curObject->currentElevation)) + { + if (DespawnOWEDueToNPCCollision(curObject, objectEvent)) + continue; + + TryTriggerOverworldWilEncounter(objectEvent, curObject); return i; + } } } } @@ -6962,6 +7006,15 @@ enum Direction GetOppositeDirection(enum Direction direction) return directions[direction - 1]; } +enum Direction GetNinetyDegreeDirection(enum Direction direction, bool32 clockwise) +{ + if (direction <= DIR_NONE || direction >= NELEMS(sRotate90Direction)) + return DIR_NONE; + + return sRotate90Direction[direction][clockwise]; +} + + // Takes the player's original and current direction and gives a direction the copy NPC should consider as the player's direction. // See comments at the table's definition. static u32 GetPlayerDirectionForCopy(u8 initDir, u8 moveDir) @@ -10084,7 +10137,7 @@ static void ObjectEventUpdateSubpriority(struct ObjectEvent *objEvent, struct Sp SetObjectSubpriorityByElevation(objEvent->previousElevation, sprite, 1); } -static bool8 AreElevationsCompatible(u8 a, u8 b) +bool8 AreElevationsCompatible(u8 a, u8 b) { if (a == ELEVATION_TRANSITION || b == ELEVATION_TRANSITION) return TRUE; @@ -10095,6 +10148,87 @@ static bool8 AreElevationsCompatible(u8 a, u8 b) return TRUE; } +void ScriptFaceEachOther(struct ScriptContext *ctx) +{ + struct ObjectEvent *player, *npc; + player = &gObjectEvents[gPlayerAvatar.objectEventId]; + npc = &gObjectEvents[GetObjectEventIdByLocalId(gSpecialVar_LastTalked)]; + ObjectEventsTurnToEachOther(player, npc); +} + +enum Direction DetermineObjectEventDirectionFromObject(struct ObjectEvent *objectOne, struct ObjectEvent *objectTwo) +{ + s32 dx = objectOne->currentCoords.x - objectTwo->currentCoords.x; + s32 dy = objectOne->currentCoords.y - objectTwo->currentCoords.y; + + if (dx == 0 && dy == 0) + return DIR_NONE; + + s32 absX = abs(dx); + s32 absY = abs(dy); + + if (absX > absY && dx < 0) + return DIR_WEST; + else if (absX > absY && dx > 0) + return DIR_EAST; + else if (absY > absX && dy < 0) + return DIR_NORTH; + else if (absY > absX && dy > 0) + return DIR_SOUTH; + + enum Direction directionOne, directionTwo; + + if (dx < 0) + directionTwo = DIR_WEST; + else + directionTwo = DIR_EAST; + + if (dy < 0) + directionOne = DIR_NORTH; + else + directionOne = DIR_SOUTH; + + if (objectTwo->facingDirection == directionOne) + return directionOne; + else if (objectTwo->facingDirection == directionTwo) + return directionTwo; + + return (Random() % 2) ? directionOne : directionTwo; +} + +void ObjectEventsTurnToEachOther(struct ObjectEvent *objectOne, struct ObjectEvent *objectTwo) +{ + enum Direction objectDirOne, objectDirTwo; + + if (objectTwo->invisible == FALSE) + { + objectDirOne = DetermineObjectEventDirectionFromObject(objectOne, objectTwo); + objectDirTwo = objectDirOne; + + //Flip direction. + switch (objectDirOne) + { + case DIR_NORTH: + objectDirOne = DIR_SOUTH; + break; + case DIR_SOUTH: + objectDirOne = DIR_NORTH; + break; + case DIR_WEST: + objectDirOne = DIR_EAST; + break; + case DIR_EAST: + objectDirOne = DIR_WEST; + break; + default: + break; + } + + ObjectEventTurn(objectOne, objectDirOne); + ObjectEventTurn(objectTwo, objectDirTwo); + } +} + void GroundEffect_SpawnOnTallGrass(struct ObjectEvent *objEvent, struct Sprite *sprite) { gFieldEffectArguments[0] = objEvent->currentCoords.x; @@ -11792,3 +11926,379 @@ bool8 MovementAction_SpinRight_Step1(struct ObjectEvent *objectEvent, struct Spr } return FALSE; } + +bool8 MovementAction_OverworldEncounterSpawn(enum SpawnDespawnTypeOWE spawnAnimType, struct ObjectEvent *objEvent) +{ + gFieldEffectArguments[0] = objEvent->currentCoords.x; + gFieldEffectArguments[1] = objEvent->currentCoords.y; + gFieldEffectArguments[2] = spawnAnimType; + FieldEffectStart(FLDEFF_OW_ENCOUNTER_SPAWN_ANIM); + return TRUE; +} + +movement_type_def(MovementType_OverworldWildEncounter_WanderAround, gMovementTypeFuncs_WanderAround_OverworldWildEncounter) + +bool8 MovementType_OverworldWildEncounter_WanderAround_Step2(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + if (!ObjectEventExecSingleMovementAction(objectEvent, sprite)) + return FALSE; + + SetMovementDelay(sprite, sMovementDelaysOWE[Random() % ARRAY_COUNT(sMovementDelaysOWE)]); + sprite->sTypeFuncId = 3; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_WanderAround_Step3(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + if (WaitForMovementDelay(sprite)) + { + // resets a mid-movement sprite + ClearObjectEventMovement(objectEvent, sprite); + sprite->sTypeFuncId = 4; + return TRUE; + } + + if (OW_MON_WANDER_WALK == TRUE && IS_OW_MON_OBJ(objectEvent)) + UpdateMonMoveInPlace(objectEvent, sprite); + + if (CanAwareOWESeePlayer(objectEvent)) + sprite->sTypeFuncId = 7; + + return FALSE; +} + +bool8 MovementType_OverworldWildEncounter_WanderAround_Step4(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction chosenDirection = objectEvent->movementDirection; + if ((Random() & 3) != 0) + chosenDirection = GetNinetyDegreeDirection(chosenDirection, Random() % 2); + + SetObjectEventDirection(objectEvent, chosenDirection); + sprite->sTypeFuncId = 5; + if (CheckRestrictedOWEMovement(objectEvent, chosenDirection)) + sprite->sTypeFuncId = 1; + + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_WanderAround_Step5(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventSetSingleMovement(objectEvent, sprite, GetOWEWalkMovementActionInDirectionWithSpeed(objectEvent->movementDirection, OWE_GetIdleSpeedFromSpecies(OW_SPECIES(objectEvent)))); + objectEvent->singleMovementActive = TRUE; + sprite->sTypeFuncId = 6; + return TRUE; +} + +movement_type_def(MovementType_OverworldWildEncounter_ChasePlayer, gMovementTypeFuncs_ChasePlayer_OverworldWildEncounter) + +bool8 MovementType_OverworldWildEncounter_Common_Step7(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ClearObjectEventMovement(objectEvent, sprite); + SetSavedOWEMovementState(objectEvent); + sprite->sTypeFuncId = 8; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_ChasePlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + if (IsOWENextToPlayer(objectEvent)) + { + sprite->sTypeFuncId = 10; + return TRUE; + } + + ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK); + PlaySE(SE_PIN); + sprite->sTypeFuncId = 9; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_Common_Step9(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) + sprite->sTypeFuncId = 10; + + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_ChasePlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + sprite->sTypeFuncId = 11; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_ChasePlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + u32 speciesId = OW_SPECIES(objectEvent); + u32 movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(objectEvent->movementDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + sprite->sTypeFuncId = 12; + + if (CheckRestrictedOWEMovement(objectEvent, objectEvent->movementDirection)) + { + s16 x = objectEvent->currentCoords.x; + s16 y = objectEvent->currentCoords.y; + MoveCoords(objectEvent->movementDirection, &x, &y); + // If colliding with the player object, don't try to walk around it. + if (GetObjectObjectCollidesWith(objectEvent, x, y, FALSE) == gPlayerAvatar.objectEventId) + { + ObjectEventSetSingleMovement(objectEvent, sprite, GetFaceDirectionMovementAction(objectEvent->facingDirection)); + objectEvent->singleMovementActive = TRUE; + return FALSE; + } + + enum Direction newDirection = DirectionOfOWEToPlayerFromCollision(objectEvent); + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(newDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + if (CheckRestrictedOWEMovement(objectEvent, newDirection)) + movementActionId = GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection); + } + + ObjectEventSetSingleMovement(objectEvent, sprite, movementActionId); + objectEvent->singleMovementActive = TRUE; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_Common_Step12(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + if (ObjectEventExecSingleMovementAction(objectEvent, sprite)) + { + objectEvent->singleMovementActive = FALSE; + sprite->sTypeFuncId = 10; + if (!IsPlayerInsideOWEActiveDistance(objectEvent)) + { + ClearSavedOWEMovementState(objectEvent); + sprite->sTypeFuncId = 0; + } + } + return FALSE; +} + +movement_type_def(MovementType_OverworldWildEncounter_FleePlayer, gMovementTypeFuncs_FleePlayer_OverworldWildEncounter) + +bool8 MovementType_OverworldWildEncounter_FleePlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = GetOppositeDirection(DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent)); + SetObjectEventDirection(objectEvent, direction); + ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK); + PlaySE(SE_PIN); + sprite->sTypeFuncId = 9; + return TRUE; +} + +#define sCollisionTimer sprite->data[6] + +bool8 MovementType_OverworldWildEncounter_FleePlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + if (WE_OWE_FLEE_DESPAWN && sCollisionTimer >= OWE_FLEE_COLLISION_TIME) + { + RemoveObjectEvent(objectEvent); + return FALSE; + } + + enum Direction direction = GetOppositeDirection(DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent)); + SetObjectEventDirection(objectEvent, direction); + sprite->sTypeFuncId = 11; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_FleePlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + u32 speciesId = OW_SPECIES(objectEvent); + u32 movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(objectEvent->movementDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + if (CheckRestrictedOWEMovement(objectEvent, objectEvent->movementDirection)) + { + enum Direction newDirection = DirectionOfOWEToPlayerFromCollision(objectEvent); + if (newDirection != objectEvent->movementDirection) + newDirection = GetOppositeDirection(newDirection); + + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(newDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + if (CheckRestrictedOWEMovement(objectEvent, newDirection)) + { + sCollisionTimer++; + movementActionId = GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection); + } + else + { + sCollisionTimer = 0; + } + } + else + { + sCollisionTimer = 0; + } + + ObjectEventSetSingleMovement(objectEvent, sprite, movementActionId); + objectEvent->singleMovementActive = TRUE; + sprite->sTypeFuncId = 12; + return TRUE; +} + +#undef sCollisionTimer + +movement_type_def(MovementType_OverworldWildEncounter_WatchPlayer, gMovementTypeFuncs_WatchPlayer_OverworldWildEncounter) + +bool8 MovementType_OverworldWildEncounter_WatchPlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + sprite->sTypeFuncId = 10; + if (!IsOWENextToPlayer(objectEvent)) + { + ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EMOTE_QUESTION_MARK); + sprite->sTypeFuncId = 9; + } + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_WatchPlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + sprite->sTypeFuncId = 11; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_WatchPlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + ObjectEventSetSingleMovement(objectEvent, sprite, GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection)); + objectEvent->singleMovementActive = TRUE; + sprite->sTypeFuncId = 12; + return TRUE; +} + +movement_type_def(MovementType_OverworldWildEncounter_ApproachPlayer, gMovementTypeFuncs_ApproachPlayer_OverworldWildEncounter) + +#define sJumpTimer sprite->data[7] + +bool8 MovementType_OverworldWildEncounter_ApproachPlayer_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + sJumpTimer = (Random() % (OWE_APPROACH_JUMP_TIMER_MAX - OWE_APPROACH_JUMP_TIMER_MIN)) + OWE_APPROACH_JUMP_TIMER_MIN; + sprite->sTypeFuncId = 10; + if (!IsOWENextToPlayer(objectEvent)) + { + ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EMOTE_QUESTION_MARK); + sprite->sTypeFuncId = 9; + } + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_ApproachPlayer_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + sprite->sTypeFuncId = 11; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_ApproachPlayer_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + bool32 equalDistances = FALSE; + u32 distance = GetApproachingOWEDistanceToPlayer(objectEvent, &equalDistances); + u32 speciesId = OW_SPECIES(objectEvent); + u32 movementActionId; + if (distance <= 1) + { + SetObjectEventDirection(objectEvent, GetOppositeDirection(objectEvent->movementDirection)); + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(objectEvent->movementDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + if (CheckRestrictedOWEMovement(objectEvent, objectEvent->movementDirection)) + { + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + enum Direction newDirection = DirectionOfOWEToPlayerFromCollision(objectEvent); + if (objectEvent->currentCoords.x != player->currentCoords.x && objectEvent->currentCoords.y != player->currentCoords.y) + newDirection = GetOppositeDirection(newDirection); + + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(newDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + if (CheckRestrictedOWEMovement(objectEvent, newDirection)) + movementActionId = GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection); + } + } + else if (distance == OWE_APPROACH_DISTANCE && !equalDistances) + { + if (sJumpTimer <= 0) + { + sJumpTimer = (Random() % (OWE_APPROACH_JUMP_TIMER_MAX - OWE_APPROACH_JUMP_TIMER_MIN)) + OWE_APPROACH_JUMP_TIMER_MIN; + movementActionId = GetJumpInPlaceMovementAction(objectEvent->facingDirection); + PlaySE(SE_LEDGE); + } + else + { + sJumpTimer--; + movementActionId = GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection); + } + } + else + { + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(objectEvent->movementDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + + if (CheckRestrictedOWEMovement(objectEvent, objectEvent->movementDirection)) + { + s16 x = objectEvent->currentCoords.x; + s16 y = objectEvent->currentCoords.y; + MoveCoords(objectEvent->movementDirection, &x, &y); + // If colliding with the player object, don't try to walk around it. + if (GetObjectObjectCollidesWith(objectEvent, x, y, FALSE) == gPlayerAvatar.objectEventId) + { + ObjectEventSetSingleMovement(objectEvent, sprite, GetFaceDirectionMovementAction(objectEvent->facingDirection)); + objectEvent->singleMovementActive = TRUE; + return FALSE; + } + enum Direction newDirection = DirectionOfOWEToPlayerFromCollision(objectEvent); + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(newDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + + if (CheckRestrictedOWEMovement(objectEvent, newDirection)) + movementActionId = GetWalkInPlaceNormalMovementAction(objectEvent->facingDirection); + } + + sJumpTimer = (Random() % (OWE_APPROACH_JUMP_TIMER_MAX - OWE_APPROACH_JUMP_TIMER_MIN)) + OWE_APPROACH_JUMP_TIMER_MIN; + } + + ObjectEventSetSingleMovement(objectEvent, sprite, movementActionId); + objectEvent->singleMovementActive = TRUE; + sprite->sTypeFuncId = 12; + return TRUE; +} + +movement_type_def(MovementType_OverworldWildEncounter_Despawn, gMovementTypeFuncs_Despawn_OverworldWildEncounter) + +#define sDespawnTimer sprite->data[6] + +bool8 MovementType_OverworldWildEncounter_Despawn_Step8(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + ObjectEventSetSingleMovement(objectEvent, sprite, MOVEMENT_ACTION_EMOTE_EXCLAMATION_MARK); + PlaySE(SE_PIN); + sDespawnTimer = 0; + sprite->sTypeFuncId = 9; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_Despawn_Step10(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + enum Direction direction = DetermineObjectEventDirectionFromObject(&gObjectEvents[gPlayerAvatar.objectEventId], objectEvent); + SetObjectEventDirection(objectEvent, direction); + sprite->sTypeFuncId = 11; + return TRUE; +} + +bool8 MovementType_OverworldWildEncounter_Despawn_Step11(struct ObjectEvent *objectEvent, struct Sprite *sprite) +{ + if (sDespawnTimer == OWE_DESPAWN_FRAMES) + { + RemoveObjectEvent(objectEvent); + return FALSE; + } + + ObjectEventSetSingleMovement(objectEvent, sprite, GetFaceDirectionMovementAction(objectEvent->facingDirection)); + objectEvent->singleMovementActive = TRUE; + sDespawnTimer++; + sprite->sTypeFuncId = 12; + return TRUE; +} + +#undef sDespawnTimer diff --git a/src/field_control_avatar.c b/src/field_control_avatar.c index 96afec4c1f35..5e450e6268db 100644 --- a/src/field_control_avatar.c +++ b/src/field_control_avatar.c @@ -35,9 +35,11 @@ #include "trainer_hill.h" #include "vs_seeker.h" #include "wild_encounter.h" +#include "wild_encounter_ow.h" #include "constants/event_bg.h" #include "constants/event_objects.h" #include "constants/field_poison.h" +#include "constants/layouts.h" #include "constants/metatile_behaviors.h" #include "constants/songs.h" #include "constants/trainer_hill.h" @@ -405,6 +407,8 @@ static const u8 *GetInteractedObjectEventScript(struct MapPosition *position, u8 script = GetTrainerHillTrainerScript(); else if (PlayerHasFollowerNPC() && objectEventId == GetFollowerNPCObjectId()) script = GetFollowerNPCScriptPointer(); + else if (ShouldRunDefaultOWEScript(objectEventId)) + script = InteractWithOverworldWildEncounter; else script = GetObjectEventScriptPointerByObjectEventId(objectEventId); @@ -631,8 +635,17 @@ static const u8 *GetInteractedMetatileScript(struct MapPosition *position, u8 me return NULL; } -static const u8 *GetInteractedWaterScript(struct MapPosition *unused1, u8 metatileBehavior, enum Direction direction) +static const u8 *GetInteractedWaterScript(struct MapPosition *position, u8 metatileBehavior, enum Direction direction) { + // Does this need a define for the surf elevation (1) check? + // Can be used in sElevationToSubpriority and other places too + u8 objectEventId = GetObjectEventIdByPosition(position->x, position->y, ELEVATION_SURF); + if (IsPlayerFacingSurfableFishableWater() == TRUE && ShouldRunDefaultOWEScript(objectEventId)) + { + gSpecialVar_LastTalked = gObjectEvents[objectEventId].localId; + return InteractWithOverworldWildEncounter; + } + if (MetatileBehavior_IsFastWater(metatileBehavior) == TRUE && !TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING)) return EventScript_CurrentTooFast; if (IsFieldMoveUnlocked(FIELD_MOVE_SURF) && PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE @@ -886,9 +899,26 @@ void RestartWildEncounterImmunitySteps(void) sWildEncounterImmunitySteps = 0; } -static bool8 CheckStandardWildEncounter(u16 metatileBehavior) +static bool32 ShouldDisableRandomEncounters(void) { if (FlagGet(OW_FLAG_NO_ENCOUNTER)) + return TRUE; + + if (!WE_VANILLA_RANDOM && WE_OW_ENCOUNTERS) + { + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS && !WE_OWE_BATTLE_PIKE) + return FALSE; + + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR && !WE_OWE_BATTLE_PYRAMID) + return FALSE; + } + + return !WE_VANILLA_RANDOM; +} + +static bool8 CheckStandardWildEncounter(u16 metatileBehavior) +{ + if (ShouldDisableRandomEncounters()) return FALSE; if (sWildEncounterImmunitySteps < 4) diff --git a/src/field_effect.c b/src/field_effect.c index 75d3e689bece..20a8065649bc 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -876,6 +876,13 @@ void FieldEffectScript_LoadFadedPalette(u8 **script) UpdateSpritePaletteWithWeather(paletteSlot, ShouldFieldEffectBeFogBlended(*script)); } +void FieldEffect_LoadFadedPalette(struct SpritePalette *palette, enum ColorMapType colorMap) +{ + u32 paletteSlot = LoadSpritePalette(palette); + SetPaletteColorMapType(paletteSlot + 16, colorMap); + UpdateSpritePaletteWithWeather(paletteSlot, TRUE); +} + void FieldEffectScript_LoadPalette(u8 **script) { struct SpritePalette *palette = (struct SpritePalette *)FieldEffectScript_ReadWord(script); diff --git a/src/field_effect_helpers.c b/src/field_effect_helpers.c index d7c56ea2898c..6b62f9179c3b 100755 --- a/src/field_effect_helpers.c +++ b/src/field_effect_helpers.c @@ -11,6 +11,7 @@ #include "sound.h" #include "sprite.h" #include "trig.h" +#include "wild_encounter_ow.h" #include "constants/event_objects.h" #include "constants/field_effects.h" #include "constants/rgb.h" @@ -64,6 +65,9 @@ void SetUpShadow(struct ObjectEvent *objectEvent) void SetUpReflection(struct ObjectEvent *objectEvent, struct Sprite *sprite, bool8 stillReflection) { + if (IsOverworldWildEncounter(objectEvent, OWE_GENERATED)) + return; + struct Sprite *reflectionSprite; reflectionSprite = &gSprites[CreateCopySpriteAt(sprite, sprite->x, sprite->y, 152)]; @@ -1903,3 +1907,54 @@ static void UpdateGrassFieldEffectSubpriority(struct Sprite *sprite, u8 elevatio } } } + +u32 FldEff_OWE_SpawnAnim(void) +{ + u8 spriteId; + u8 visual; + s16 xOffset = 0, yOffset = 0; + enum SpawnDespawnTypeOWE spawnAnim = gFieldEffectArguments[2]; + struct SpritePalette palette = GetOWESpawnDespawnAnimFldEffPalette(spawnAnim); + + switch (spawnAnim) + { + case OWE_SPAWN_ANIM_GRASS: + visual = FLDEFFOBJ_JUMP_TALL_GRASS; + yOffset = 8; + break; + + case OWE_SPAWN_ANIM_LONG_GRASS: + visual = FLDEFFOBJ_JUMP_LONG_GRASS; + break; + + case OWE_SPAWN_ANIM_WATER: + visual = FLDEFFOBJ_JUMP_BIG_SPLASH; + yOffset = 8; + break; + + case OWE_SPAWN_ANIM_UNDERWATER: + visual = FLDEFFOBJ_BUBBLES; + break; + + case OWE_SPAWN_ANIM_CAVE: + visual = FLDEFFOBJ_GROUND_IMPACT_DUST; + yOffset = 12; + break; + + case OWE_SPAWN_ANIM_SHINY: + default: + visual = FLDEFFOBJ_SHINY_SPARKLE; + break; + } + + FieldEffect_LoadFadedPalette(&palette, COLOR_MAP_DARK_CONTRAST); + SetSpritePosToOffsetMapCoords((s16 *)&gFieldEffectArguments[0], (s16 *)&gFieldEffectArguments[1], 8, 0); + spriteId = CreateSpriteAtEnd(gFieldEffectObjectTemplatePointers[visual], gFieldEffectArguments[0] + xOffset, gFieldEffectArguments[1] + yOffset, 0); + if (spriteId != MAX_SPRITES) + { + struct Sprite *sprite = &gSprites[spriteId]; + sprite->coordOffsetEnabled = TRUE; + sprite->oam.priority = 2; + } + return spriteId; +} diff --git a/src/fieldmap.c b/src/fieldmap.c index 2a2ca145392e..7688fbe16870 100644 --- a/src/fieldmap.c +++ b/src/fieldmap.c @@ -808,6 +808,7 @@ bool8 CameraMove(int x, int y) gSaveBlock1Ptr->pos.x += x; gSaveBlock1Ptr->pos.y += y; MoveMapViewToBackup(direction); + TryDespawnOWEsCrossingMapConnection(); } return gCamera.active; @@ -1081,3 +1082,20 @@ void LoadMapTilesetPalettes(struct MapLayout const *mapLayout) LoadSecondaryTilesetPalette(mapLayout, FALSE); } } + +bool32 AreCoordsInsideMap(u8 mapGroup, u8 mapNum, s16 x, s16 y) +{ + const struct MapLayout *layout = Overworld_GetMapHeaderByGroupAndId(mapGroup, mapNum)->mapLayout; + s32 width = layout->width + MAP_OFFSET; + s32 height = layout->height + MAP_OFFSET; + + if (x >= 0 && x < width && y >= 0 && y < height) + return TRUE; + + return FALSE; +} + +bool32 AreCoordsInsidePlayerMap(s16 x, s16 y) +{ + return AreCoordsInsideMap(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum, x, y); +} diff --git a/src/follower_npc.c b/src/follower_npc.c index e7790af4a639..0bafbdd85887 100644 --- a/src/follower_npc.c +++ b/src/follower_npc.c @@ -1365,20 +1365,7 @@ void FollowerNPC_HandleSprite(void) enum Direction DetermineFollowerNPCDirection(struct ObjectEvent *player, struct ObjectEvent *follower) { - s32 delta_x = follower->currentCoords.x - player->currentCoords.x; - s32 delta_y = follower->currentCoords.y - player->currentCoords.y; - - if (delta_x < 0) - return DIR_EAST; - else if (delta_x > 0) - return DIR_WEST; - - if (delta_y < 0) - return DIR_SOUTH; - else if (delta_y > 0) - return DIR_NORTH; - - return DIR_NONE; + return DetermineObjectEventDirectionFromObject(player, follower); } u32 GetFollowerNPCObjectId(void) @@ -1826,38 +1813,10 @@ void ScriptFaceFollowerNPC(struct ScriptContext *ctx) if (!FNPC_ENABLE_NPC_FOLLOWERS || !PlayerHasFollowerNPC()) return; - enum Direction playerDirection, followerDirection; struct ObjectEvent *player, *follower; player = &gObjectEvents[gPlayerAvatar.objectEventId]; follower = &gObjectEvents[GetFollowerNPCData(FNPC_DATA_OBJ_ID)]; - - if (follower->invisible == FALSE) - { - playerDirection = DetermineFollowerNPCDirection(player, follower); - followerDirection = playerDirection; - - //Flip direction. - switch (playerDirection) - { - case DIR_NORTH: - playerDirection = DIR_SOUTH; - break; - case DIR_SOUTH: - playerDirection = DIR_NORTH; - break; - case DIR_WEST: - playerDirection = DIR_EAST; - break; - case DIR_EAST: - playerDirection = DIR_WEST; - break; - default: - break; - } - - ObjectEventTurn(player, playerDirection); - ObjectEventTurn(follower, followerDirection); - } + ObjectEventsTurnToEachOther(player, follower); } static const u8 *const FollowerNPCHideMovementsSpeedTable[][4] = diff --git a/src/item_use.c b/src/item_use.c index e954daa11320..c59b2aa11df6 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -1003,6 +1003,7 @@ static void Task_UseRepel(u8 taskId) VarSet(VAR_LAST_REPEL_LURE_USED, gSpecialVar_ItemId); #endif RemoveUsedItem(); + SetInstantOWESpawnTimer(); if (CurrentBattlePyramidLocation() == PYRAMID_LOCATION_NONE) DisplayItemMessage(taskId, FONT_NORMAL, gStringVar4, CloseItemMessage); else diff --git a/src/load_save.c b/src/load_save.c index ea0fcbdb8d05..e9de05f7a8ae 100644 --- a/src/load_save.c +++ b/src/load_save.c @@ -13,6 +13,7 @@ #include "save_location.h" #include "script_pokemon_util.h" #include "trainer_hill.h" +#include "wild_encounter_ow.h" #include "gba/flash_internal.h" #include "decoration_inventory.h" #include "agb_flash.h" @@ -237,6 +238,7 @@ void LoadObjectEvents(void) gObjectEvents[i].graphicsId & OBJ_EVENT_MON) gObjectEvents[i].active = TRUE; } + SetMinimumOWESpawnTimer(); } void CopyPartyAndObjectsToSave(void) diff --git a/src/overworld.c b/src/overworld.c index 795dfed4a730..622c639d0d5b 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -69,6 +69,7 @@ #include "tv.h" #include "scanline_effect.h" #include "wild_encounter.h" +#include "wild_encounter_ow.h" #include "vs_seeker.h" #include "frontier_util.h" #include "constants/abilities.h" @@ -925,6 +926,7 @@ void LoadMapFromCameraTransition(u8 mapGroup, u8 mapNum) || gMapHeader.regionMapSectionId != sLastMapSectionId) ShowMapNamePopup(); } + SetMinimumOWESpawnTimer(); } static void LoadMapFromWarp(bool32 a1) @@ -985,6 +987,7 @@ static void LoadMapFromWarp(bool32 a1) UpdateTVScreensOnMap(gBackupMapLayout.width, gBackupMapLayout.height); InitSecretBaseAppearance(TRUE); } + SetMinimumOWESpawnTimer(); } void ResetInitialPlayerAvatarState(void) @@ -1392,8 +1395,43 @@ void Overworld_FadeOutMapMusic(void) FadeOutMapMusic(4); } +static bool32 ShouldPlayAmbientCryVanillaOWE(void) +{ + bool32 owePlayed = FALSE; + + if (GetNumberOfActiveOWEs(OWE_ANY)) + { + switch (OW_AMBIENT_CRIES) + { + case OW_AMBIENT_CRIES_OWE_ONLY: + case OW_AMBIENT_CRIES_OWE_PRIORITY: + PlayAmbientOWECry(); + owePlayed = TRUE; + break; + + default: + break; + } + } + + switch (OW_AMBIENT_CRIES) + { + case OW_AMBIENT_CRIES_VANILLA: + return TRUE; + + case OW_AMBIENT_CRIES_OWE_PRIORITY: + return !owePlayed; + + default: + return FALSE; + } +} + static void PlayAmbientCry(void) { + if (!ShouldPlayAmbientCryVanillaOWE()) + return; + s16 x, y; s8 pan; s8 volume; @@ -1816,6 +1854,7 @@ static void OverworldBasic(void) ApplyWeatherColorMapIfIdle(gWeatherPtr->colorMapIndex); } } + OverworldWildEncounters_CB(); } // This CB2 is used when starting diff --git a/src/pokemon.c b/src/pokemon.c index 2c923a5b5450..1737a8e4a55f 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -981,6 +981,7 @@ const struct NatureInfo gNaturesInfo[NUM_NATURES] = #include "data/pokemon/form_species_tables.h" #include "data/pokemon/form_change_tables.h" #include "data/pokemon/form_change_table_pointers.h" +#include "data/pokemon/wild_encounter_ow_behavior.h" #include "data/object_events/object_event_pic_tables_followers.h" #include "data/pokemon/species_info.h" @@ -1317,6 +1318,48 @@ void CreateMonWithIVs(struct Pokemon *mon, u16 species, u8 level, u32 personalit CalculateMonStats(mon); } +bool32 ComputePlayerShinyOdds(u32 personality, u32 value) +{ + bool32 isShiny; + if (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY)) + { + isShiny = FALSE; + } + else if (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY)) + { + isShiny = TRUE; + } + else if (P_ONLY_OBTAINABLE_SHINIES && (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE || (B_FLAG_NO_CATCHING != 0 && FlagGet(B_FLAG_NO_CATCHING)))) + { + isShiny = FALSE; + } + else if (P_NO_SHINIES_WITHOUT_POKEBALLS && !HasAtLeastOnePokeBall()) + { + isShiny = FALSE; + } + else + { + u32 totalRerolls = 0; + if (CheckBagHasItem(ITEM_SHINY_CHARM, 1)) + totalRerolls += I_SHINY_CHARM_ADDITIONAL_ROLLS; + if (LURE_STEP_COUNT != 0) + totalRerolls += 1; + totalRerolls += CalculateChainFishingShinyRolls(); + if (gDexNavSpecies) + totalRerolls += CalculateDexNavShinyRolls(); + + u32 shinyPersonality = personality; + while (GET_SHINY_VALUE(value, shinyPersonality) >= SHINY_ODDS && totalRerolls > 0) + { + shinyPersonality = Random32(); + totalRerolls--; + } + + isShiny = GET_SHINY_VALUE(value, shinyPersonality) < SHINY_ODDS; + } + return isShiny; +} + void SetBoxMonIVs(struct BoxPokemon *mon, u8 fixedIV) { u32 i, value; @@ -1391,42 +1434,7 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u32 personal else // Player is the OT { value = READ_OTID_FROM_SAVE; - if (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY)) - { - isShiny = FALSE; - } - else if (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY)) - { - isShiny = TRUE; - } - else if (P_ONLY_OBTAINABLE_SHINIES && (CurrentBattlePyramidLocation() != PYRAMID_LOCATION_NONE || (B_FLAG_NO_CATCHING != 0 && FlagGet(B_FLAG_NO_CATCHING)))) - { - isShiny = FALSE; - } - else if (P_NO_SHINIES_WITHOUT_POKEBALLS && !HasAtLeastOnePokeBall()) - { - isShiny = FALSE; - } - else - { - u32 totalRerolls = 0; - if (CheckBagHasItem(ITEM_SHINY_CHARM, 1)) - totalRerolls += I_SHINY_CHARM_ADDITIONAL_ROLLS; - if (LURE_STEP_COUNT != 0) - totalRerolls += 1; - totalRerolls += CalculateChainFishingShinyRolls(); - if (gDexNavSpecies) - totalRerolls += CalculateDexNavShinyRolls(); - - u32 shinyPersonality = personality; - while (GET_SHINY_VALUE(value, shinyPersonality) >= SHINY_ODDS && totalRerolls > 0) - { - shinyPersonality = Random32(); - totalRerolls--; - } - - isShiny = GET_SHINY_VALUE(value, shinyPersonality) < SHINY_ODDS; - } + isShiny = ComputePlayerShinyOdds(personality, value); } SetBoxMonData(boxMon, MON_DATA_PERSONALITY, &personality); @@ -7432,3 +7440,45 @@ void ChangePokemonNicknameWithCallback(void (*callback)(void)) GetBoxMonData(boxMon, MON_DATA_NICKNAME, gStringVar2); DoNamingScreen(NAMING_SCREEN_NICKNAME, gStringVar2, GetBoxMonData(boxMon, MON_DATA_SPECIES), GetBoxMonGender(boxMon), GetBoxMonData(boxMon, MON_DATA_PERSONALITY), callback); } + +u32 OWE_GetMovementTypeFromSpecies(u32 speciesId) +{ + speciesId = SanitizeSpeciesId(speciesId); + enum OverworldWildEncounterBehaviors behavior = gSpeciesInfo[speciesId].overworldEncounterBehavior; + return sOWESpeciesBehavior[behavior].movementType; +} + +u32 OWE_GetViewDistanceFromSpecies(u32 speciesId) +{ + speciesId = SanitizeSpeciesId(speciesId); + enum OverworldWildEncounterBehaviors behavior = gSpeciesInfo[speciesId].overworldEncounterBehavior; + return sOWESpeciesBehavior[behavior].viewDistance; +} + +u32 OWE_GetViewWidthFromSpecies(u32 speciesId) +{ + speciesId = SanitizeSpeciesId(speciesId); + enum OverworldWildEncounterBehaviors behavior = gSpeciesInfo[speciesId].overworldEncounterBehavior; + return sOWESpeciesBehavior[behavior].viewWidth; +} + +u32 OWE_GetViewActiveDistanceFromSpecies(u32 speciesId) +{ + speciesId = SanitizeSpeciesId(speciesId); + enum OverworldWildEncounterBehaviors behavior = gSpeciesInfo[speciesId].overworldEncounterBehavior; + return sOWESpeciesBehavior[behavior].activeDistance; +} + +enum SpeedOWE OWE_GetIdleSpeedFromSpecies(u32 speciesId) +{ + speciesId = SanitizeSpeciesId(speciesId); + enum OverworldWildEncounterBehaviors behavior = gSpeciesInfo[speciesId].overworldEncounterBehavior; + return sOWESpeciesBehavior[behavior].idleSpeed; +} + +enum SpeedOWE OWE_GetActiveSpeedFromSpecies(u32 speciesId) +{ + speciesId = SanitizeSpeciesId(speciesId); + enum OverworldWildEncounterBehaviors behavior = gSpeciesInfo[speciesId].overworldEncounterBehavior; + return sOWESpeciesBehavior[behavior].activeSpeed; +} diff --git a/src/sprite.c b/src/sprite.c index 0fd90eb7a232..739dc82cefe0 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -680,6 +680,44 @@ s16 AllocSpriteTiles(u16 tileCount) return start; } +bool32 CanAllocSpriteTiles(u16 tileCount) +{ + u16 i; + u16 numTilesFound; + + if (tileCount == 0) + return TRUE; + + i = gReservedSpriteTileCount; + + for (;;) + { + while (SPRITE_TILE_IS_ALLOCATED(i)) + { + i++; + if (i == TOTAL_OBJ_TILE_COUNT) + return FALSE; + } + + numTilesFound = 1; + + while (numTilesFound != tileCount) + { + i++; + if (i == TOTAL_OBJ_TILE_COUNT) + return FALSE; + + if (!SPRITE_TILE_IS_ALLOCATED(i)) + numTilesFound++; + else + break; + } + + if (numTilesFound == tileCount) + return TRUE; + } +} + u8 SpriteTileAllocBitmapOp(u16 bit, u8 op) { u8 index = bit / 8; @@ -1592,14 +1630,16 @@ u32 LoadSpritePalette(const struct SpritePalette *palette) if (index == 0xFF) { - return 0xFF; - } - else - { - sSpritePaletteTags[index] = palette->tag; - DoLoadSpritePalette(palette->data, PLTT_ID(index)); - return index; + TryAndDespawnOldestGeneratedOWE_Palette(); + index = IndexOfSpritePaletteTag(TAG_NONE); + + if (index == 0xFF) + return 0xFF; } + + sSpritePaletteTags[index] = palette->tag; + DoLoadSpritePalette(palette->data, PLTT_ID(index)); + return index; } u32 LoadSpritePaletteWithTag(const u16 *pal, u16 tag) @@ -2105,3 +2145,14 @@ inline u32 GetSpriteHeight(struct Sprite *sprite) { return gOamDimensions[sprite->oam.shape][sprite->oam.size].height; } + +u32 CountFreePaletteSlots(void) +{ + u32 i, count = 0; + + for (i = gReservedSpritePaletteCount; i < 16; i++) + if (sSpritePaletteTags[i] == TAG_NONE) + count++; + + return count; +} diff --git a/src/trainer_see.c b/src/trainer_see.c index 95663d1e0bb0..a0e0a75b3abe 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -723,6 +723,7 @@ static u8 CheckPathBetweenTrainerAndPlayer(struct ObjectEvent *trainerObj, u8 ap { // Check for collisions on approach, ignoring the "out of range" collision for regular movement collision = GetCollisionFlagsAtCoords(trainerObj, x, y, direction); + collision = DespawnOWEDueToTrainerSight(collision, x, y); if (collision != 0 && (collision & ~(1 << (COLLISION_OUTSIDE_RANGE - 1)))) return 0; } diff --git a/src/wild_encounter.c b/src/wild_encounter.c index 26b796c365ee..0e451317b7ab 100644 --- a/src/wild_encounter.c +++ b/src/wild_encounter.c @@ -43,12 +43,8 @@ extern const u8 EventScript_SprayWoreOff[]; #define NUM_FISHING_SPOTS_3 149 #define NUM_FISHING_SPOTS (NUM_FISHING_SPOTS_1 + NUM_FISHING_SPOTS_2 + NUM_FISHING_SPOTS_3) -#define WILD_CHECK_REPEL (1 << 0) -#define WILD_CHECK_KEEN_EYE (1 << 1) - static u16 FeebasRandom(void); static void FeebasSeedRng(u16 seed); -static bool8 IsWildLevelAllowedByRepel(u8 level); static void ApplyFluteEncounterRateMod(u32 *encRate); static void ApplyCleanseTagEncounterRateMod(u32 *encRate); static u8 GetMaxLevelOfSpeciesInWildTable(const struct WildPokemon *wildMon, u16 species, enum WildPokemonArea area); @@ -57,7 +53,6 @@ static bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildM #else static bool8 TryGetAbilityInfluencedWildMonIndex(const struct WildPokemon *wildMon, enum Type type, enum Ability ability, u8 *monIndex); #endif -static bool8 IsAbilityAllowingEncounter(u8 level); EWRAM_DATA static u8 sWildEncountersDisabled = 0; EWRAM_DATA static u32 sFeebasRngValue = 0; @@ -67,7 +62,7 @@ EWRAM_DATA u8 gChainFishingDexNavStreak = 0; #include "data/wild_encounters.h" -static const struct WildPokemon sWildFeebas = {20, 25, SPECIES_FEEBAS}; +const struct WildPokemon gWildFeebas = {20, 25, SPECIES_FEEBAS}; static const u16 sRoute119WaterTileData[] = { @@ -113,18 +108,16 @@ static u16 GetFeebasFishingSpotId(s16 targetX, s16 targetY, u8 section) return spotId + 1; } -static bool8 CheckFeebas(void) +bool8 CheckFeebasAtCoords(s16 x, s16 y) { u8 i; u16 feebasSpots[NUM_FEEBAS_SPOTS]; - s16 x, y; u8 route119Section = 0; u16 spotId; if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(MAP_ROUTE119) && gSaveBlock1Ptr->location.mapNum == MAP_NUM(MAP_ROUTE119)) { - GetXYCoordsOneStepInFrontOfPlayer(&x, &y); x -= MAP_OFFSET; y -= MAP_OFFSET; @@ -328,7 +321,7 @@ static u32 ChooseWildMonIndex_Fishing(u8 rod) return wildMonIndex; } -static u8 ChooseWildMonLevel(const struct WildPokemon *wildPokemon, u8 wildMonIndex, enum WildPokemonArea area) +u8 ChooseWildMonLevel(const struct WildPokemon *wildPokemon, u8 wildMonIndex, enum WildPokemonArea area) { u8 min; u8 max; @@ -483,7 +476,7 @@ void CreateWildMon(u16 species, u8 level) #define TRY_GET_ABILITY_INFLUENCED_WILD_MON_INDEX(wildPokemon, type, ability, ptr, count) TryGetAbilityInfluencedWildMonIndex(wildPokemon, type, ability, ptr) #endif -static bool8 TryGenerateWildMon(const struct WildPokemonInfo *wildMonInfo, enum WildPokemonArea area, u8 flags) +bool8 TryGenerateWildMon(const struct WildPokemonInfo *wildMonInfo, enum WildPokemonArea area, u8 flags) { u8 wildMonIndex = 0; u8 level; @@ -552,7 +545,7 @@ static u16 GenerateFishingWildMon(const struct WildPokemonInfo *wildMonInfo, u8 return wildMonSpecies; } -static bool8 SetUpMassOutbreakEncounter(u8 flags) +bool8 SetUpMassOutbreakEncounter(u8 flags) { u16 i; @@ -566,7 +559,7 @@ static bool8 SetUpMassOutbreakEncounter(u8 flags) return TRUE; } -static bool8 DoMassOutbreakEncounterTest(void) +bool8 DoMassOutbreakEncounterTest(void) { if (gSaveBlock1Ptr->outbreakPokemonSpecies != SPECIES_NONE && gSaveBlock1Ptr->location.mapNum == gSaveBlock1Ptr->outbreakLocationMapNum @@ -636,7 +629,7 @@ static bool8 AllowWildCheckOnNewMetatile(void) return TRUE; } -static bool8 AreLegendariesInSootopolisPreventingEncounters(void) +bool8 AreLegendariesInSootopolisPreventingEncounters(void) { if (gSaveBlock1Ptr->location.mapGroup != MAP_GROUP(MAP_SOOTOPOLIS_CITY) || gSaveBlock1Ptr->location.mapNum != MAP_NUM(MAP_SOOTOPOLIS_CITY)) @@ -688,7 +681,7 @@ bool8 StandardWildEncounter(u16 curMetatileBehavior, u16 prevMetatileBehavior) else if (TryGenerateWildMon(gBattlePyramidWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo, WILD_AREA_LAND, WILD_CHECK_KEEN_EYE) != TRUE) return FALSE; - GenerateBattlePyramidWildMon(); + GenerateBattlePyramidWildMon(SPECIES_NONE); BattleSetup_StartWildBattle(); return TRUE; } @@ -865,7 +858,7 @@ bool8 SweetScentWildEncounter(void) if (TryGenerateWildMon(gBattlePyramidWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo, WILD_AREA_LAND, 0) != TRUE) return FALSE; - GenerateBattlePyramidWildMon(); + GenerateBattlePyramidWildMon(SPECIES_NONE); BattleSetup_StartWildBattle(); return TRUE; } @@ -932,14 +925,16 @@ void FishingWildEncounter(u8 rod) { u16 species; u32 headerId; + s16 x, y; enum TimeOfDay timeOfDay; gIsFishingEncounter = TRUE; - if (CheckFeebas() == TRUE) + GetXYCoordsOneStepInFrontOfPlayer(&x, &y); + if (CheckFeebasAtCoords(x, y) == TRUE) { - u8 level = ChooseWildMonLevel(&sWildFeebas, 0, WILD_AREA_FISHING); + u8 level = ChooseWildMonLevel(&gWildFeebas, 0, WILD_AREA_FISHING); - species = sWildFeebas.species; + species = gWildFeebas.species; CreateWildMon(species, level); } else @@ -1050,7 +1045,7 @@ bool8 UpdateRepelCounter(void) return FALSE; } -static bool8 IsWildLevelAllowedByRepel(u8 wildLevel) +bool8 IsWildLevelAllowedByRepel(u8 wildLevel) { u8 i; @@ -1069,7 +1064,7 @@ static bool8 IsWildLevelAllowedByRepel(u8 wildLevel) return FALSE; } -static bool8 IsAbilityAllowingEncounter(u8 level) +bool8 IsAbilityAllowingEncounter(u8 level) { enum Ability ability; diff --git a/src/wild_encounter_ow.c b/src/wild_encounter_ow.c new file mode 100644 index 000000000000..f3123c1b71d1 --- /dev/null +++ b/src/wild_encounter_ow.c @@ -0,0 +1,1844 @@ +#include "global.h" +#include "wild_encounter_ow.h" +#include "battle_setup.h" +#include "battle_main.h" +#include "battle_pike.h" +#include "battle_pyramid.h" +#include "event_data.h" +#include "event_object_movement.h" +#include "fieldmap.h" +#include "field_effect.h" +#include "field_player_avatar.h" +#include "follower_npc.h" +#include "metatile_behavior.h" +#include "overworld.h" +#include "random.h" +#include "roamer.h" +#include "script.h" +#include "script_movement.h" +#include "sprite.h" +#include "sound.h" +#include "task.h" +#include "trainer_hill.h" +#include "wild_encounter.h" +#include "constants/battle_frontier.h" +#include "constants/event_objects.h" +#include "constants/field_effects.h" +#include "constants/layouts.h" +#include "constants/item.h" +#include "constants/map_types.h" +#include "constants/trainer_types.h" +#include "constants/songs.h" +#include "constants/vars.h" +#include "constants/wild_encounter.h" + + +#define sOverworldEncounterLevel trainerRange_berryTreeId +#define sAge playerCopyableMovement +#define sRoamerOutbreakStatus warpArrowSpriteId +#define OWE_NON_ROAMER_OUTBREAK 0 +#define OWE_MASS_OUTBREAK_INDEX ROAMER_COUNT + 1 +#define OWE_INVALID_ROAMER_OUTBREAK OWE_MASS_OUTBREAK_INDEX + 1 +#define OWE_MAX_ROAMERS UINT8_MAX - 2 + +#define OWE_FLAG_BIT (1 << 7) +#define OWE_SAVED_MOVEMENT_STATE_FLAG OWE_FLAG_BIT +#define OWE_NO_DESPAWN_FLAG OWE_FLAG_BIT + +#define OWE_SPAWNS_MAX 4 +#define OWE_SPAWN_DISTANCE_LAND 1 // A spawn cannot happen within this many tiles of the player position. +#define OWE_SPAWN_DISTANCE_WATER 3 // A spawn cannot happen within this many tiles of the player position (while surfing). +#define OWE_SPAWN_WIDTH_TOTAL 15 // Width of the on-screen spawn area in tiles. +#define OWE_SPAWN_HEIGHT_TOTAL 9 // Height of the on-screen spawn area in tiles. +#define OWE_SPAWN_WIDTH_RADIUS (OWE_SPAWN_WIDTH_TOTAL - 1) / 2 // Distance from center to left/right edge (not including center). +#define OWE_SPAWN_HEIGHT_RADIUS (OWE_SPAWN_HEIGHT_TOTAL - 1) / 2 // Distance from center to top/bottom edge (not including center). + +#define OWE_SPAWN_TIME_REPLACEMENT 240 // The number of frames before an existing spawn will be replaced with a new one (requires WE_OWE_SPAWN_REPLACEMENT). +#define OWE_SPAWN_TIME_LURE 0 +#define OWE_SPAWN_TIME_MINIMUM 30 // The minimum value the spawn wait time can be reset to. Prevents spawn attempts every frame. +#define OWE_SPAWN_TIME_PER_ACTIVE 30 // The number of frames that will be added to the countdown per currently active spawn. + +#define OWE_MON_SIGHT_WIDTH 3 +#define OWE_MON_SIGHT_LENGTH 4 +#define OWE_CHASE_RANGE 5 +#define OWE_RESTORED_MOVEMENT_FUNC_ID 10 + +#define OWE_NO_ENCOUNTER_SET 0xFF +#define OWE_INVALID_SPAWN_SLOT 0xFF + + +#if WE_OW_ENCOUNTERS == TRUE && ROAMER_COUNT > OWE_MAX_ROAMERS +#error "ROAMER_COUNT needs to be less than OWE_MAX_ROAMERS due to it being stored in the u8 field warpArrowSpriteId" +#endif + +#if OW_POKEMON_OBJECT_EVENTS == FALSE && WE_OW_ENCOUNTERS == TRUE +#error "OW_POKEMON_OBJECT_EVENTS needs to be TRUE in order for WE_OW_ENCOUNTERS to work." +#endif + + +static inline u32 GetLocalIdByOWESpawnSlot(u32 spawnSlot) +{ + return LOCALID_OW_ENCOUNTER_END - spawnSlot; +} + +static inline u32 GetSpawnSlotByOWELocalId(u32 localId) +{ + return LOCALID_OW_ENCOUNTER_END - localId; +} + +static inline u32 GetOWERoamerIndex(const struct ObjectEvent *owe) +{ + return owe->sRoamerOutbreakStatus & ~OWE_SAVED_MOVEMENT_STATE_FLAG; +} + +static inline bool32 HasSavedOWEMovementState(const struct ObjectEvent *owe) +{ + return owe->sRoamerOutbreakStatus & OWE_SAVED_MOVEMENT_STATE_FLAG; +} + +void SetSavedOWEMovementState(struct ObjectEvent *owe) +{ + owe->sRoamerOutbreakStatus |= OWE_SAVED_MOVEMENT_STATE_FLAG; +} + +void ClearSavedOWEMovementState(struct ObjectEvent *owe) +{ + owe->sRoamerOutbreakStatus &= ~OWE_SAVED_MOVEMENT_STATE_FLAG; +} + +static inline u32 GetOWEEncounterLevel(u32 level) +{ + return level & ~OWE_NO_DESPAWN_FLAG; +} + +static inline void SetOWEEncounterLevel(u32 *level, u32 newLevel) +{ + *level = (*level & OWE_NO_DESPAWN_FLAG) | (newLevel & ~OWE_NO_DESPAWN_FLAG); +} + +static inline bool32 HasOWENoDespawnFlag(const struct ObjectEvent *owe) +{ + return owe->sOverworldEncounterLevel & OWE_NO_DESPAWN_FLAG; +} + +static inline void SetOWENoDespawnFlag(u32 *level) +{ + *level |= OWE_NO_DESPAWN_FLAG; +} + +static inline bool32 ShouldSpawnWaterOWE(void) +{ + return TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING | PLAYER_AVATAR_FLAG_UNDERWATER); +} + +static bool32 CreateEnemyPartyOWE(u32 *speciesId, u32 *level, u32 *indexRoamerOutbreak, s32 x, s32 y); +static bool32 OWE_DoesOWERoamerExist(void); +static u32 GetOWERoamerStatusFromIndex(u32 indexRoamer); +static u32 GetOWERoamerOutbreakStatus(struct ObjectEvent *owe); +static bool32 StartWildBattleWithOWE_CheckRoamer(u32 indexRoamerOutbreak); +static bool32 StartWildBattleWithOWE_CheckBattleFrontier(u32 headerId); +static bool32 StartWildBattleWithOWE_CheckMassOutbreak(u32 indexRoamerOutbreak, u32 speciesId); +static bool32 StartWildBattleWithOWE_CheckDoubleBattle(struct ObjectEvent *owe, u32 headerId); +static bool32 CheckCuurentWildMonHeaderForOWE(bool32 shouldSpawnWaterMons); +static u32 GetOldestActiveOWESlot(bool32 forceRemove); +static u32 GetNextOWESpawnSlot(void); +static u32 GetSpeciesByOWESpawnSlot(u32 spawnSlot); +static bool32 TrySelectTileForOWE(s32* outX, s32* outY); +static void SetSpeciesInfoForOWE(u32 *speciesId, bool32 *isShiny, bool32 *isFemale, u32 *level, u32 *indexRoamerOutbreak, s32 x, s32 y); +static u32 GetGraphicsIdForOWE(u32 *speciesId, bool32 *isShiny, bool32 *isFemale, u32 *level, u32 *indexRoamerOutbreak, s32 x, s32 y); +static bool32 CheckCanRemoveOWE(u32 localId); +static bool32 CheckCanLoadOWE(u32 speciesId, bool32 isFemale, bool32 isShiny, s32 x, s32 y); +static bool32 CheckCanLoadOWE_Palette(u32 speciesId, bool32 isFemale, bool32 isShiny, s32 x, s32 y); +static bool32 CheckCanLoadOWE_Tiles(u32 speciesId, bool32 isFemale, bool32 isShiny, s32 x, s32 y); +static void SortOWEAges(void); +static u32 RemoveOldestGeneratedOWE(void); +static bool32 ShouldDespawnGeneratedForNewOWE(struct ObjectEvent *owe); +static void SetNewOWESpawnCountdown(void); +static void DoOWESpawnDespawnAnim(struct ObjectEvent *owe, bool32 animSpawn); +static enum SpawnDespawnTypeOWE GetOWESpawnDespawnAnimType(u32 metatileBehavior); +static void PlayOWECry(struct ObjectEvent *owe); +static struct ObjectEvent *GetOWEObjectEvent(void); +static bool32 OWE_ShouldPlayOWEFleeSound(struct ObjectEvent *owe); +static bool32 CheckRestrictedOWEMovementAtCoords(struct ObjectEvent *owe, s32 xNew, s32 yNew, enum Direction newDirection, enum Direction collisionDirection); +static bool32 CheckRestrictedOWEMovementMetatile(s32 xCurrent, s32 yCurrent, s32 xNew, s32 yNew); +static bool32 CheckRestrictedOWEMovementMap(struct ObjectEvent *owe, s32 xNew, s32 yNew); +static bool32 IsOWELineOfSightClear(struct ObjectEvent *player, enum Direction direction, u32 distance); +static enum Direction CheckOWEPathToPlayerFromCollision(struct ObjectEvent *owe, enum Direction newDirection); +static void Task_OWEApproachForBattle(u8 taskId); +static bool32 CheckValidOWESpecies(u32 speciesId); + +static EWRAM_DATA u8 sOWESpawnCountdown = 0; + +struct AgeSort +{ + u8 slot:4; + u8 age:4; +}; + + +void OverworldWildEncounters_CB(void) +{ + bool32 shouldSpawnWaterMons = ShouldSpawnWaterOWE(); + + if (ArePlayerFieldControlsLocked() || FlagGet(DN_FLAG_SEARCHING) || !CheckCuurentWildMonHeaderForOWE(shouldSpawnWaterMons)) + return; + + if (!WE_OW_ENCOUNTERS + || FlagGet(WE_OWE_FLAG_DISABLED) + || FlagGet(OW_FLAG_NO_ENCOUNTER) + || FlagGet(DN_FLAG_SEARCHING) + || (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS && !WE_OWE_BATTLE_PIKE) + || (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR && !WE_OWE_BATTLE_PYRAMID) + || InTrainerHillChallenge()) + { + if (sOWESpawnCountdown != OWE_NO_ENCOUNTER_SET) + { + DespwnAllOverworldWildEncounters(OWE_GENERATED, 0); + sOWESpawnCountdown = OWE_NO_ENCOUNTER_SET; + } + return; + } + else if (sOWESpawnCountdown == OWE_NO_ENCOUNTER_SET) + { + SetMinimumOWESpawnTimer(); + } + + if (sOWESpawnCountdown) + { + sOWESpawnCountdown--; + return; + } + + DespwnAllOverworldWildEncounters(OWE_GENERATED, WILD_CHECK_REPEL); + struct ObjectEvent* player = &gObjectEvents[gPlayerAvatar.objectEventId]; + if (player->currentCoords.x != player->previousCoords.x || player->currentCoords.y != player->previousCoords.y) + return; + + u32 spawnSlot = GetNextOWESpawnSlot(); + s32 x, y; + if (spawnSlot == OWE_INVALID_SPAWN_SLOT + || (shouldSpawnWaterMons && AreLegendariesInSootopolisPreventingEncounters()) + || !TrySelectTileForOWE(&x, &y)) + { + SetMinimumOWESpawnTimer(); + return; + } + + u32 speciesId = SPECIES_NONE; + bool32 isShiny = FALSE; + bool32 isFemale = FALSE; + u32 indexRoamerOutbreak = OWE_NON_ROAMER_OUTBREAK; + u32 localId = GetLocalIdByOWESpawnSlot(spawnSlot); + u32 level = MIN_LEVEL; + u32 graphicsId = GetGraphicsIdForOWE(&speciesId, &isShiny, &isFemale, &level, &indexRoamerOutbreak, x, y); + + if (speciesId == SPECIES_NONE + || !IsWildLevelAllowedByRepel(GetOWEEncounterLevel(level)) + || !IsAbilityAllowingEncounter(GetOWEEncounterLevel(level)) + || !CheckCanLoadOWE(speciesId, isFemale, isShiny, x, y)) + { + SetMinimumOWESpawnTimer(); + return; + } + + struct ObjectEventTemplate objectEventTemplate = { + .localId = localId, + .graphicsId = graphicsId, + .x = x - MAP_OFFSET, + .y = y - MAP_OFFSET, + .elevation = MapGridGetElevationAt(x, y), + .movementType = OWE_GetMovementTypeFromSpecies(speciesId), + .trainerType = TRAINER_TYPE_OW_WILD_ENCOUNTER, + .script = InteractWithOverworldWildEncounter, + }; + u32 objectEventId = GetObjectEventIdByLocalId(localId); + struct ObjectEvent *owe = &gObjectEvents[objectEventId]; + if (ShouldDespawnGeneratedForNewOWE(owe)) + RemoveObjectEvent(owe); + objectEventId = SpawnSpecialObjectEvent(&objectEventTemplate); + + assertf(objectEventId < OBJECT_EVENTS_COUNT, "could not spawn generated overworld encounter. too many object events exist") + { + SetMinimumOWESpawnTimer(); + return; + } + + owe = &gObjectEvents[objectEventId]; + owe->disableCoveringGroundEffects = TRUE; + owe->sOverworldEncounterLevel = level; + owe->sRoamerOutbreakStatus = indexRoamerOutbreak; + + enum Direction directions[4]; + memcpy(directions, gStandardDirections, sizeof directions); + ObjectEventTurn(owe, directions[Random() & 3]); + SetNewOWESpawnCountdown(); +} + +bool32 IsOverworldWildEncounter(struct ObjectEvent *owe, enum TypeOWE oweType) +{ + if (!IS_OW_MON_OBJ(owe)) + return FALSE; + + if (owe->trainerType != TRAINER_TYPE_OW_WILD_ENCOUNTER) + return FALSE; + + switch (oweType) + { + default: + case OWE_ANY: + return TRUE; + + case OWE_GENERATED: + return (owe->localId <= LOCALID_OW_ENCOUNTER_END && owe->localId > (LOCALID_OW_ENCOUNTER_END - OWE_SPAWNS_MAX)); + + case OWE_MANUAL: + return (owe->localId > LOCALID_OW_ENCOUNTER_END || owe->localId <= (LOCALID_OW_ENCOUNTER_END - OWE_SPAWNS_MAX)); + } +} + +void StartWildBattleWithOWE(void) +{ + u32 localId = gSpecialVar_LastTalked; + u32 objEventId = GetObjectEventIdByLocalId(localId); + u32 headerId = GetCurrentMapWildMonHeaderId(); + struct ObjectEvent *owe = &gObjectEvents[objEventId]; + u32 indexRoamerOutbreak = GetOWERoamerIndex(owe); + + assertf(objEventId < OBJECT_EVENTS_COUNT && IsOverworldWildEncounter(owe, OWE_ANY), "cannot start overworld wild enocunter as the selected object is invalid.\nlocalId: %d", localId) + { + UnlockPlayerFieldControls(); + UnfreezeObjectEvents(); + return; + } + + if (indexRoamerOutbreak && StartWildBattleWithOWE_CheckRoamer(GetOWERoamerOutbreakStatus(owe))) + return; + + u32 speciesId = OW_SPECIES(owe); + bool32 shiny = OW_SHINY(owe) ? TRUE : FALSE; + u32 gender = OW_FEMALE(owe) ? MON_FEMALE : MON_MALE; + u32 level = GetOWEEncounterLevel(owe->sOverworldEncounterLevel); + u32 personality; + + switch (gSpeciesInfo[speciesId].genderRatio) + { + case MON_MALE: + case MON_FEMALE: + case MON_GENDERLESS: + gender = gSpeciesInfo[speciesId].genderRatio; + } + + if (level < MIN_LEVEL || level > MAX_LEVEL) + level = MIN_LEVEL; + + ZeroEnemyPartyMons(); + personality = GetMonPersonality(speciesId, gender, NATURE_RANDOM, RANDOM_UNOWN_LETTER); + CreateMonWithIVs(&gEnemyParty[0], speciesId, level, personality, OTID_STRUCT_PLAYER_ID, USE_RANDOM_IVS); + GiveMonInitialMoveset(&gEnemyParty[0]); + SetMonData(&gEnemyParty[0], MON_DATA_IS_SHINY, &shiny); + + if (StartWildBattleWithOWE_CheckBattleFrontier(headerId)) + return; + + if (StartWildBattleWithOWE_CheckMassOutbreak(indexRoamerOutbreak, speciesId)) + return; + + if (StartWildBattleWithOWE_CheckDoubleBattle(owe, headerId)) + return; + + BattleSetup_StartWildBattle(); +} + +static bool32 CreateEnemyPartyOWE(u32 *speciesId, u32 *level, u32 *indexRoamerOutbreak, s32 x, s32 y) +{ + const struct WildPokemonInfo *wildMonInfo; + enum WildPokemonArea wildArea; + enum TimeOfDay timeOfDay; + u32 headerId = GetCurrentMapWildMonHeaderId(); + u32 metatileBehavior = MapGridGetMetatileBehaviorAt(x, y); + + if (headerId == HEADER_NONE) + { + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS) + { + headerId = GetBattlePikeWildMonHeaderId(); + timeOfDay = GetTimeOfDayForEncounters(headerId, WILD_AREA_LAND); + if (TryGenerateWildMon(gBattlePikeWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo, WILD_AREA_LAND, 0) != TRUE) + return FALSE; + else if (!TryGenerateBattlePikeWildMon(TRUE)) + return FALSE; + + return TRUE; + } + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) + { + headerId = gSaveBlock2Ptr->frontier.curChallengeBattleNum; + timeOfDay = GetTimeOfDayForEncounters(headerId, WILD_AREA_LAND); + if (TryGenerateWildMon(gBattlePyramidWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo, WILD_AREA_LAND, 0) != TRUE) + return FALSE; + + u32 id = GetMonData(&gEnemyParty[0], MON_DATA_SPECIES); + GenerateBattlePyramidWildMon(SPECIES_NONE); + SetMonData(&gEnemyParty[0], MON_DATA_LEVEL, &id); + return TRUE; + } + + return FALSE; + } + + if (MetatileBehavior_IsWaterWildEncounter(metatileBehavior)) + { + wildArea = WILD_AREA_WATER; + timeOfDay = GetTimeOfDayForEncounters(headerId, wildArea); + wildMonInfo = gWildMonHeaders[headerId].encounterTypes[timeOfDay].waterMonsInfo; + } + else + { + wildArea = WILD_AREA_LAND; + timeOfDay = GetTimeOfDayForEncounters(headerId, wildArea); + wildMonInfo = gWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo; + } + + if (wildMonInfo == NULL) + return FALSE; + + /* + These functions perform checks of various encounter types in the following order: + 1. Attempt to generate a Roamer Encounter + 2. Attempt to generate a Feebas Encounter + 3. Attempt to generate a Mass Outbreak Encounter + 4. Attempt to generate a Standard Wild Encounter + + The structure of this statement ensures that only one of these encounter types can succeed per call, + with the resultant wild mon being created in gEnemyParty[0]. + If none of these checks succeed, speciesId is set to SPECIES_NONE and FALSE is returned. + */ + + if (*indexRoamerOutbreak != OWE_INVALID_ROAMER_OUTBREAK) + { + if (TryStartRoamerEncounter() && !OWE_DoesOWERoamerExist()) + { + *indexRoamerOutbreak = GetOWERoamerStatusFromIndex(gEncounteredRoamerIndex); + return TRUE; + } + else if (WE_OWE_FEEBAS_SPOTS && MetatileBehavior_IsWaterWildEncounter(metatileBehavior) && CheckFeebasAtCoords(x, y)) + { + CreateWildMon(gWildFeebas.species, ChooseWildMonLevel(&gWildFeebas, 0, WILD_AREA_FISHING)); + if (WE_OWE_PREVENT_FEEBAS_DESPAWN) + SetOWENoDespawnFlag(level); + + return TRUE; + } + else if (DoMassOutbreakEncounterTest() && MetatileBehavior_IsLandWildEncounter(metatileBehavior)) + { + SetUpMassOutbreakEncounter(0); + *indexRoamerOutbreak = OWE_MASS_OUTBREAK_INDEX; + return TRUE; + } + else + { + return TryGenerateWildMon(wildMonInfo, wildArea, 0); + } + } + + return TryGenerateWildMon(wildMonInfo, wildArea, 0); +} + +static bool32 OWE_DoesOWERoamerExist(void) +{ + for (u32 i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + struct ObjectEvent *owe = &gObjectEvents[i]; + if (IsOverworldWildEncounter(owe, OWE_ANY) && GetOWERoamerOutbreakStatus(owe) == gEncounteredRoamerIndex) + return TRUE; + } + + return FALSE; +} + +static u32 GetOWERoamerStatusFromIndex(u32 indexRoamer) +{ + if (indexRoamer < ROAMER_COUNT) + return indexRoamer + 1; + + return indexRoamer; +} + +static u32 GetOWERoamerOutbreakStatus(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return OWE_INVALID_ROAMER_OUTBREAK; + + u32 status = GetOWERoamerIndex(owe); + if (status == OWE_NON_ROAMER_OUTBREAK || status == OWE_MASS_OUTBREAK_INDEX) + { + return OWE_INVALID_ROAMER_OUTBREAK; + } + + return status - 1; +} + +static bool32 StartWildBattleWithOWE_CheckRoamer(u32 indexRoamerOutbreak) +{ + if (indexRoamerOutbreak < ROAMER_COUNT + && IsRoamerAt(indexRoamerOutbreak, gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum)) + { + CreateRoamerMonInstance(indexRoamerOutbreak); + gEncounteredRoamerIndex = indexRoamerOutbreak; + BattleSetup_StartRoamerBattle(); + return TRUE; + } + + return FALSE; +} + +static bool32 StartWildBattleWithOWE_CheckBattleFrontier(u32 headerId) +{ + if (headerId == HEADER_NONE) + { + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS) + { + TryGenerateBattlePikeWildMon(FALSE); + BattleSetup_StartBattlePikeWildBattle(); + return TRUE; + } + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) + { + u32 id = GetMonData(&gEnemyParty[0], MON_DATA_LEVEL); + u32 species = GetMonData(&gEnemyParty[0], MON_DATA_SPECIES); + SetMonData(&gEnemyParty[0], MON_DATA_SPECIES, &id); + if (!BATTLE_PYRAMID_RANDOM_ENCOUNTERS) + species = SPECIES_NONE; + GenerateBattlePyramidWildMon(species); + BattleSetup_StartWildBattle(); + return TRUE; + } + } + + return FALSE; +} + +static bool32 StartWildBattleWithOWE_CheckMassOutbreak(u32 indexRoamerOutbreak, u32 speciesId) +{ + if (indexRoamerOutbreak == OWE_MASS_OUTBREAK_INDEX + && gSaveBlock1Ptr->outbreakPokemonSpecies == speciesId + && gSaveBlock1Ptr->location.mapNum == gSaveBlock1Ptr->outbreakLocationMapNum + && gSaveBlock1Ptr->location.mapGroup == gSaveBlock1Ptr->outbreakLocationMapGroup) + { + for (u32 i = 0; i < MAX_MON_MOVES; i++) + SetMonMoveSlot(&gEnemyParty[0], gSaveBlock1Ptr->outbreakPokemonMoves[i], i); + + BattleSetup_StartWildBattle(); + return TRUE; + } + + return FALSE; +} + +static bool32 StartWildBattleWithOWE_CheckDoubleBattle(struct ObjectEvent *owe, u32 headerId) +{ + enum WildPokemonArea wildArea; + enum TimeOfDay timeOfDay; + const struct WildPokemonInfo *wildMonInfo; + u32 metatileBehavior = MapGridGetMetatileBehaviorAt(owe->currentCoords.x, owe->currentCoords.y); + + if (TryDoDoubleWildBattle()) + { + struct Pokemon mon1 = gEnemyParty[0]; + + if (MetatileBehavior_IsWaterWildEncounter(metatileBehavior)) + { + wildArea = WILD_AREA_WATER; + timeOfDay = GetTimeOfDayForEncounters(headerId, wildArea); + wildMonInfo = gWildMonHeaders[headerId].encounterTypes[timeOfDay].waterMonsInfo; + } + else + { + wildArea = WILD_AREA_LAND; + timeOfDay = GetTimeOfDayForEncounters(headerId, wildArea); + wildMonInfo = gWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo; + } + + if (TryGenerateWildMon(wildMonInfo, wildArea, WILD_CHECK_REPEL | WILD_CHECK_KEEN_EYE)) + { + gEnemyParty[1] = mon1; + BattleSetup_StartDoubleWildBattle(); + return TRUE; + } + } + + return FALSE; +} + +void SetInstantOWESpawnTimer(void) +{ + if (!WE_OW_ENCOUNTERS) + return; + + sOWESpawnCountdown = 0; +} + +void SetMinimumOWESpawnTimer(void) +{ + if (!WE_OW_ENCOUNTERS) + return; + + sOWESpawnCountdown = OWE_SPAWN_TIME_MINIMUM; + if (LURE_STEP_COUNT && GetNumberOfActiveOWEs(OWE_GENERATED) < OWE_SPAWNS_MAX) + sOWESpawnCountdown = OWE_SPAWN_TIME_LURE; +} + +void TryTriggerOverworldWilEncounter(struct ObjectEvent *obstacle, struct ObjectEvent *collider) +{ + if (WE_OWE_REPEL_DEXNAV_COLLISION && (FlagGet(DN_FLAG_SEARCHING) || REPEL_STEP_COUNT)) + return; + + bool32 playerIsCollider = (collider->isPlayer && IsOverworldWildEncounter(obstacle, OWE_ANY)); + bool32 playerIsObstacle = (obstacle->isPlayer && IsOverworldWildEncounter(collider, OWE_ANY)); + + if (!(playerIsCollider || playerIsObstacle)) + return; + + struct ObjectEvent *wildMon = playerIsCollider ? obstacle : collider; + u32 indexRoamerOutbreak = GetOWERoamerIndex(wildMon); + if (indexRoamerOutbreak + && indexRoamerOutbreak < OWE_MASS_OUTBREAK_INDEX + && !IsRoamerAt(GetOWERoamerOutbreakStatus(wildMon), gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum)) + { + RemoveObjectEvent(wildMon); + return; + } + + gSpecialVar_LastTalked = wildMon->localId; + gSpecialVar_0x8004 = OW_SPECIES(wildMon); + gSelectedObjectEvent = GetObjectEventIdByLocalId(wildMon->localId); + + // Stop the bobbing animation. + if (wildMon->movementActionId >= MOVEMENT_ACTION_WALK_IN_PLACE_NORMAL_DOWN && wildMon->movementActionId <= MOVEMENT_ACTION_WALK_IN_PLACE_NORMAL_RIGHT) + ClearObjectEventMovement(wildMon, &gSprites[wildMon->spriteId]); + + ScriptContext_SetupScript(InteractWithOverworldWildEncounter); +} + +bool32 ShouldRunDefaultOWEScript(u32 objectEventId) +{ + struct ObjectEvent *owe = &gObjectEvents[objectEventId]; + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return FALSE; + + if (IsOverworldWildEncounter(owe, OWE_MANUAL) + && GetObjectEventScriptPointerByObjectEventId(objectEventId) != InteractWithOverworldWildEncounter + && GetObjectEventScriptPointerByObjectEventId(objectEventId) != NULL) + return FALSE; + + gSpecialVar_0x8004 = OW_SPECIES(owe); + return TRUE; +} + +static bool32 CheckCuurentWildMonHeaderForOWE(bool32 shouldSpawnWaterMons) +{ + u32 headerId = GetCurrentMapWildMonHeaderId(); + enum TimeOfDay timeOfDay; + + if (headerId == HEADER_NONE) + { + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS) + { + headerId = GetBattlePikeWildMonHeaderId(); + timeOfDay = GetTimeOfDayForEncounters(headerId, WILD_AREA_LAND); + return gBattlePikeWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo != NULL; + } + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) + { + headerId = gSaveBlock2Ptr->frontier.curChallengeBattleNum; + timeOfDay = GetTimeOfDayForEncounters(headerId, WILD_AREA_LAND); + return gBattlePyramidWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo != NULL; + } + return FALSE; + } + + if (shouldSpawnWaterMons) + { + timeOfDay = GetTimeOfDayForEncounters(headerId, WILD_AREA_WATER); + return gWildMonHeaders[headerId].encounterTypes[timeOfDay].waterMonsInfo != NULL; + } + + timeOfDay = GetTimeOfDayForEncounters(headerId, WILD_AREA_LAND); + return gWildMonHeaders[headerId].encounterTypes[timeOfDay].landMonsInfo != NULL; +} + +static u32 GetOldestActiveOWESlot(bool32 forceRemove) +{ + struct ObjectEvent *slotMon, *oldest = &gObjectEvents[GetObjectEventIdByLocalId(LOCALID_OW_ENCOUNTER_END)]; + u32 spawnSlot; + + for (spawnSlot = 0; spawnSlot < OWE_SPAWNS_MAX; spawnSlot++) + { + slotMon = &gObjectEvents[GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(spawnSlot))]; + if (OW_SPECIES(slotMon) != SPECIES_NONE && (!HasOWENoDespawnFlag(slotMon) || forceRemove == TRUE)) + { + oldest = slotMon; + break; + } + } + + if (spawnSlot >= OWE_SPAWNS_MAX) + return OWE_INVALID_SPAWN_SLOT; + + for (spawnSlot = 0; spawnSlot < OWE_SPAWNS_MAX; spawnSlot++) + { + slotMon = &gObjectEvents[GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(spawnSlot))]; + if (OW_SPECIES(slotMon) != SPECIES_NONE && (!HasOWENoDespawnFlag(slotMon) || forceRemove == TRUE)) + { + if (slotMon->sAge > oldest->sAge) + oldest = slotMon; + } + } + + return GetSpawnSlotByOWELocalId(oldest->localId); +} + +static u32 GetNextOWESpawnSlot(void) +{ + u32 spawnSlot; + + // All mon slots are in use + if (GetNumberOfActiveOWEs(OWE_GENERATED) >= OWE_SPAWNS_MAX) + { + if (WE_OWE_SPAWN_REPLACEMENT) + { + // Cycle through so we remove the oldest mon first + spawnSlot = GetOldestActiveOWESlot(FALSE); + if (spawnSlot == OWE_INVALID_SPAWN_SLOT) + return OWE_INVALID_SPAWN_SLOT; + } + else + { + return OWE_INVALID_SPAWN_SLOT; + } + } + else + { + for (spawnSlot = 0; spawnSlot < OWE_SPAWNS_MAX; spawnSlot++) + { + if (GetSpeciesByOWESpawnSlot(spawnSlot) == SPECIES_NONE) + break; + } + } + + return spawnSlot; +} + +static u32 GetSpeciesByOWESpawnSlot(u32 spawnSlot) +{ + u32 objEventId = GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(spawnSlot)); + struct ObjectEvent *owe = &gObjectEvents[objEventId]; + + if (objEventId >= OBJECT_EVENTS_COUNT) + return SPECIES_NONE; + + return OW_SPECIES(owe); +} + +static bool32 TrySelectTileForOWE(s32* outX, s32* outY) +{ + u32 elevation; + u32 tileBehavior; + s16 playerX, playerY; + s16 x, y; + u32 closeDistance; + bool32 isEncounterTile = FALSE; + + // Spawn further away when surfing + if (ShouldSpawnWaterOWE()) + closeDistance = OWE_SPAWN_DISTANCE_WATER; + else + closeDistance = OWE_SPAWN_DISTANCE_LAND; + + // Select a random tile in [-7, -4] [7, 4] range + // Make sure is not directly next to player + // Can we make get random tile its own function for use elsewhere in the codebase? + // Have defines used and then replace MAP_METATILE_VIEW_X/Y with them + do + { + x = (s16)(Random() % OWE_SPAWN_WIDTH_TOTAL) - OWE_SPAWN_WIDTH_RADIUS; + y = (s16)(Random() % OWE_SPAWN_HEIGHT_TOTAL) - OWE_SPAWN_HEIGHT_RADIUS; + } + while (abs(x) <= closeDistance && abs(y) <= closeDistance); + + // We won't spawn mons in the immediate facing direction + // (stops mons spawning in as I'm running in a straight line) + switch (GetPlayerFacingDirection()) + { + case DIR_NORTH: + if(x == 0 && y < 0) + x = -1; + break; + case DIR_SOUTH: + if(x == 0 && y > 0) + x = 1; + break; + case DIR_EAST: + if(y == 0 && x > 0) + y = -1; + break; + case DIR_WEST: + if(y == 0 && x < 0) + y = 1; + break; + default: + break; + } + + PlayerGetDestCoords(&playerX, &playerY); + x += playerX; + y += playerY; + + elevation = MapGridGetElevationAt(x, y); + + if (gMapHeader.mapLayoutId != LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) + { + if (!AreCoordsInsidePlayerMap(x, y)) + return FALSE; + } + else + { + if (x < 0 || x >= 32 || y < 0 || y >= 32) + return FALSE; + } + + // These elevations cause weird interactions, so spawns are prevented. + if (elevation == ELEVATION_TRANSITION || elevation == ELEVATION_MULTI_LEVEL) + return FALSE; + + tileBehavior = MapGridGetMetatileBehaviorAt(x, y); + if (ShouldSpawnWaterOWE() && MetatileBehavior_IsWaterWildEncounter(tileBehavior)) + isEncounterTile = TRUE; + + if (!ShouldSpawnWaterOWE() && (MetatileBehavior_IsLandWildEncounter(tileBehavior) || MetatileBehavior_IsIndoorEncounter(tileBehavior))) + isEncounterTile = TRUE; + + if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PIKE_ROOM_WILD_MONS + || gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_FLOOR) + isEncounterTile = TRUE; + + if (isEncounterTile && !MapGridGetCollisionAt(x, y)) + { + *outX = x; + *outY = y; + + if (GetObjectEventIdByPosition(x, y, 0) == OBJECT_EVENTS_COUNT) + return TRUE; + } + + return FALSE; +} + +static void SetSpeciesInfoForOWE(u32 *speciesId, bool32 *isShiny, bool32 *isFemale, u32 *level, u32 *indexRoamerOutbreak, s32 x, s32 y) +{ + u32 personality; + + if (!CreateEnemyPartyOWE(speciesId, level, indexRoamerOutbreak, x, y)) + { + ZeroEnemyPartyMons(); + *speciesId = SPECIES_NONE; + return; + } + + *speciesId = GetMonData(&gEnemyParty[0], MON_DATA_SPECIES); + SetOWEEncounterLevel(level, GetMonData(&gEnemyParty[0], MON_DATA_LEVEL)); + personality = GetMonData(&gEnemyParty[0], MON_DATA_PERSONALITY); + + if (*speciesId == SPECIES_UNOWN) + *speciesId = GetUnownSpeciesId(personality); + + *isShiny = ComputePlayerShinyOdds(personality, READ_OTID_FROM_SAVE); + if (GetGenderFromSpeciesAndPersonality(*speciesId, personality) == MON_FEMALE) + *isFemale = TRUE; + else + *isFemale = FALSE; + + if (WE_OWE_PREVENT_SHINY_DESPAWN && *isShiny) + SetOWENoDespawnFlag(level); + + ZeroEnemyPartyMons(); +} + +static u32 GetGraphicsIdForOWE(u32 *speciesId, bool32 *isShiny, bool32 *isFemale, u32 *level, u32 *indexRoamerOutbreak, s32 x, s32 y) +{ + SetSpeciesInfoForOWE(speciesId, isShiny, isFemale, level, indexRoamerOutbreak, x, y); + assertf(CheckValidOWESpecies(*speciesId), "invalid generated overworld encounter\nspecies: %d\ncheck if valid wild mon header exists", speciesId, x, y); + u32 graphicsId = *speciesId + OBJ_EVENT_MON; + + if (*isFemale) + graphicsId += OBJ_EVENT_MON_FEMALE; + + if (*isShiny) + graphicsId += OBJ_EVENT_MON_SHINY; + + return graphicsId; +} + +static bool32 CheckCanRemoveOWE(u32 localId) +{ + if (!WE_OW_ENCOUNTERS) + return FALSE; + + if (!GetNumberOfActiveOWEs(OWE_GENERATED)) + return FALSE; + + if (!(localId <= (LOCALID_OW_ENCOUNTER_END - OWE_SPAWNS_MAX + 1) || localId > LOCALID_OW_ENCOUNTER_END)) + return FALSE; + + return TRUE; +} + +static bool32 CheckCanLoadOWE(u32 speciesId, bool32 isFemale, bool32 isShiny, s32 x, s32 y) +{ + assertf(CheckCanLoadOWE_Palette(speciesId, isFemale, isShiny, x, y), "could not load palette for overworld encounter\nspecies: %d\nfemale: %d\nshiny: %d\ncoords: %d %d", speciesId, isFemale, isShiny, x, y) + { + return FALSE; + } + + assertf(CheckCanLoadOWE_Tiles(speciesId, isFemale, isShiny, x, y), "could not load sprite tiles for overworld encounter\nspecies: %d\nfemale: %d\nshiny: %d\ncoords: %d %d", speciesId, isFemale, isShiny, x, y) + { + return FALSE; + } + + return TRUE; +} + +static bool32 CheckCanLoadOWE_Palette(u32 speciesId, bool32 isFemale, bool32 isShiny, s32 x, s32 y) +{ + u32 numFreePalSlots = CountFreePaletteSlots(); + u32 tag = speciesId + OBJ_EVENT_MON + (isShiny ? OBJ_EVENT_MON_SHINY : 0); + +#if P_GENDER_DIFFERENCES + if (isFemale && gSpeciesInfo[speciesId].overworldShinyPaletteFemale != NULL) + tag += OBJ_EVENT_MON_FEMALE; +#endif + + // We need at least 2 pal slots open. One for the object and one for the spawn field effect. + // Add this and tiles to seperate graphics check function + if (numFreePalSlots == 1) + { + u32 metatileBehavior = MapGridGetMetatileBehaviorAt(x, y); + struct SpritePalette palette = GetOWESpawnDespawnAnimFldEffPalette(GetOWESpawnDespawnAnimType(metatileBehavior)); + // If the mon's palette or field effect palette isn't already loaded, don't spawn. + // Include check if female or shiny mon is loaded and use that tag if possible + if (IndexOfSpritePaletteTag(tag) == 0xFF && IndexOfSpritePaletteTag(palette.tag) == 0xFF) + return FALSE; + } + else if (numFreePalSlots == 0) + { + return FALSE; + } + + return TRUE; +} +#define OWE_FIELD_EFFECT_TILE_NUM 16 // Number of tiiles to add for field effect spawning +static bool32 CheckCanLoadOWE_Tiles(u32 speciesId, bool32 isFemale, bool32 isShiny, s32 x, s32 y) +{ + u32 tag = speciesId + OBJ_EVENT_MON + (isShiny ? OBJ_EVENT_MON_SHINY : 0); + // const struct ObjectEventGraphicsInfo *graphicsInfo = SpeciesToGraphicsInfo(speciesId, isShiny, isFemale); + const struct ObjectEventGraphicsInfo *graphicsInfo = GetObjectEventGraphicsInfo(tag); + tag = LoadSheetGraphicsInfo(graphicsInfo, tag, NULL); + u32 tileCount = graphicsInfo->size / TILE_SIZE_4BPP; + if (OW_GFX_COMPRESS) + { + // If tiles are already existing return early, spritesheet is loaded when compressed + if (IndexOfSpriteTileTag(tag) != 0xFF) + { + DebugPrintf("\n\nALREADY LOADED\nSpecies: %S", GetSpeciesName(speciesId)); + return TRUE; + } + + u32 frames = graphicsInfo->anims == sAnimTable_Following_Asym ? 8 : 6; + tileCount *= frames; + } + + tileCount += OWE_FIELD_EFFECT_TILE_NUM; + if (!CanAllocSpriteTiles(tileCount)) + { + DebugPrintf("\n\nNO SPAWN\nSpecies: %S\nSheet Tile Count: %d", GetSpeciesName(speciesId), tileCount); + return FALSE; + } + + DebugPrintf("\n\nSPAWN\nSpecies: %S\nSheet Tile Count: %d", GetSpeciesName(speciesId), tileCount); + FreeSpriteTilesByTag(tag); + return TRUE; +} +#undef OWE_FIELD_EFFECT_TILE_NUM + +static void SortOWEAges(void) +{ + struct ObjectEvent *slotMon; + struct AgeSort array[OWE_SPAWNS_MAX]; + struct AgeSort current; + u32 numActive = GetNumberOfActiveOWEs(OWE_GENERATED); + u32 count = 0; + s32 i, j; + + if (OWE_SPAWNS_MAX <= 1) + return; + + for (i = 0; i < OWE_SPAWNS_MAX; i++) + { + slotMon = &gObjectEvents[GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(i))]; + if (OW_SPECIES(slotMon) != SPECIES_NONE) + { + array[count].slot = i; + array[count].age = slotMon->sAge; + count++; + } + if (count == numActive) + break; + } + + for (i = 1; i < numActive; i++) + { + current = array[i]; + j = i - 1; + + while (j >= 0 && array[j].age < current.age) + { + array[j + 1] = array[j]; + j--; + } + + array[j + 1] = current; + } + + array[0].age = numActive; + slotMon = &gObjectEvents[GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(array[0].slot))]; + slotMon->sAge = numActive; + + for (i = 1; i < numActive; i++) + { + slotMon = &gObjectEvents[GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(array[i].slot))]; + array[i].age = array[i - 1].age - 1; + slotMon->sAge = array[i].age; + } +} + +void OnOverworldWildEncounterSpawn(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return; + + if (IsOverworldWildEncounter(owe, OWE_GENERATED)) + SortOWEAges(); + + DoOWESpawnDespawnAnim(owe, TRUE); +} + +void OnOverworldWildEncounterDespawn(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return; + + owe->sOverworldEncounterLevel = 0; + owe->sAge = 0; + owe->sRoamerOutbreakStatus = 0; + + DoOWESpawnDespawnAnim(owe, FALSE); +} + +bool32 IsOWEDespawnExempt(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return FALSE; + + if (HasOWENoDespawnFlag(owe) && AreCoordsInsidePlayerMap(owe->currentCoords.x, owe->currentCoords.y)) + return TRUE; + + owe->offScreen = TRUE; + return FALSE; +} + +bool32 DespawnOWEDueToNPCCollision(struct ObjectEvent *curObject, struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(curObject, OWE_GENERATED) || IsOverworldWildEncounter(owe, OWE_ANY) || owe->isPlayer) + return FALSE; + + RemoveObjectEvent(curObject); + return TRUE; +} + +u32 DespawnOWEDueToTrainerSight(u32 collision, s32 x, s32 y) +{ + if (!(collision & (1 << (COLLISION_OBJECT_EVENT - 1)))) + return collision; + + struct ObjectEvent *owe = &gObjectEvents[GetObjectEventIdByXY(x, y)]; + if (!IsOverworldWildEncounter(owe, OWE_GENERATED)) + return collision; + + RemoveObjectEvent(owe); + return collision & (1 << (COLLISION_OBJECT_EVENT - 1)); +} + +void DespwnAllOverworldWildEncounters(enum TypeOWE oweType, u32 flags) +{ + s32 dx = 0, dy = 0; + + if (gCamera.active) + { + dx = gCamera.x; + dy = gCamera.y; + } + + for (u32 i = 0; i < OBJECT_EVENTS_COUNT; ++i) + { + struct ObjectEvent *owe = &gObjectEvents[i]; + + if (!owe->active) + continue; + + if (!IsOverworldWildEncounter(owe, oweType)) + continue; + + if (flags & WILD_CHECK_REPEL) + { + if (!REPEL_STEP_COUNT) + continue; + + if (HasOWENoDespawnFlag(owe)) + continue; + + if (IsWildLevelAllowedByRepel(GetOWEEncounterLevel(owe->sOverworldEncounterLevel))) + continue; + } + + UpdateObjectEventCoords(owe, dx, dy); + RemoveObjectEvent(owe); + } +} + +bool32 TryAndDespawnOldestGeneratedOWE_Object(u32 localId, u8 *objectEventId) +{ + // does CheckCanRemoveOWE need to be used in TryAndDespawnOldestGeneratedOWE_Palette + if (CheckCanRemoveOWE(localId)) + { + *objectEventId = RemoveOldestGeneratedOWE(); + if (*objectEventId == OBJECT_EVENTS_COUNT) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +void TryAndDespawnOldestGeneratedOWE_Palette(void) +{ + // Should have similar naming convention for these despawn functions based on Num Object Events, Pals & Tiles + if (WE_OW_ENCOUNTERS && CountFreePaletteSlots() < 2) + { + u32 count = GetNumberOfActiveOWEs(OWE_GENERATED); + + if (count > 0) + { + for (; count > 0; count--) + { + RemoveOldestGeneratedOWE(); + if (CountFreePaletteSlots() >= 2) + break; + } + } + } +} + +void DespawnOWEOnBattleStart(void) +{ + struct ObjectEvent *owe = &gObjectEvents[GetObjectEventIdByLocalId(gSpecialVar_LastTalked)]; + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return; + + if (IsOverworldWildEncounter(owe, OWE_MANUAL)) + FlagSet(GetObjectEventFlagIdByLocalIdAndMap(owe->localId, owe->mapNum, owe->mapGroup)); + + RemoveObjectEvent(owe); + SetNewOWESpawnCountdown(); + gSpecialVar_LastTalked = LOCALID_NONE; +} + +void TryDespawnOWEsCrossingMapConnection(void) +{ + if (gMain.callback2 != CB2_Overworld) + return; + + if (!WE_OWE_DESPAWN_ON_ENTER_TOWN) + return; + + if (gMapHeader.mapType != MAP_TYPE_CITY && gMapHeader.mapType != MAP_TYPE_TOWN) + return; + + if (WE_OWE_DESPAWN_SOUND) + PlaySE(SE_FLEE); + + DespwnAllOverworldWildEncounters(OWE_ANY, 0); +} + +static u32 RemoveOldestGeneratedOWE(void) +{ + u32 oldestSlot = GetOldestActiveOWESlot(TRUE); + + if (oldestSlot == OWE_INVALID_SPAWN_SLOT) + return OBJECT_EVENTS_COUNT; + + u32 objectEventId = GetObjectEventIdByLocalId(GetLocalIdByOWESpawnSlot(oldestSlot)); + RemoveObjectEvent(&gObjectEvents[objectEventId]); + return objectEventId; +} + +static bool32 ShouldDespawnGeneratedForNewOWE(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_GENERATED)) + return FALSE; + + return WE_OWE_SPAWN_REPLACEMENT && GetNumberOfActiveOWEs(OWE_GENERATED) >= OWE_SPAWNS_MAX; +} + +static void SetNewOWESpawnCountdown(void) +{ + u32 numActive = GetNumberOfActiveOWEs(OWE_GENERATED); + + if (WE_OWE_SPAWN_REPLACEMENT && numActive >= OWE_SPAWNS_MAX) + sOWESpawnCountdown = OWE_SPAWN_TIME_REPLACEMENT; + else if (LURE_STEP_COUNT && numActive < OWE_SPAWNS_MAX) + sOWESpawnCountdown = OWE_SPAWN_TIME_LURE; + else + sOWESpawnCountdown = OWE_SPAWN_TIME_MINIMUM + (OWE_SPAWN_TIME_PER_ACTIVE * numActive); +} + +static void DoOWESpawnDespawnAnim(struct ObjectEvent *owe, bool32 animSpawn) +{ + if (gMain.callback2 != CB2_Overworld) + return; + + enum SpawnDespawnTypeOWE spawnAnimType; + bool32 isShiny = OW_SHINY(owe) ? TRUE : FALSE; + + if (animSpawn) + PlayOWECry(owe); + + if (!animSpawn && OWE_ShouldPlayOWEFleeSound(owe)) + PlaySE(SE_FLEE); + + if (WE_OWE_SHINY_SPARKLE && isShiny && animSpawn) + { + PlaySE(SE_SHINY); + spawnAnimType = OWE_SPAWN_ANIM_SHINY; + } + else + { + u32 metatileBehavior = MapGridGetMetatileBehaviorAt(owe->currentCoords.x, owe->currentCoords.y); + spawnAnimType = GetOWESpawnDespawnAnimType(metatileBehavior); + } + MovementAction_OverworldEncounterSpawn(spawnAnimType, owe); +} + +static enum SpawnDespawnTypeOWE GetOWESpawnDespawnAnimType(u32 metatileBehavior) +{ + if (MetatileBehavior_IsPokeGrass(metatileBehavior) || MetatileBehavior_IsAshGrass(metatileBehavior)) + return OWE_SPAWN_ANIM_GRASS; + else if (MetatileBehavior_IsLongGrass(metatileBehavior)) + return OWE_SPAWN_ANIM_LONG_GRASS; + else if (MetatileBehavior_IsSurfableFishableWater(metatileBehavior) && gMapHeader.mapType != MAP_TYPE_UNDERWATER) + return OWE_SPAWN_ANIM_WATER; + else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_UNDERWATER)) + return OWE_SPAWN_ANIM_UNDERWATER; + else + return OWE_SPAWN_ANIM_CAVE; +} + +static void PlayOWECry(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return; + + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + u32 speciesId = OW_SPECIES(owe); + s32 distanceX = owe->currentCoords.x - player->currentCoords.x; + s32 distanceY = owe->currentCoords.y - player->currentCoords.y; + u32 distanceMax = OWE_SPAWN_WIDTH_RADIUS + OWE_SPAWN_HEIGHT_RADIUS; + u32 distance; + u32 volume; + s32 pan; + + if (distanceX > OWE_SPAWN_WIDTH_RADIUS) + distanceX = OWE_SPAWN_WIDTH_RADIUS; + else if (distanceX < -OWE_SPAWN_WIDTH_RADIUS) + distanceX = -OWE_SPAWN_WIDTH_RADIUS; + + distanceY = abs(distanceY); + if (distanceY > OWE_SPAWN_HEIGHT_RADIUS) + distanceY = OWE_SPAWN_HEIGHT_RADIUS; + + distance = abs(distanceX) + distanceY; + if (distance > distanceMax) + distance = distanceMax; + + volume = 80 - (distance * (80 - 50)) / distanceMax; + pan = 212 + ((distanceX + OWE_SPAWN_WIDTH_RADIUS) * (300 - 212)) / (2 * OWE_SPAWN_WIDTH_RADIUS); + + PlayCry_NormalNoDucking(speciesId, pan, volume, CRY_PRIORITY_AMBIENT); +} + +static struct ObjectEvent *GetOWEObjectEvent(void) +{ + u32 numActive = GetNumberOfActiveOWEs(OWE_ANY); + u32 randomIndex; + u32 counter = 0; + struct ObjectEvent *owe; + + if (numActive) + randomIndex = Random() % numActive; + else + return NULL; + + for (u32 i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + owe = &gObjectEvents[i]; + if (IsOverworldWildEncounter(owe, OWE_ANY)) + { + if (counter >= randomIndex) + return owe; + else + counter++; + } + } + return NULL; +} + +static bool32 OWE_ShouldPlayOWEFleeSound(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY) || OW_SPECIES(owe) == SPECIES_NONE) + return FALSE; + + if (!AreCoordsInsidePlayerMap(owe->currentCoords.x, owe->currentCoords.y)) + return FALSE; + + if (ShouldDespawnGeneratedForNewOWE(owe)) + return FALSE; + + if (owe->offScreen) + return FALSE; + + return WE_OWE_DESPAWN_SOUND; +} + +#define sTypeFuncId data[1] // Same as in src/event_object_movement.c +#define sJumpTimer sprite->data[7] // Same as in src/event_object_movement.c +void RestoreSavedOWEBehaviorState(struct ObjectEvent *owe, struct Sprite *sprite) +{ + if (IsOverworldWildEncounter(owe, OWE_ANY) && HasSavedOWEMovementState(owe)) + { + sprite->sTypeFuncId = OWE_RESTORED_MOVEMENT_FUNC_ID; + if (owe->movementType == MOVEMENT_TYPE_APPROACH_PLAYER_OWE) + sJumpTimer = (Random() % (OWE_APPROACH_JUMP_TIMER_MAX - OWE_APPROACH_JUMP_TIMER_MIN)) + OWE_APPROACH_JUMP_TIMER_MIN; + } +} +#undef sTypeFuncId +#undef sJumpTimer + +// Returns TRUE if movement is restricted. +bool32 CheckRestrictedOWEMovement(struct ObjectEvent *owe, enum Direction direction) +{ + if (GetCollisionInDirection(owe, direction)) + return TRUE; + + if (CanAwareOWESeePlayer(owe) && WE_OWE_UNRESTRICT_SIGHT) + return FALSE; + + s32 xCurrent = owe->currentCoords.x; + s32 yCurrent = owe->currentCoords.y; + s32 xNew = xCurrent + gDirectionToVectors[direction].x; + s32 yNew = yCurrent + gDirectionToVectors[direction].y; + + if (CheckRestrictedOWEMovementMetatile(xCurrent, yCurrent, xNew, yNew)) + return TRUE; + + if (CheckRestrictedOWEMovementMap(owe, xNew, yNew)) + return TRUE; + + return FALSE; +} + +static bool32 CheckRestrictedOWEMovementAtCoords(struct ObjectEvent *owe, s32 xNew, s32 yNew, enum Direction newDirection, enum Direction collisionDirection) +{ + if (CheckRestrictedOWEMovementMetatile(owe->currentCoords.x, owe->currentCoords.y, xNew, yNew)) + return FALSE; + + if (CheckRestrictedOWEMovementMap(owe, xNew, yNew)) + return FALSE; + + if (GetCollisionAtCoords(owe, xNew, yNew, collisionDirection)) + return FALSE; + + return TRUE; +} + +static bool32 CheckRestrictedOWEMovementMetatile(s32 xCurrent, s32 yCurrent, s32 xNew, s32 yNew) +{ + if (!WE_OWE_RESTRICT_METATILE) + return FALSE; + u32 metatileBehaviourCurrent = MapGridGetMetatileBehaviorAt(xCurrent, yCurrent); + u32 metatileBehaviourNew = MapGridGetMetatileBehaviorAt(xNew, yNew); + + if (MetatileBehavior_IsLandWildEncounter(metatileBehaviourCurrent) + && MetatileBehavior_IsLandWildEncounter(metatileBehaviourNew)) + return FALSE; + + if (MetatileBehavior_IsWaterWildEncounter(metatileBehaviourCurrent) + && MetatileBehavior_IsWaterWildEncounter(metatileBehaviourNew)) + return FALSE; + + if (MetatileBehavior_IsIndoorEncounter(metatileBehaviourCurrent) + && MetatileBehavior_IsIndoorEncounter(metatileBehaviourNew)) + return FALSE; + + if (!MetatileBehavior_IsLandWildEncounter(metatileBehaviourCurrent) + && !MetatileBehavior_IsWaterWildEncounter(metatileBehaviourCurrent) + && !MetatileBehavior_IsIndoorEncounter(metatileBehaviourCurrent)) + return FALSE; + + return TRUE; +} + +static bool32 CheckRestrictedOWEMovementMap(struct ObjectEvent *owe, s32 xNew, s32 yNew) +{ + if (!WE_OWE_RESTRICT_MAP) + return FALSE; + + if (owe->mapGroup == gSaveBlock1Ptr->location.mapGroup + && owe->mapNum == gSaveBlock1Ptr->location.mapNum) + return !AreCoordsInsidePlayerMap(xNew, yNew); + else + return AreCoordsInsidePlayerMap(xNew, yNew); +} + +bool32 CanAwareOWESeePlayer(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY) || owe->movementType == MOVEMENT_TYPE_WANDER_AROUND_OWE) + return FALSE; + + if (IsPlayerInsideOWEActiveDistance(owe) && (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_DASH) + || (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_BIKE) && gPlayerAvatar.runningState == MOVING))) + return TRUE; + + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + u32 speciesId = OW_SPECIES(owe); + u32 viewDistance = OWE_GetViewDistanceFromSpecies(speciesId); + u32 viewWidth = OWE_GetViewWidthFromSpecies(speciesId); + s32 halfWidth = (viewWidth - 1) / 2; + enum Direction direction = owe->facingDirection; + bool32 retVal = FALSE; + + switch (direction) + { + case DIR_NORTH: + if (player->currentCoords.y < owe->currentCoords.y + && owe->currentCoords.y - player->currentCoords.y <= viewDistance + && player->currentCoords.x >= owe->currentCoords.x - halfWidth + && player->currentCoords.x <= owe->currentCoords.x + halfWidth) + retVal = TRUE; + break; + + case DIR_SOUTH: + if (player->currentCoords.y > owe->currentCoords.y + && player->currentCoords.y - owe->currentCoords.y <= viewDistance + && player->currentCoords.x >= owe->currentCoords.x - halfWidth + && player->currentCoords.x <= owe->currentCoords.x + halfWidth) + retVal = TRUE; + break; + + case DIR_EAST: + if (player->currentCoords.x > owe->currentCoords.x + && player->currentCoords.x - owe->currentCoords.x <= viewDistance + && player->currentCoords.y >= owe->currentCoords.y - halfWidth + && player->currentCoords.y <= owe->currentCoords.y + halfWidth) + retVal = TRUE; + break; + + case DIR_WEST: + if (player->currentCoords.x < owe->currentCoords.x + && owe->currentCoords.x - player->currentCoords.x <= viewDistance + && player->currentCoords.y >= owe->currentCoords.y - halfWidth + && player->currentCoords.y <= owe->currentCoords.y + halfWidth) + retVal = TRUE; + break; + + default: + retVal = FALSE; + break; + } + + if (retVal && IsOWELineOfSightClear(player, GetOppositeDirection(direction), viewDistance)) + return TRUE; + + return FALSE; +} + +static bool32 IsOWELineOfSightClear(struct ObjectEvent *player, enum Direction direction, u32 distance) +{ + s16 x = player->currentCoords.x; + s16 y = player->currentCoords.y; + u32 i; + + for (i = 0; i < distance; i++) + { + MoveCoords(direction, &x, &y); + if (MapGridGetCollisionAt(x, y) + || GetMapBorderIdAt(x, y) == CONNECTION_INVALID + || IsMetatileDirectionallyImpassable(player, x, y, GetOppositeDirection(direction)) + || IsElevationMismatchAt(player->currentElevation, x, y)) + return FALSE; + } + + return TRUE; +} + +bool32 IsPlayerInsideOWEActiveDistance(struct ObjectEvent *owe) +{ + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return FALSE; + + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + u32 distance = OWE_CHASE_RANGE; + u32 speciesId = OW_SPECIES(owe); + + if (speciesId != SPECIES_NONE) + distance = OWE_GetViewActiveDistanceFromSpecies(speciesId); + + if (player->currentCoords.y <= owe->currentCoords.y + distance && player->currentCoords.y >= owe->currentCoords.y - distance + && player->currentCoords.x <= owe->currentCoords.x + distance && player->currentCoords.x >= owe->currentCoords.x - distance) + return TRUE; + + return FALSE; +} + +bool32 IsOWENextToPlayer(struct ObjectEvent *owe) +{ + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + + if ((owe->currentCoords.x != player->currentCoords.x && owe->currentCoords.y != player->currentCoords.y) || (owe->currentCoords.x < player->currentCoords.x - 1 || owe->currentCoords.x > player->currentCoords.x + 1 || owe->currentCoords.y < player->currentCoords.y - 1 || owe->currentCoords.y > player->currentCoords.y + 1)) + return FALSE; + + return TRUE; +} + +enum Direction DirectionOfOWEToPlayerFromCollision(struct ObjectEvent *owe) +{ + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + + switch (owe->movementDirection) + { + case DIR_NORTH: + case DIR_SOUTH: + if (player->currentCoords.x < owe->currentCoords.x) + return DIR_WEST; + else if (player->currentCoords.x == owe->currentCoords.x) + return CheckOWEPathToPlayerFromCollision(owe, (Random() % 2) == 0 ? DIR_EAST : DIR_WEST); + else + return DIR_EAST; + case DIR_EAST: + case DIR_WEST: + if (player->currentCoords.y < owe->currentCoords.y) + return DIR_NORTH; + else if (player->currentCoords.y == owe->currentCoords.y) + return CheckOWEPathToPlayerFromCollision(owe, (Random() % 2) == 0 ? DIR_NORTH : DIR_SOUTH); + else + return DIR_SOUTH; + } + + return owe->movementDirection; +} + +u32 GetApproachingOWEDistanceToPlayer(struct ObjectEvent *owe, bool32 *equalDistances) +{ + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + s32 absX, absY; + s32 distanceX = player->currentCoords.x - owe->currentCoords.x; + s32 distanceY = player->currentCoords.y - owe->currentCoords.y; + + if (distanceX < 0) + absX = distanceX * -1; + else + absX = distanceX; + + if (distanceY < 0) + absY = distanceY * -1; + else + absY = distanceY; + + if (absY == absX) + *equalDistances = TRUE; + + if (absY > absX) + return absY; + else + return absX; +} + +u32 GetOWEWalkMovementActionInDirectionWithSpeed(enum Direction direction, u32 speed) +{ + switch (speed) + { + case OWE_SPEED_SLOW: + return GetWalkSlowMovementAction(direction); + case OWE_SPEED_FAST: + return GetWalkFastMovementAction(direction); + case OWE_SPEED_FASTER: + return GetWalkFasterMovementAction(direction); + } + + return GetWalkNormalMovementAction(direction); +} + +static enum Direction CheckOWEPathToPlayerFromCollision(struct ObjectEvent *owe, enum Direction newDirection) +{ + s16 x = owe->currentCoords.x; + s16 y = owe->currentCoords.y; + + MoveCoords(newDirection, &x, &y); + if (CheckRestrictedOWEMovementAtCoords(owe, x, y, newDirection, newDirection)) + { + if (owe->movementType == MOVEMENT_TYPE_FLEE_PLAYER_OWE) + return GetOppositeDirection(newDirection); + + MoveCoords(owe->movementDirection, &x, &y); + if (CheckRestrictedOWEMovementAtCoords(owe, x, y, newDirection, owe->movementDirection)) + return newDirection; + } + + x = owe->currentCoords.x; + y = owe->currentCoords.y; + MoveCoords(GetOppositeDirection(newDirection), &x, &y); + if (CheckRestrictedOWEMovementAtCoords(owe, x, y, newDirection, newDirection)) + { + if (owe->movementType == MOVEMENT_TYPE_FLEE_PLAYER_OWE) + return newDirection; + + MoveCoords(owe->movementDirection, &x, &y); + if (CheckRestrictedOWEMovementAtCoords(owe, x, y, newDirection, owe->movementDirection)) + return GetOppositeDirection(newDirection); + } + + return owe->movementDirection; +} + +#define tObjectId data[0] +void OWEApproachForBattle(void) +{ + u32 objectEventId = GetObjectEventIdByLocalId(gSpecialVar_LastTalked); + struct ObjectEvent *owe = &gObjectEvents[objectEventId]; + if (!WE_OWE_APPROACH_FOR_BATTLE || !IsOverworldWildEncounter(owe, OWE_ANY)) + { + FreezeObjectEvent(owe); + return; + } + + if (!IsOverworldWildEncounter(owe, OWE_ANY)) + return; + + u32 taskId = CreateTask(Task_OWEApproachForBattle, 2); + if (FindTaskIdByFunc(Task_OWEApproachForBattle) == TASK_NONE) + { + FreezeObjectEvent(owe); + return; + } + + ScriptContext_Stop(); + gTasks[taskId].tObjectId = objectEventId; +} + +static void Task_OWEApproachForBattle(u8 taskId) +{ + struct ObjectEvent *OWE = &gObjectEvents[gTasks[taskId].tObjectId]; + + // Let the mon continue to take steps until right next to the player. + if (ObjectEventClearHeldMovementIfFinished(OWE)) + { + struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId]; + if (IsOWENextToPlayer(OWE)) + { + ObjectEventsTurnToEachOther(player, OWE); + ScriptContext_Enable(); + DestroyTask(taskId); + return; + } + + u32 speciesId = OW_SPECIES(OWE); + enum Direction direction = DetermineObjectEventDirectionFromObject(player, OWE); + u32 movementActionId; + + SetObjectEventDirection(OWE, direction); + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(OWE->movementDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + + if (CheckRestrictedOWEMovement(OWE, OWE->movementDirection)) + { + struct ObjectEvent *followerMon = GetFollowerObject(); + u32 idFollowerNPC = GetFollowerNPCObjectId(); + struct ObjectEvent *followerNPC = &gObjectEvents[idFollowerNPC]; + s16 x = OWE->currentCoords.x; + s16 y = OWE->currentCoords.y; + u32 collidingObject; + + MoveCoords(OWE->movementDirection, &x, &y); + collidingObject = GetObjectObjectCollidesWith(OWE, x, y, FALSE); + + if (collidingObject == GetObjectEventIdByLocalId(followerMon->localId) && followerMon != NULL && !followerMon->invisible) + { + ClearObjectEventMovement(followerMon, &gSprites[followerMon->spriteId]); + gSprites[followerMon->spriteId].animCmdIndex = 0; + ObjectEventSetHeldMovement(followerMon, MOVEMENT_ACTION_ENTER_POKEBALL); + } + else if (collidingObject == idFollowerNPC && FNPC_ENABLE_NPC_FOLLOWERS && PlayerHasFollowerNPC() && !followerNPC->invisible) + { + enum Direction direction = DetermineFollowerNPCDirection(&gObjectEvents[gPlayerAvatar.objectEventId], followerNPC); + ClearObjectEventMovement(followerNPC, &gSprites[followerNPC->spriteId]); + gSprites[followerNPC->spriteId].animCmdIndex = 0; + ObjectEventSetHeldMovement(followerNPC, GetWalkNormalMovementAction(direction)); + CreateTask(Task_HideNPCFollowerAfterMovementFinish, 2); + } + else if (collidingObject == gPlayerAvatar.objectEventId) + { + movementActionId = GetFaceDirectionMovementAction(OWE->facingDirection); + } + else + { + direction = DirectionOfOWEToPlayerFromCollision(OWE); + SetObjectEventDirection(OWE, direction); + movementActionId = GetOWEWalkMovementActionInDirectionWithSpeed(OWE->movementDirection, OWE_GetActiveSpeedFromSpecies(speciesId)); + } + } + ObjectEventSetHeldMovement(OWE, movementActionId); + } + +} +#undef tObjectId + +void PlayAmbientOWECry(void) +{ + PlayOWECry(GetOWEObjectEvent()); +} + +u32 GetNumberOfActiveOWEs(enum TypeOWE oweType) +{ + u32 numActive = 0; + for (u32 i = 0; i < OBJECT_EVENTS_COUNT; i++) + { + if (IsOverworldWildEncounter(&gObjectEvents[i], oweType)) + numActive++; + } + return numActive; +} + +const struct ObjectEventTemplate TryGetObjectEventTemplateForOWE(const struct ObjectEventTemplate *template) +{ + if (template->trainerType != TRAINER_TYPE_OW_WILD_ENCOUNTER || (template->localId <= LOCALID_OW_ENCOUNTER_END + && template->localId > (LOCALID_OW_ENCOUNTER_END - OWE_SPAWNS_MAX))) + return *template; + + struct ObjectEventTemplate templateOWE = *template; + + u32 graphicsId; + u32 speciesId, speciesTemplate = SanitizeSpeciesId(templateOWE.graphicsId & OBJ_EVENT_MON_SPECIES_MASK); + bool32 isShiny = FALSE, isShinyTemplate = (templateOWE.graphicsId & OBJ_EVENT_MON_SHINY) ? TRUE : FALSE; + bool32 isFemale = FALSE; + u32 level = MIN_LEVEL, levelTemplate = templateOWE.sOverworldEncounterLevel; + u32 indexRoamerOutbreak = OWE_INVALID_ROAMER_OUTBREAK; + u32 x = template->x; + u32 y = template->y; + + SetSpeciesInfoForOWE(&speciesId, &isShiny, &isFemale, &level, &indexRoamerOutbreak, x, y); + if (speciesTemplate) + speciesId = speciesTemplate; + + if (levelTemplate) + level = levelTemplate; + + bool32 validSpecies = CheckValidOWESpecies(speciesId); + bool32 validLevel = GetOWEEncounterLevel(level) >= MIN_LEVEL && GetOWEEncounterLevel(level) <= MAX_LEVEL; + assertf(validSpecies && validLevel, "invalid manual overworld encounter\nspecies: %d\nlevel: %d\nx: %d y: %d\ncheck if valid wild mon header exists", speciesId, level, x, y) + { + if (!validSpecies) + { + // Currently causes assertf on each player step as function is called. + templateOWE.graphicsId = OBJ_EVENT_GFX_BOY_1; + templateOWE.trainerType = TRAINER_TYPE_NONE; + templateOWE.sOverworldEncounterLevel = 0; + templateOWE.movementType = MOVEMENT_TYPE_NONE; + return templateOWE; + } + else if (!validLevel) + { + level = MIN_LEVEL; + } + } + + if (isShinyTemplate) + isShiny = isShinyTemplate; + + if (templateOWE.graphicsId & OBJ_EVENT_MON && templateOWE.graphicsId & OBJ_EVENT_MON_FEMALE) + isFemale = TRUE; + else if (templateOWE.graphicsId & OBJ_EVENT_MON) + isFemale = FALSE; + else + isFemale = GetGenderFromSpeciesAndPersonality(speciesId, Random32()) == MON_FEMALE; + + if (templateOWE.movementType == MOVEMENT_TYPE_NONE) + templateOWE.movementType = OWE_GetMovementTypeFromSpecies(speciesId); + + graphicsId = speciesId + OBJ_EVENT_MON; + if (isFemale) + graphicsId += OBJ_EVENT_MON_FEMALE; + if (isShiny) + graphicsId += OBJ_EVENT_MON_SHINY; + + templateOWE.graphicsId = graphicsId; + templateOWE.sOverworldEncounterLevel = level; + + return templateOWE; +} + +struct SpritePalette GetOWESpawnDespawnAnimFldEffPalette(enum SpawnDespawnTypeOWE spawnAnim) +{ + struct SpritePalette palette = gSpritePalette_GeneralFieldEffect0; + switch (spawnAnim) + { + case OWE_SPAWN_ANIM_GRASS: + case OWE_SPAWN_ANIM_LONG_GRASS: + palette = gSpritePalette_GeneralFieldEffect1; + break; + + case OWE_SPAWN_ANIM_WATER: + case OWE_SPAWN_ANIM_UNDERWATER: + case OWE_SPAWN_ANIM_CAVE: + case OWE_SPAWN_ANIM_SHINY: + default: + break; + } + + return palette; +} + +static bool32 CheckValidOWESpecies(u32 speciesId) +{ + if (speciesId == SPECIES_NONE) + return FALSE; + + if (speciesId >= NUM_SPECIES) + return FALSE; + + if (!IsSpeciesEnabled(speciesId)) + return FALSE; + + return TRUE; +} + +#undef sOverworldEncounterLevel +#undef sAge +#undef sRoamerOutbreakStatus