pmd-red/src/dungeon_ai_targeting.c
2023-01-13 22:36:16 -05:00

571 lines
18 KiB
C

#include "global.h"
#include "dungeon_ai_targeting.h"
#include "constants/ability.h"
#include "constants/dungeon.h"
#include "constants/iq_skill.h"
#include "constants/item.h"
#include "constants/status.h"
#include "constants/tactic.h"
#include "constants/targeting.h"
#include "dungeon_engine.h"
#include "dungeon_global_data.h"
#include "dungeon_items.h"
#include "dungeon_map_access.h"
#include "dungeon_movement.h"
#include "dungeon_pokemon_attributes.h"
#include "dungeon_random.h"
#include "dungeon_util.h"
#include "map.h"
#include "tile_types.h"
extern void ShowVisualFlags(struct Entity *r0);
const u8 gDirectionBitMasks_2[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const u8 gDirectionBitMasks_3[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const u8 gDirectionBitMasks_4[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const u8 gDirectionBitMasks_5[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const u8 gDirectionBitMasks_6[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const u8 gDirectionBitMasks_7[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const u8 gTargetingData[3][2][2][2] = {
{
{
{TARGET_CAPABILITY_CANNOT_ATTACK, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET},
{TARGET_CAPABILITY_CAN_TARGET, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET}
},
{
{TARGET_CAPABILITY_CAN_TARGET, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET},
{TARGET_CAPABILITY_CANNOT_ATTACK, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET}
}
},
{
{
{TARGET_CAPABILITY_CANNOT_ATTACK, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET},
{TARGET_CAPABILITY_CAN_TARGET, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET}
},
{
{TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET, TARGET_CAPABILITY_CAN_TARGET},
{TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET, TARGET_CAPABILITY_CAN_TARGET}
}
},
{
{
{TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET, TARGET_CAPABILITY_CAN_TARGET},
{TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET, TARGET_CAPABILITY_CAN_TARGET}
},
{
{TARGET_CAPABILITY_CAN_TARGET, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET},
{TARGET_CAPABILITY_CANNOT_ATTACK, TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET}
}
}
};
bool8 sub_8070F3C(struct Entity * pokemon, struct Position *pos, s32 direction)
{
u8 terrain;
struct Tile *tile;
terrain = GetCrossableTerrain(pokemon->info->id);
tile = GetTile(pos->x + gAdjacentTileOffsets[direction].x, pos->y + gAdjacentTileOffsets[direction].y);
if ((!(tile->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL)) &&
(((tile->monster == NULL || (GetEntityType(tile->monster) == ENTITY_MONSTER))))) {
if (!IsCurrentFixedRoomBossFight())
{
if (pokemon->info->transformStatus == STATUS_MOBILE ||
HasHeldItem(pokemon, ITEM_MOBILE_SCARF))
{
terrain = CROSSABLE_TERRAIN_WALL;
}
else if (IQSkillIsEnabled(pokemon, IQ_ALL_TERRAIN_HIKER))
{
// BUG: If the Pokémon is a Ghost type that can normally attack through walls,
// All-Terrain Hiker/Super Mobile may make the AI think it can't attack through walls.
terrain = CROSSABLE_TERRAIN_CREVICE;
}
else if (IQSkillIsEnabled(pokemon, IQ_SUPER_MOBILE))
{
if ((direction & 1) != 0)
{
terrain = CROSSABLE_TERRAIN_CREVICE;
}
else
{
terrain = CROSSABLE_TERRAIN_WALL;
}
}
}
tile = GetTile(pos->x, pos->y);
if (((tile->walkableNeighborFlags[terrain] & gDirectionBitMasks_2[direction & DIRECTION_MASK]))) {
return TRUE;
}
}
return FALSE;
}
bool8 sub_8070F14(struct Entity * pokemon, s32 direction)
{
struct Tile *tile;
tile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[direction].x, pokemon->pos.y + gAdjacentTileOffsets[direction].y);
if ((!(tile->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL)) &&
(((tile->monster == NULL)))) {
tile = GetTile(pokemon->pos.x, pokemon->pos.y);
if (((tile->walkableNeighborFlags[0] & gDirectionBitMasks_3[direction & DIRECTION_MASK]))) {
return TRUE;
}
}
return FALSE;
}
bool8 sub_8070F80(struct Entity * pokemon, s32 direction)
{
u8 terrain;
struct Tile *tile;
terrain = GetCrossableTerrain(pokemon->info->id);
tile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[direction].x, pokemon->pos.y + gAdjacentTileOffsets[direction].y);
if ((!(tile->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL)) &&
(((tile->monster == NULL || (GetEntityType(tile->monster) == ENTITY_MONSTER))))) {
if (!IsCurrentFixedRoomBossFight())
{
if (pokemon->info->transformStatus == STATUS_MOBILE ||
HasHeldItem(pokemon, ITEM_MOBILE_SCARF))
{
terrain = CROSSABLE_TERRAIN_WALL;
}
else if (IQSkillIsEnabled(pokemon, IQ_ALL_TERRAIN_HIKER))
{
// BUG: If the Pokémon is a Ghost type that can normally attack through walls,
// All-Terrain Hiker/Super Mobile may make the AI think it can't attack through walls.
terrain = CROSSABLE_TERRAIN_CREVICE;
}
else if (IQSkillIsEnabled(pokemon, IQ_SUPER_MOBILE))
{
if ((direction & 1) != 0)
{
terrain = CROSSABLE_TERRAIN_CREVICE;
}
else
{
terrain = CROSSABLE_TERRAIN_WALL;
}
}
}
tile = GetTile(pokemon->pos.x, pokemon->pos.y);
if (((tile->walkableNeighborFlags[terrain] & gDirectionBitMasks_4[direction & DIRECTION_MASK]))) {
return TRUE;
}
}
return FALSE;
}
bool8 sub_8071058(struct Entity * pokemon, s32 direction)
{
u8 terrain;
struct Tile *tile;
terrain = GetCrossableTerrain(pokemon->info->id);
tile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[direction].x, pokemon->pos.y + gAdjacentTileOffsets[direction].y);
if ((!(tile->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL)) &&
(((tile->monster == NULL || (GetEntityType(tile->monster) == ENTITY_MONSTER)) ||
(!tile->monster->info->isNotTeamMember)))) {
if (!IsCurrentFixedRoomBossFight())
{
if (pokemon->info->transformStatus == STATUS_MOBILE ||
HasHeldItem(pokemon, ITEM_MOBILE_SCARF))
{
terrain = CROSSABLE_TERRAIN_WALL;
}
else if (IQSkillIsEnabled(pokemon, IQ_ALL_TERRAIN_HIKER))
{
// BUG: If the Pokémon is a Ghost type that can normally attack through walls,
// All-Terrain Hiker/Super Mobile may make the AI think it can't attack through walls.
terrain = CROSSABLE_TERRAIN_CREVICE;
}
else if (IQSkillIsEnabled(pokemon, IQ_SUPER_MOBILE))
{
if ((direction & 1) != 0)
{
terrain = CROSSABLE_TERRAIN_CREVICE;
}
else
{
terrain = CROSSABLE_TERRAIN_WALL;
}
}
}
tile = GetTile(pokemon->pos.x, pokemon->pos.y);
if (((tile->walkableNeighborFlags[terrain] & gDirectionBitMasks_5[direction & DIRECTION_MASK]))) {
return TRUE;
}
}
return FALSE;
}
bool8 CanAttackInDirection(struct Entity *pokemon, s32 direction)
{
u8 crossableTerrain = GetCrossableTerrain(pokemon->info->id);
struct Tile *tile;
if (crossableTerrain < CROSSABLE_TERRAIN_CREVICE)
{
crossableTerrain = CROSSABLE_TERRAIN_CREVICE;
}
tile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[direction].x,
pokemon->pos.y + gAdjacentTileOffsets[direction].y);
if (!(tile->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL) &&
(tile->monster == NULL || GetEntityType(tile->monster) == ENTITY_MONSTER))
{
if (!IsCurrentFixedRoomBossFight())
{
if (pokemon->info->transformStatus == STATUS_MOBILE ||
HasHeldItem(pokemon, ITEM_MOBILE_SCARF))
{
crossableTerrain = CROSSABLE_TERRAIN_WALL;
}
else if (IQSkillIsEnabled(pokemon, IQ_ALL_TERRAIN_HIKER))
{
// BUG: If the Pokémon is a Ghost type that can normally attack through walls,
// All-Terrain Hiker/Super Mobile may make the AI think it can't attack through walls.
crossableTerrain = CROSSABLE_TERRAIN_CREVICE;
}
else if (IQSkillIsEnabled(pokemon, IQ_SUPER_MOBILE))
{
if ((direction & 1) != 0)
{
crossableTerrain = CROSSABLE_TERRAIN_CREVICE;
}
else
{
crossableTerrain = CROSSABLE_TERRAIN_WALL;
}
}
}
tile = GetTile(pokemon->pos.x, pokemon->pos.y);
if (tile->walkableNeighborFlags[crossableTerrain] & gDirectionBitMasks_6[direction & DIRECTION_MASK])
{
return TRUE;
}
}
return FALSE;
}
bool8 CanAIMonsterMoveInDirection(struct Entity *pokemon, s32 direction, bool8 *pokemonInFront)
{
u8 crossableTerrain = GetCrossableTerrain(pokemon->info->id);
struct Tile *frontTile, *currentTile;
*pokemonInFront = FALSE;
frontTile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[direction].x,
pokemon->pos.y + gAdjacentTileOffsets[direction].y);
if (frontTile->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL)
{
return FALSE;
}
if (frontTile->terrainType & TERRAIN_TYPE_IN_MONSTER_HOUSE &&
!gDungeon->monsterHouseTriggered &&
IQSkillIsEnabled(pokemon, IQ_HOUSE_AVOIDER))
{
return FALSE;
}
if (frontTile->object != NULL &&
IQSkillIsEnabled(pokemon, IQ_TRAP_AVOIDER) &&
GetEntityType(frontTile->object) == ENTITY_TRAP &&
(frontTile->object->isVisible || pokemon->info->eyesightStatus == STATUS_EYEDROPS))
{
return FALSE;
}
if ((frontTile->terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)) == TERRAIN_TYPE_SECONDARY &&
gDungeonWaterType[gDungeon->tileset] == DUNGEON_WATER_TYPE_LAVA &&
IQSkillIsEnabled(pokemon, IQ_LAVA_EVADER))
{
return FALSE;
}
if (!IsCurrentFixedRoomBossFight())
{
if (pokemon->info->transformStatus == STATUS_MOBILE ||
HasHeldItem(pokemon, ITEM_MOBILE_SCARF))
{
crossableTerrain = CROSSABLE_TERRAIN_WALL;
}
else if (IQSkillIsEnabled(pokemon, IQ_ALL_TERRAIN_HIKER))
{
// BUG: If the Pokémon is a Ghost type that can normally move through walls,
// All-Terrain Hiker/Super Mobile may make the AI think it can't move through walls.
crossableTerrain = CROSSABLE_TERRAIN_CREVICE;
}
else if (IQSkillIsEnabled(pokemon, IQ_SUPER_MOBILE))
{
if ((direction & 1) != 0)
{
crossableTerrain = CROSSABLE_TERRAIN_CREVICE;
}
else
{
crossableTerrain = CROSSABLE_TERRAIN_WALL;
}
}
}
currentTile = GetTile(pokemon->pos.x, pokemon->pos.y);
if (currentTile->walkableNeighborFlags[crossableTerrain] & gDirectionBitMasks_7[direction & DIRECTION_MASK])
{
if (frontTile->monster == NULL)
{
return TRUE;
}
else
{
*pokemonInFront = TRUE;
}
}
return FALSE;
}
bool8 IsAtJunction(struct Entity *pokemon)
{
u32 crossableTerrain = GetCrossableTerrain(pokemon->info->id);
if (!IsCurrentFixedRoomBossFight())
{
if (pokemon->info->transformStatus == STATUS_MOBILE || HasHeldItem(pokemon, ITEM_MOBILE_SCARF))
{
crossableTerrain = CROSSABLE_TERRAIN_WALL;
}
else if (IQSkillIsEnabled(pokemon, IQ_ALL_TERRAIN_HIKER))
{
crossableTerrain = CROSSABLE_TERRAIN_CREVICE;
}
else if (IQSkillIsEnabled(pokemon, IQ_SUPER_MOBILE))
{
crossableTerrain = CROSSABLE_TERRAIN_WALL;
}
}
if (crossableTerrain == CROSSABLE_TERRAIN_WALL)
{
struct EntityInfo *pokemonInfo = pokemon->info;
pokemonInfo->mobileTurnTimer += DungeonRandInt(100);
if (pokemonInfo->mobileTurnTimer < 200)
{
return FALSE;
}
else
{
pokemonInfo->mobileTurnTimer = 0;
}
}
else
{
struct Tile *mapTile;
char walkableNeighborFlags;
if (gDungeonWaterType[gDungeon->tileset] == DUNGEON_WATER_TYPE_LAVA
&& crossableTerrain == CROSSABLE_TERRAIN_LIQUID
&& IQSkillIsEnabled(pokemon, IQ_LAVA_EVADER))
{
crossableTerrain = CROSSABLE_TERRAIN_REGULAR;
}
mapTile = GetTile(pokemon->pos.x, pokemon->pos.y);
walkableNeighborFlags = mapTile->walkableNeighborFlags[crossableTerrain];
/*
Check for configurations of open tiles that are considered junctions; i.e., shaped like a 'T' or '+'.
X=Wall, O=Open
0x54 0x51 0x45 0x15 0x55
XOX XOX XXX XOX XOX
OOO OOX OOO XOO OOO
XXX XOX XOX XOX XOX
*/
if (walkableNeighborFlags != 0x54 && walkableNeighborFlags != 0x51 && walkableNeighborFlags != 0x45 && walkableNeighborFlags != 0x15 && walkableNeighborFlags != 0x55)
{
return FALSE;
}
}
return TRUE;
}
bool8 ShouldAvoidFirstHit(struct Entity *pokemon, bool8 forceAvoid)
{
if (!HasTactic(pokemon, TACTIC_AVOID_THE_FIRST_HIT))
return FALSE;
if (!forceAvoid)
return FALSE;
return TRUE;
}
bool8 ShouldMonsterRunAway(struct Entity *pokemon)
{
if (!EntityExists(pokemon))
{
return FALSE;
}
else
{
struct EntityInfo *pokemonInfo = pokemon->info;
if (pokemonInfo->terrifiedTurns != 0)
{
return TRUE;
}
if (pokemonInfo->isTeamLeader)
{
return FALSE;
}
if (HasAbility(pokemon, ABILITY_RUN_AWAY))
{
bool8 runAwayActive = pokemonInfo->HP < pokemonInfo->maxHPStat / 2;
if (runAwayActive)
{
return TRUE;
}
}
if (HasTactic(pokemon, TACTIC_GET_AWAY) ||
(HasTactic(pokemon, TACTIC_AVOID_TROUBLE) && pokemonInfo->HP <= pokemonInfo->maxHPStat / 2))
{
return TRUE;
}
return FALSE;
}
}
bool8 ShouldMonsterRunAwayAndShowEffect(struct Entity *pokemon, bool8 showRunAwayEffect)
{
if (ShouldMonsterRunAway(pokemon))
{
CheckRunAwayVisualFlag(pokemon, showRunAwayEffect);
return TRUE;
}
return FALSE;
}
void CheckRunAwayVisualFlag(struct Entity *pokemon, bool8 showRunAwayEffect)
{
bool8 cVar1;
struct EntityInfo *iVar2;
struct EntityInfo *iVar3;
iVar2 = pokemon->info;
iVar3 = iVar2;
if (((!iVar2->isTeamLeader) && HasAbility(pokemon,ABILITY_RUN_AWAY) &&
(cVar1 = SetVisualFlags(iVar3,4,iVar2->HP <= iVar2->maxHPStat / 2), showRunAwayEffect)) &&
(cVar1)) {
ShowVisualFlags(pokemon);
}
}
u8 CanTarget(struct Entity *pokemon, struct Entity *targetPokemon, bool8 ignoreInvisible, bool8 checkPetrified)
{
struct EntityInfo *pokemonInfo = pokemon->info;
struct EntityInfo *targetData = targetPokemon->info;
u8 targetingDecoy;
u8 pokemonTargetingDecoy;
bool8 pokemonIsEnemy;
bool8 targetIsEnemy;
bool8 targetIsDecoy;
if (pokemon == targetPokemon)
{
return TARGET_CAPABILITY_CANNOT_ATTACK;
}
if (pokemonInfo->shopkeeper == SHOPKEEPER_MODE_SHOPKEEPER ||
targetData->shopkeeper == SHOPKEEPER_MODE_SHOPKEEPER ||
pokemonInfo->clientType == CLIENT_TYPE_DONT_MOVE ||
targetData->clientType == CLIENT_TYPE_DONT_MOVE ||
pokemonInfo->clientType == CLIENT_TYPE_CLIENT ||
targetData->clientType == CLIENT_TYPE_CLIENT ||
(checkPetrified && !pokemonInfo->isNotTeamMember && targetData->immobilizeStatus == STATUS_PETRIFIED) ||
(!ignoreInvisible && targetData->transformStatus == STATUS_INVISIBLE && !CanSeeInvisibleMonsters(pokemon)))
{
return TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET;
}
pokemonTargetingDecoy = pokemonInfo->targetingDecoy;
targetingDecoy = TARGETING_DECOY_NONE;
if (pokemonTargetingDecoy != TARGETING_DECOY_NONE)
{
targetingDecoy = TARGETING_DECOY_WILD;
if (pokemonTargetingDecoy == TARGETING_DECOY_TEAM)
{
targetingDecoy = TARGETING_DECOY_TEAM;
}
}
if (pokemonInfo->shopkeeper != SHOPKEEPER_MODE_NORMAL)
{
pokemonIsEnemy = FALSE;
if (pokemonInfo->shopkeeper == SHOPKEEPER_MODE_ATTACK_TEAM)
{
pokemonIsEnemy = TRUE;
}
}
else
{
pokemonIsEnemy = pokemonInfo->isNotTeamMember ? TRUE : FALSE;
}
if (targetData->shopkeeper != SHOPKEEPER_MODE_NORMAL)
{
targetIsEnemy = FALSE;
if (targetData->shopkeeper == SHOPKEEPER_MODE_ATTACK_TEAM)
{
targetIsEnemy = TRUE;
}
}
else
{
targetIsEnemy = targetData->isNotTeamMember ? TRUE : FALSE;
}
targetIsDecoy = FALSE;
if (targetData->waitingStatus == STATUS_DECOY)
{
targetIsDecoy = TRUE;
}
return gTargetingData[targetingDecoy][pokemonIsEnemy][targetIsEnemy][targetIsDecoy];
}
static inline bool8 JoinLocationCannotUseItems_1(struct EntityInfo *pokemonInfo)
{
if (pokemonInfo->joinedAt == DUNGEON_JOIN_LOCATION_CLIENT_POKEMON)
{
return TRUE;
}
if (pokemonInfo->joinedAt == DUNGEON_RESCUE_TEAM_BASE)
{
return TRUE;
}
return FALSE;
}
u8 sub_807167C(struct Entity * pokemon, struct Entity * target)
{
bool8 cannotUseItems;
struct EntityInfo * targetEntityInfo;
struct EntityInfo * pokemonEntityData;
pokemonEntityData = pokemon->info;
targetEntityInfo = target->info;
if (pokemonEntityData->clientType != CLIENT_TYPE_CLIENT) {
cannotUseItems = JoinLocationCannotUseItems_1(pokemonEntityData);
if (!cannotUseItems && (pokemonEntityData->shopkeeper == SHOPKEEPER_MODE_NORMAL) && (targetEntityInfo->clientType != CLIENT_TYPE_CLIENT)) {
cannotUseItems = JoinLocationCannotUseItems_1(targetEntityInfo);
if (cannotUseItems || (targetEntityInfo->shopkeeper != SHOPKEEPER_MODE_NORMAL)) {
error:
return TARGET_CAPABILITY_CAN_ATTACK_NOT_TARGET;
}
else
{
if ((pokemonEntityData->isNotTeamMember) != (targetEntityInfo->isNotTeamMember)) {
return TARGET_CAPABILITY_CAN_TARGET;
}
else {
return TARGET_CAPABILITY_CANNOT_ATTACK;
}
}
}
}
goto error;
}