pmd-red/src/move_checks.c
2024-12-04 15:43:48 +01:00

875 lines
26 KiB
C

#include "global.h"
#include "math.h"
#include "constants/status.h"
#include "constants/type.h"
#include "constants/weather.h"
#include "dungeon_engine.h"
#include "structs/str_dungeon.h"
#include "dungeon_items.h"
#include "dungeon_map_access.h"
#include "dungeon_pokemon_attributes.h"
#include "dungeon_util.h"
#include "dungeon_visibility.h"
#include "move_checks.h"
#include "moves.h"
#include "number_util.h"
#include "status.h"
#include "status_actions.h"
#include "status_checks_1.h"
#include "text_util.h"
#include "tile_types.h"
#include "trap.h"
#include "weather.h"
#include "dungeon_config.h"
bool8 CanUseOnSelfWithStatusChecker(Entity *pokemon, Move *move)
{
EntityInfo *pokemonInfo = GetEntInfo(pokemon);
switch (move->id)
{
case MOVE_HAIL:
if (GetApparentWeather(pokemon) == WEATHER_HAIL)
{
return FALSE;
}
break;
case MOVE_RAGE:
if (pokemonInfo->bideClassStatus.status == STATUS_ENRAGED)
{
return FALSE;
}
break;
case MOVE_COUNTER:
case MOVE_PURSUIT:
if (pokemonInfo->reflectClassStatus.status == STATUS_COUNTER)
{
return FALSE;
}
break;
case MOVE_MIRROR_MOVE:
if (pokemonInfo->reflectClassStatus.status == STATUS_MIRROR_MOVE)
{
return FALSE;
}
break;
case MOVE_HOWL:
case MOVE_MEDITATE:
case MOVE_SHARPEN:
if (pokemonInfo->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_BELLY_DRUM:
if (pokemonInfo->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE || FixedPointToInt(pokemonInfo->belly) <= 0)
{
return FALSE;
}
break;
case MOVE_ACID_ARMOR:
case MOVE_BARRIER:
case MOVE_DEFENSE_CURL:
case MOVE_HARDEN:
case MOVE_IRON_DEFENSE:
case MOVE_WITHDRAW:
if (pokemonInfo->defensiveStages[STAT_STAGE_DEF] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_BIDE:
case MOVE_REVENGE:
if (pokemonInfo->bideClassStatus.status == STATUS_BIDE)
{
return FALSE;
}
break;
case MOVE_AGILITY:
{
u32 r1;
#ifndef NONMATCHING
asm("":"=r"(r1));
#else
r1 = 0;
#endif
if (GetEntInfo(pokemon)->speedStage >= MAX_SPEED_STAGE)
{
r1 = !r1;
return FALSE;
}
break;
}
case MOVE_LOCK_ON:
case MOVE_MIND_READER:
if (pokemonInfo->sureShotClassStatus.status == STATUS_SURE_SHOT)
{
return FALSE;
}
break;
case MOVE_COSMIC_POWER:
if (pokemonInfo->defensiveStages[STAT_STAGE_DEF] >= MAX_STAT_STAGE && pokemonInfo->defensiveStages[STAT_STAGE_SP_DEF] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_ENDURE:
if (pokemonInfo->reflectClassStatus.status == STATUS_ENDURING)
{
return FALSE;
}
break;
case MOVE_CHARGE:
if (pokemonInfo->bideClassStatus.status == STATUS_CHARGING)
{
return FALSE;
}
break;
case MOVE_MIST:
if (pokemonInfo->reflectClassStatus.status == STATUS_MIST)
{
return FALSE;
}
break;
case MOVE_LIGHT_SCREEN:
if (pokemonInfo->reflectClassStatus.status == STATUS_LIGHT_SCREEN)
{
return FALSE;
}
break;
case MOVE_MINIMIZE:
if (pokemonInfo->hitChanceStages[STAT_STAGE_EVASION] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_INGRAIN:
if (pokemonInfo->frozenClassStatus.status == STATUS_INGRAIN || pokemonInfo->maxHPStat / 2 < pokemonInfo->HP)
{
return FALSE;
}
break;
case MOVE_SWALLOW:
if (pokemonInfo->maxHPStat <= pokemonInfo->HP || pokemonInfo->stockpileStage == 0)
{
return FALSE;
}
break;
case MOVE_SPIT_UP:
if (pokemonInfo->stockpileStage == 0)
{
return FALSE;
}
break;
case MOVE_DOOM_DESIRE:
case MOVE_FUTURE_SIGHT:
if (pokemonInfo->sureShotClassStatus.status == STATUS_SET_DAMAGE)
{
return FALSE;
}
break;
case MOVE_BULK_UP:
if (pokemonInfo->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE && pokemonInfo->defensiveStages[STAT_STAGE_DEF] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_CAMOUFLAGE:
if (MonsterIsType(pokemon, gDungeonCamouflageTypes[gDungeon->tileset]))
{
return FALSE;
}
break;
case MOVE_TAIL_GLOW:
if (pokemonInfo->offensiveStages[STAT_STAGE_SP_ATK] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_DESTINY_BOND:
if (pokemonInfo->leechSeedClassStatus.status == STATUS_DESTINY_BOND)
{
return FALSE;
}
break;
case MOVE_MIRROR_COAT:
if (pokemonInfo->reflectClassStatus.status == STATUS_MIRROR_COAT)
{
return FALSE;
}
break;
case MOVE_REFLECT:
if (pokemonInfo->reflectClassStatus.status == STATUS_REFLECT)
{
return FALSE;
}
break;
case MOVE_DRAGON_DANCE:
if (pokemonInfo->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE && GetEntInfo(pokemon)->speedStage >= MAX_SPEED_STAGE)
{
return FALSE;
}
break;
case MOVE_MAGIC_COAT:
if (pokemonInfo->reflectClassStatus.status == STATUS_MAGIC_COAT)
{
return FALSE;
}
break;
case MOVE_DETECT:
case MOVE_PROTECT:
if (pokemonInfo->reflectClassStatus.status == STATUS_PROTECT)
{
return FALSE;
}
break;
case MOVE_RAIN_DANCE:
if (GetApparentWeather(pokemon) == WEATHER_RAIN)
{
return FALSE;
}
break;
case MOVE_SANDSTORM:
if (GetApparentWeather(pokemon) == WEATHER_SANDSTORM)
{
return FALSE;
}
break;
case MOVE_SUNNY_DAY:
if (GetApparentWeather(pokemon) == WEATHER_SUNNY)
{
return FALSE;
}
break;
case MOVE_SAFEGUARD:
if (pokemonInfo->reflectClassStatus.status == STATUS_SAFEGUARD)
{
return FALSE;
}
break;
case MOVE_INVISIFY:
if (pokemonInfo->invisibleClassStatus.status == STATUS_INVISIBLE)
{
return FALSE;
}
break;
case MOVE_FOCUS_ENERGY:
if (pokemonInfo->sureShotClassStatus.status == STATUS_FOCUS_ENERGY)
{
return FALSE;
}
break;
case MOVE_TAKEAWAY:
if (pokemonInfo->heldItem.flags & ITEM_FLAG_EXISTS)
{
return FALSE;
}
break;
case MOVE_REST:
if (!HasLowHealth(pokemon) && !HasNegativeStatus(pokemon))
{
return FALSE;
}
break;
case MOVE_DIVE:
if (IsTileGround(GetTileAtEntitySafe(pokemon)))
{
return FALSE;
}
break;
case MOVE_DIG:
{
Tile *tile = GetTileAtEntitySafe(pokemon);
if (!IsTileGround(tile) || (tile->terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)) != TERRAIN_TYPE_NORMAL)
{
return FALSE;
}
break;
}
case MOVE_TRAP_BUSTER:
{
Entity *object = GetTileAtEntitySafe(pokemon)->object;
if (object == NULL || GetEntityType(object) != ENTITY_TRAP)
{
return FALSE;
}
break;
}
case MOVE_MUD_SPORT:
if (gDungeon->weather.mudSportTurns > 0)
{
return FALSE;
}
break;
case MOVE_WATER_SPORT:
if (gDungeon->weather.waterSportTurns > 0)
{
return FALSE;
}
break;
case MOVE_GRUDGE:
if (pokemonInfo->grudge)
{
return FALSE;
}
break;
case MOVE_DECOY_MAKER:
case MOVE_FOLLOW_ME:
case MOVE_SUBSTITUTE:
if (gDungeon->decoyIsActive)
{
return FALSE;
}
break;
case MOVE_STOCKPILE:
if (pokemonInfo->stockpileStage >= MAX_STOCKPILE_STAGE)
{
return FALSE;
}
break;
case MOVE_CLEANSE:
if (pokemonInfo->heldItem.flags & ITEM_FLAG_EXISTS &&
!(pokemonInfo->heldItem.flags & ITEM_FLAG_STICKY))
{
return FALSE;
}
break;
case MOVE_DOUBLE_TEAM:
if (pokemonInfo->hitChanceStages[STAT_STAGE_EVASION] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_GROWTH:
if (pokemonInfo->offensiveStages[STAT_STAGE_SP_ATK] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_SWORDS_DANCE:
if (pokemonInfo->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_WISH:
if (pokemonInfo->isNotTeamMember || pokemonInfo->reflectClassStatus.status == STATUS_WISH)
{
return FALSE;
}
break;
case MOVE_TRANSFORM:
if (pokemonInfo->invisibleClassStatus.status == STATUS_TRANSFORMED)
{
return FALSE;
}
break;
case MOVE_SPIKES:
if (!CanLayTrap(&pokemon->pos))
{
return FALSE;
}
break;
case MOVE_CALM_MIND:
if (pokemonInfo->offensiveStages[STAT_STAGE_SP_ATK] < MAX_STAT_STAGE)
{
break;
}
case MOVE_AMNESIA:
if (pokemonInfo->defensiveStages[STAT_STAGE_SP_DEF] >= MAX_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_SNATCH:
if (pokemonInfo->curseClassStatus.status == STATUS_SNATCH)
{
return FALSE;
}
break;
case MOVE_BEAT_UP:
case MOVE_BLOWBACK:
case MOVE_HURL:
case MOVE_MEMENTO:
case MOVE_ROAR:
case MOVE_STAY_AWAY:
case MOVE_SWITCHER:
case MOVE_TELEPORT:
case MOVE_VITAL_THROW:
case MOVE_WARP:
case MOVE_WHIRLWIND:
if (IsBossFight())
{
return FALSE;
}
break;
case MOVE_CONVERSION_2:
if (pokemonInfo->reflectClassStatus.status == STATUS_CONVERSION2)
{
return FALSE;
}
break;
case MOVE_HELPING_HAND:
if (pokemonInfo->isNotTeamMember)
{
s32 i;
for (i = 0; i < DUNGEON_MAX_WILD_POKEMON; i++)
{
Entity *target = gDungeon->wildPokemon[i];
if (EntityExists(target) && target != pokemon && CanSeeTarget(pokemon, target))
{
if (GetEntInfo(target)->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE)
{
continue;
}
if (GetEntInfo(target)->offensiveStages[STAT_STAGE_SP_ATK] < MAX_STAT_STAGE)
{
break;
}
}
}
if (i == DUNGEON_MAX_WILD_POKEMON)
{
return FALSE;
}
break;
}
else
{
s32 i;
for (i = 0; i < MAX_TEAM_MEMBERS; i++)
{
Entity *target = gDungeon->teamPokemon[i];
if (EntityExists(target) && target != pokemon && CanSeeTarget(pokemon, target))
{
if (GetEntInfo(target)->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE)
{
continue;
}
if (GetEntInfo(target)->offensiveStages[STAT_STAGE_SP_ATK] < MAX_STAT_STAGE)
{
break;
}
}
}
if (i == MAX_TEAM_MEMBERS)
{
return FALSE;
}
break;
}
}
return TRUE;
}
bool8 CanUseOnTargetWithStatusChecker(Entity *user, Entity *target, Move *move)
{
EntityInfo *userData = GetEntInfo(user);
EntityInfo *targetData = GetEntInfo(target);
s32 i;
if (targetData->frozenClassStatus.status == STATUS_FROZEN && MoveCannotHitFrozen(move))
{
return FALSE;
}
switch (move->id)
{
case MOVE_GRASSWHISTLE:
case MOVE_HYPNOSIS:
case MOVE_LOVELY_KISS:
case MOVE_SING:
case MOVE_SLEEP_POWDER:
case MOVE_SPORE:
if (IsSleeping(target))
{
return FALSE;
}
break;
case MOVE_YAWN:
if (targetData->sleepClassStatus.status == STATUS_YAWNING)
{
return FALSE;
}
if (IsSleeping(target))
{
return FALSE;
}
break;
case MOVE_NIGHTMARE:
if (targetData->sleepClassStatus.status == STATUS_NIGHTMARE)
{
return FALSE;
}
break;
case MOVE_SWEET_SCENT:
if (targetData->hitChanceStages[STAT_STAGE_EVASION] <= 0)
{
return FALSE;
}
break;
case MOVE_CHARM:
if (F248LessThanFloat(targetData->offensiveMultipliers[STAT_STAGE_ATK], STAT_MULTIPLIER_THRESHOLD))
{
return FALSE;
}
break;
case MOVE_ENCORE:
if (targetData->cringeClassStatus.status == STATUS_ENCORE)
{
return FALSE;
}
if (!HasLastUsedMove(targetData->moves.moves))
{
return FALSE;
}
break;
case MOVE_SUPER_FANG:
if (targetData->HP <= 1)
{
return FALSE;
}
break;
case MOVE_PAIN_SPLIT:
if (targetData->HP <= userData->HP)
{
return FALSE;
}
break;
case MOVE_TORMENT:
if (HasDisabledMove(targetData->moves.moves) || !HasLastUsedMove(targetData->moves.moves))
{
return FALSE;
}
break;
case MOVE_COTTON_SPORE:
case MOVE_SCARY_FACE:
case MOVE_STRING_SHOT:
if (GetEntInfo(target)->speedStage <= 0)
{
return FALSE;
}
break;
case MOVE_SCREECH:
if (F248LessThanFloat(targetData->defensiveMultipliers[STAT_STAGE_DEF], STAT_MULTIPLIER_THRESHOLD))
{
return FALSE;
}
break;
case MOVE_FAKE_TEARS:
if (targetData->defensiveStages[STAT_STAGE_SP_DEF] <= 0)
{
return FALSE;
}
break;
case MOVE_SPITE:
if (LastUsedMoveOutOfPP(targetData->moves.moves))
{
return FALSE;
}
break;
case MOVE_SMOKESCREEN:
if (targetData->sureShotClassStatus.status == STATUS_WHIFFER)
{
return FALSE;
}
break;
case MOVE_MEMENTO:
if (F248LessThanFloat(targetData->offensiveMultipliers[STAT_STAGE_ATK], STAT_MULTIPLIER_THRESHOLD) &&
F248LessThanFloat(targetData->offensiveMultipliers[STAT_STAGE_SP_ATK], STAT_MULTIPLIER_THRESHOLD))
{
return FALSE;
}
break;
case MOVE_WILL_O_WISP:
if (targetData->burnClassStatus.status == STATUS_BURN)
{
return FALSE;
}
break;
case MOVE_FORESIGHT:
case MOVE_ODOR_SLEUTH:
if (targetData->types[0] == TYPE_GHOST || targetData->types[1] == TYPE_GHOST)
{
if (!targetData->exposed)
{
break;
}
}
if (targetData->hitChanceStages[STAT_STAGE_EVASION] <= DEFAULT_STAT_STAGE)
{
return FALSE;
}
break;
case MOVE_DISABLE:
case MOVE_GLARE:
case MOVE_STUN_SPORE:
case MOVE_THUNDER_WAVE:
if (targetData->burnClassStatus.status == STATUS_PARALYSIS)
{
return FALSE;
}
break;
case MOVE_ENDEAVOR:
if (targetData->HP - userData->HP <= 0)
{
return FALSE;
}
break;
case MOVE_LEER:
case MOVE_TAIL_WHIP:
if (targetData->defensiveStages[STAT_STAGE_DEF] <= 0)
{
return FALSE;
}
break;
case MOVE_METAL_SOUND:
if (targetData->defensiveStages[STAT_STAGE_SP_DEF] <= 0)
{
return FALSE;
}
break;
case MOVE_TICKLE:
if (targetData->offensiveStages[STAT_STAGE_ATK] <= 0 &&
targetData->defensiveStages[STAT_STAGE_DEF] <= 0)
{
return FALSE;
}
break;
case MOVE_BLOCK:
case MOVE_MEAN_LOOK:
case MOVE_SPIDER_WEB:
if (targetData->frozenClassStatus.status == STATUS_SHADOW_HOLD)
{
return FALSE;
}
break;
case MOVE_HAZE:
{
for (i = 0; i < 2; i++)
{
if (targetData->offensiveStages[i] < DEFAULT_STAT_STAGE) break;
if (targetData->defensiveStages[i] < DEFAULT_STAT_STAGE) break;
if (targetData->hitChanceStages[i] < DEFAULT_STAT_STAGE ||
F248LessThanInt(targetData->offensiveMultipliers[i], 1) ||
F248LessThanInt(targetData->defensiveMultipliers[i], 1))
{
break;
}
}
if (i == 2)
{
return FALSE;
}
break;
}
case MOVE_UPROAR:
if (targetData->sleepClassStatus.status == STATUS_SLEEPLESS)
{
return FALSE;
}
break;
case MOVE_PSYCH_UP:
{
for (i = 0; i < 2; i++)
{
if (userData->offensiveStages[i] < targetData->offensiveStages[i]) break;
if (userData->defensiveStages[i] < targetData->defensiveStages[i] ||
userData->hitChanceStages[i] < targetData->hitChanceStages[i] ||
userData->offensiveMultipliers[i].raw < targetData->offensiveMultipliers[i].raw ||
userData->defensiveMultipliers[i].raw < targetData->defensiveMultipliers[i].raw)
{
break;
}
}
if (i == 2)
{
return FALSE;
}
break;
}
case MOVE_FLASH:
case MOVE_KINESIS:
case MOVE_SAND_ATTACK:
if (targetData->hitChanceStages[STAT_STAGE_ACCURACY] <= 0)
{
return FALSE;
}
break;
case MOVE_TAUNT:
if (targetData->cringeClassStatus.status == STATUS_TAUNTED)
{
return FALSE;
}
break;
case MOVE_TRICK:
if (!(userData->heldItem.flags & ITEM_FLAG_EXISTS) ||
!(targetData->heldItem.flags & ITEM_FLAG_EXISTS))
{
return FALSE;
}
break;
case MOVE_KNOCK_OFF:
if (!(targetData->heldItem.flags & ITEM_FLAG_EXISTS))
{
return FALSE;
}
break;
case MOVE_FEATHERDANCE:
case MOVE_GROWL:
if (targetData->offensiveStages[STAT_STAGE_ATK] <= 0)
{
return FALSE;
}
break;
case MOVE_ROLE_PLAY:
if (userData->abilities[0] == targetData->abilities[0] &&
userData->abilities[1] == targetData->abilities[1])
{
return FALSE;
}
break;
case MOVE_CURSE:
if (MonsterIsType(user, TYPE_GHOST))
{
if (targetData->curseClassStatus.status == STATUS_CURSED)
{
return FALSE;
}
}
else
{
if (userData->offensiveStages[STAT_STAGE_ATK] >= MAX_STAT_STAGE &&
userData->defensiveStages[STAT_STAGE_DEF] >= MAX_STAT_STAGE)
{
return FALSE;
}
}
break;
case MOVE_IMPRISON:
case MOVE_OBSERVER:
if (targetData->cringeClassStatus.status == STATUS_PAUSED)
{
return FALSE;
}
break;
case MOVE_FALSE_SWIPE:
if (targetData->HP <= 1)
{
return FALSE;
}
break;
case MOVE_LEECH_SEED:
if (targetData->leechSeedClassStatus.status == STATUS_LEECH_SEED)
{
return FALSE;
}
break;
case MOVE_PERISH_SONG:
if (targetData->perishSongTurns != 0)
{
return FALSE;
}
break;
case MOVE_MIMIC:
case MOVE_SKETCH:
if (!HasLastUsedMove(targetData->moves.moves))
{
return FALSE;
}
break;
case MOVE_ATTRACT:
if (targetData->cringeClassStatus.status == STATUS_INFATUATED)
{
return FALSE;
}
break;
case MOVE_WRAP:
if (targetData->frozenClassStatus.status == STATUS_WRAP)
{
return FALSE;
}
if (targetData->frozenClassStatus.status == STATUS_WRAPPED)
{
return FALSE;
}
break;
case MOVE_TOXIC:
if (targetData->burnClassStatus.status == STATUS_BADLY_POISONED)
{
return FALSE;
}
break;
case MOVE_POISON_GAS:
case MOVE_POISONPOWDER:
if (targetData->burnClassStatus.status == STATUS_POISONED)
{
return FALSE;
}
if (targetData->burnClassStatus.status == STATUS_BADLY_POISONED)
{
return FALSE;
}
break;
case MOVE_CONFUSE_RAY:
case MOVE_FLATTER:
case MOVE_SUPERSONIC:
case MOVE_SWAGGER:
case MOVE_SWEET_KISS:
case MOVE_TEETER_DANCE:
case MOVE_TOTTER:
if (targetData->cringeClassStatus.status == STATUS_CONFUSED)
{
return FALSE;
}
break;
}
return TRUE;
}
bool8 HasDisabledMove(Move *moves)
{
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i].moveFlags & MOVE_FLAG_EXISTS && moves[i].moveFlags & MOVE_FLAG_DISABLED)
{
return TRUE;
}
}
if (moves[STRUGGLE_MOVE_INDEX].moveFlags & MOVE_FLAG_DISABLED)
{
return TRUE;
}
return FALSE;
}
bool8 LastUsedMoveOutOfPP(Move *moves)
{
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i].moveFlags & MOVE_FLAG_EXISTS &&
moves[i].moveFlags & MOVE_FLAG_LAST_USED &&
moves[i].PP == 0)
{
return TRUE;
}
}
return FALSE;
}
bool8 HasLastUsedMove(Move *moves)
{
s32 i;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i].moveFlags & MOVE_FLAG_EXISTS && moves[i].moveFlags & MOVE_FLAG_LAST_USED)
{
return TRUE;
}
}
if (moves[STRUGGLE_MOVE_INDEX].moveFlags & MOVE_FLAG_LAST_USED)
{
return TRUE;
}
return FALSE;
}