#include "global.h" #include "dungeon_ai_attack.h" #include "constants/direction.h" #include "constants/dungeon_action.h" #include "constants/iq_skill.h" #include "constants/move_id.h" #include "constants/status.h" #include "constants/tactic.h" #include "constants/targeting.h" #include "constants/type.h" #include "charge_move.h" #include "dungeon_action.h" #include "dungeon_ai_targeting.h" #include "dungeon_ai_targeting_1.h" #include "dungeon_ai_targeting_2.h" #include "dungeon_capabilities_1.h" #include "dungeon_global_data.h" #include "dungeon_map_access.h" #include "dungeon_pokemon_attributes.h" #include "dungeon_random.h" #include "dungeon_random_1.h" #include "dungeon_util.h" #include "dungeon_visibility.h" #include "move_util.h" #include "moves.h" #include "position_util.h" #include "status_checker.h" #include "status_checks.h" #include "status_checks_1.h" #include "targeting.h" #include "targeting_flags.h" #include "type_effectiveness.h" #define REGULAR_ATTACK_INDEX 4 const s16 gRegularAttackWeights[] = {100, 20, 30, 40, 50}; extern bool8 gCanAttackInDirection[NUM_DIRECTIONS]; extern s32 gNumPotentialTargets; extern s32 gPotentialAttackTargetWeights[NUM_DIRECTIONS]; extern u8 gPotentialAttackTargetDirections[NUM_DIRECTIONS]; extern struct DungeonEntity *gPotentialTargets[NUM_DIRECTIONS]; void DecideAttack(struct DungeonEntity *pokemon) { struct DungeonEntityData *pokemonData = pokemon->entityData; s32 i; s32 chargeStatus = CHARGING_STATUS_CHARGE; struct MoveTargetResults moveTargetResults[MAX_MON_MOVES + 1]; bool8 willNotUnlinkMove[MAX_MON_MOVES]; s32 randomWeight; bool8 hasPPChecker; s32 numUsableMoves; s32 total; s32 weightCounter; bool8 hasWeakTypePicker; s32 regularAttackTargetDir; bool8 canTargetRegularAttack; s32 maxWeight; if (CannotAttack(pokemon, FALSE) || ShouldAvoidEnemiesAndShowEffect(pokemon, TRUE) || HasTactic(pokemon, TACTIC_KEEP_YOUR_DISTANCE) || (pokemonData->volatileStatus == VOLATILE_STATUS_CONFUSED && RollPercentChance(gConfusedAttackChance))) { return; } if (pokemonData->chargingStatus != CHARGING_STATUS_NONE) { for (i = 0; i < MAX_MON_MOVES; i++) { if (pokemonData->moves[i].moveFlags & MOVE_FLAG_EXISTS && MoveMatchesChargingStatus(pokemon, &pokemonData->moves[i]) && pokemonData->chargingStatusMoveIndex == i) { s32 chosenMoveIndex; SetAction(&pokemonData->action, DUNGEON_ACTION_USE_MOVE_AI); chosenMoveIndex = i; if (i > 0 && pokemonData->moves[i].moveFlags & MOVE_FLAG_LINKED) { while (--chosenMoveIndex > 0) { if (!(pokemonData->moves[chosenMoveIndex].moveFlags & MOVE_FLAG_LINKED)) { break; } } } pokemonData->action.actionUseIndex = chosenMoveIndex; TargetTileInFront(pokemon); return; } } } total = 0; numUsableMoves = 0; for (i = 0; i < MAX_MON_MOVES; i++) { struct PokemonMove *move = &pokemonData->moves[i]; if (pokemonData->moves[i].moveFlags & MOVE_FLAG_EXISTS) { if (pokemonData->moves[i].moveFlags & MOVE_FLAG_ENABLED) { numUsableMoves++; } total += move->PP; } } if (total == 0) { struct PokemonMove struggle; InitPokemonMove(&struggle, MOVE_STRUGGLE); FindMoveTarget(&moveTargetResults[0], pokemon, &struggle); if (moveTargetResults[0].moveUsable) { SetAction(&pokemonData->action, DUNGEON_ACTION_STRUGGLE); pokemonData->action.facingDir = moveTargetResults[0].targetDir & DIRECTION_MASK; TargetTileInFront(pokemon); } return; } hasWeakTypePicker = HasIQSkill(pokemon, IQ_SKILL_WEAK_TYPE_PICKER); hasPPChecker = HasIQSkill(pokemon, IQ_SKILL_PP_CHECKER) != FALSE; total = 0; for (i = 0; i < MAX_MON_MOVES; i++) { willNotUnlinkMove[i] = TRUE; } if (hasPPChecker) { s32 minPP = 99; s32 linkedMoveStartIndex = 0; // Linked moves unlink if any move in the chain runs out of PP. // With PP Checker, the AI avoids this by not using linked moves if any move in the chain has 1 PP left. // This requires a separate check from the 0-PP check used for unlinked moves. for (i = 0; i < MAX_MON_MOVES; i++) { struct PokemonMove *move = &pokemonData->moves[i]; if (!(move->moveFlags & MOVE_FLAG_EXISTS)) { break; } if (i != 0 && !(move->moveFlags & MOVE_FLAG_LINKED)) { if (linkedMoveStartIndex + 1 < i && minPP <= 1 && linkedMoveStartIndex + 1 <= i) { s32 j; for (j = linkedMoveStartIndex; j < i; j++) { willNotUnlinkMove[j] = FALSE; } } minPP = move->PP; linkedMoveStartIndex = i; } else { s32 newMinPP = move->PP; if (newMinPP > minPP) { newMinPP = minPP; } minPP = newMinPP; } } if (linkedMoveStartIndex + 1 < i && minPP <= 1 && linkedMoveStartIndex + 1 <= i) { s32 j; for (j = linkedMoveStartIndex; j < i; j++) { willNotUnlinkMove[j] = FALSE; } } } for (i = 0; i < MAX_MON_MOVES; i++) { struct PokemonMove *move; moveTargetResults[i].moveUsable = FALSE; move = &pokemonData->moves[i]; if (move->moveFlags & MOVE_FLAG_EXISTS && willNotUnlinkMove[i] && IsMoveIndexUsable(pokemon, i, hasPPChecker) && move->moveFlags & MOVE_FLAG_ENABLED) { moveTargetResults[i].moveUsable = TRUE; if (pokemonData->chargingStatus == chargeStatus) { if (move->moveID == MOVE_CHARGE) { moveTargetResults[i].moveWeight = 0; continue; } else if (GetMoveTypeForPokemon(pokemon, move) == TYPE_ELECTRIC) { moveTargetResults[i].moveWeight = GetMoveWeight(move); } else { moveTargetResults[i].moveWeight = 1; } } else if (hasWeakTypePicker) { struct MoveTargetResults *results = &moveTargetResults[i]; results->moveWeight = FindMoveTarget(results, pokemon, move); } else { moveTargetResults[i].moveWeight = GetMoveWeight(move); } total += moveTargetResults[i].moveWeight; } } moveTargetResults[REGULAR_ATTACK_INDEX].moveWeight = 0; if (!HasIQSkill(pokemon, IQ_SKILL_EXCLUSIVE_MOVE_USER) && pokemonData->chargingStatus != chargeStatus) { moveTargetResults[REGULAR_ATTACK_INDEX].moveUsable = TRUE; if (pokemonData->chargingStatus == chargeStatus) { // Never reached? Charge already skips the regular attack in the outer block. moveTargetResults[REGULAR_ATTACK_INDEX].moveWeight = 1; } else if (hasWeakTypePicker) { moveTargetResults[REGULAR_ATTACK_INDEX].moveWeight = 2; } else { moveTargetResults[REGULAR_ATTACK_INDEX].moveWeight = gRegularAttackWeights[numUsableMoves]; } total += moveTargetResults[REGULAR_ATTACK_INDEX].moveWeight; } if (hasWeakTypePicker) { s32 i; maxWeight = 0; total = 0; for (i = 0; i < MAX_MON_MOVES + 1; i++) { if (!moveTargetResults[i].moveUsable) { moveTargetResults[i].moveWeight = 0; } else if (maxWeight < moveTargetResults[i].moveWeight) { maxWeight = moveTargetResults[i].moveWeight; } } for (i = 0; i < MAX_MON_MOVES + 1; i++) { if (moveTargetResults[i].moveUsable) { if (maxWeight != moveTargetResults[i].moveWeight) { moveTargetResults[i].moveWeight = 0; } total += moveTargetResults[i].moveWeight; } } } if (total == 0) { return; } randomWeight = DungeonRandomCapped(total); weightCounter = 0; if (!HasIQSkill(pokemon, IQ_SKILL_EXCLUSIVE_MOVE_USER)) { canTargetRegularAttack = TargetRegularAttack(pokemon, ®ularAttackTargetDir, TRUE); } else { canTargetRegularAttack = FALSE; regularAttackTargetDir = DIRECTION_SOUTH; } for (i = 0; i <= REGULAR_ATTACK_INDEX; i++) { if (moveTargetResults[i].moveUsable && moveTargetResults[i].moveWeight != 0) { weightCounter += moveTargetResults[i].moveWeight; if (weightCounter >= randomWeight) { if (i == REGULAR_ATTACK_INDEX) { if (canTargetRegularAttack) { SetAction(&pokemonData->action, DUNGEON_ACTION_REGULAR_ATTACK); pokemonData->action.facingDir = regularAttackTargetDir & DIRECTION_MASK; TargetTileInFront(pokemon); } } else { FindMoveTarget(&moveTargetResults[i], pokemon, &pokemonData->moves[i]); if (moveTargetResults[i].moveUsable) { s32 chosenMoveIndex; SetAction(&pokemonData->action, DUNGEON_ACTION_USE_MOVE_AI); chosenMoveIndex = i; if (i > 0 && pokemonData->moves[i].moveFlags & MOVE_FLAG_LINKED) { while (--chosenMoveIndex > 0) { if (!(pokemonData->moves[chosenMoveIndex].moveFlags & MOVE_FLAG_LINKED)) { break; } } } pokemonData->action.facingDir = moveTargetResults[i].targetDir & DIRECTION_MASK; pokemonData->action.actionUseIndex = chosenMoveIndex; TargetTileInFront(pokemon); } else { break; } } return; } } } if (canTargetRegularAttack) { SetAction(&pokemonData->action, DUNGEON_ACTION_REGULAR_ATTACK); pokemonData->action.facingDir = regularAttackTargetDir & DIRECTION_MASK; TargetTileInFront(pokemon); } } s32 FindMoveTarget(struct MoveTargetResults *moveTargetResults, struct DungeonEntity *pokemon, struct PokemonMove *move) { s32 targetingFlags; s32 moveWeight = 1; struct DungeonEntityData *pokemonData = pokemon->entityData; s32 numPotentialTargets = 0; s32 i; bool8 hasStatusChecker; s32 rangeTargetingFlags; s32 rangeTargetingFlags2; for (i = 0; i < NUM_DIRECTIONS; i++) { gCanAttackInDirection[i] = FALSE; } targetingFlags = GetMoveTargetingFlagsForPokemon(pokemon, move, TRUE); hasStatusChecker = HasIQSkill(pokemon, IQ_SKILL_STATUS_CHECKER); moveTargetResults->moveUsable = FALSE; if ((pokemonData->volatileStatus == VOLATILE_STATUS_TAUNTED && !MoveDealsDirectDamage(move)) || (hasStatusChecker && !CanUseOnSelfWithStatusChecker(pokemon, move))) { return 1; } rangeTargetingFlags = targetingFlags & 0xF0; if (rangeTargetingFlags == TARGETING_FLAG_TARGET_OTHER || rangeTargetingFlags == TARGETING_FLAG_TARGET_FRONTAL_CONE || rangeTargetingFlags == TARGETING_FLAG_TARGET_AROUND) { if (pokemonData->eyesightStatus == EYESIGHT_STATUS_BLINKER) { u8 facingDir = pokemonData->action.facingDir; i = facingDir; // Fixes a regswap. if (!gCanAttackInDirection[i]) { gCanAttackInDirection[i] = TRUE; gPotentialAttackTargetDirections[numPotentialTargets] = i; gPotentialAttackTargetWeights[numPotentialTargets] = 99; gPotentialTargets[numPotentialTargets] = NULL; numPotentialTargets++; } } else { for (i = 0; i < NUM_DIRECTIONS; i++) { // Double assignment to fix a regswap. s16 rangeTargetingFlags = rangeTargetingFlags2 = targetingFlags & 0xF0; struct MapTile *adjacentTile = GetMapTile_1(pokemon->posWorld.x + gAdjacentTileOffsets[i].x, pokemon->posWorld.y + gAdjacentTileOffsets[i].y); struct DungeonEntity *adjacentPokemon = adjacentTile->pokemon; if (adjacentPokemon != NULL && GetEntityType(adjacentPokemon) == ENTITY_POKEMON) { if (rangeTargetingFlags != TARGETING_FLAG_TARGET_FRONTAL_CONE && rangeTargetingFlags != TARGETING_FLAG_TARGET_AROUND) { if (!CanAttackInFront(pokemon, i)) { continue; } } numPotentialTargets = WeightMoveIfUsable(numPotentialTargets, targetingFlags, pokemon, adjacentPokemon, move, hasStatusChecker); } } } } else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_ROOM) { s32 i; for (i = 0; i < DUNGEON_MAX_POKEMON; i++) { struct DungeonEntity *target = gDungeonGlobalData->allPokemon[i]; if (EntityExists(target) && CanSee(pokemon, target)) { numPotentialTargets = WeightMoveIfUsable(numPotentialTargets, targetingFlags, pokemon, target, move, hasStatusChecker); } } } else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_2_TILES_AHEAD) { for (i = 0; i < NUM_DIRECTIONS; i++) { struct MapTile *targetTile = GetMapTile_1(pokemon->posWorld.x + gAdjacentTileOffsets[i].x, pokemon->posWorld.y + gAdjacentTileOffsets[i].y); if (CanAttackInFront(pokemon, i)) { struct DungeonEntity *targetPokemon = targetTile->pokemon; if (targetPokemon != NULL && GetEntityType(targetPokemon) == ENTITY_POKEMON) { s32 prevNumPotentialTargets = numPotentialTargets; numPotentialTargets = WeightMoveIfUsable(numPotentialTargets, targetingFlags, pokemon, targetPokemon, move, hasStatusChecker); if (prevNumPotentialTargets != numPotentialTargets) { continue; } } targetTile = GetMapTile_1(pokemon->posWorld.x + gAdjacentTileOffsets[i].x * 2, pokemon->posWorld.y + gAdjacentTileOffsets[i].y * 2); targetPokemon = targetTile->pokemon; if (targetPokemon != NULL && GetEntityType(targetPokemon) == ENTITY_POKEMON) { numPotentialTargets = WeightMoveIfUsable(numPotentialTargets, targetingFlags, pokemon, targetPokemon, move, hasStatusChecker); } } } } else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_LINE || rangeTargetingFlags == TARGETING_FLAG_CUT_CORNERS) { s32 maxRange = 1; s32 i; if (rangeTargetingFlags == TARGETING_FLAG_TARGET_LINE) { maxRange = 10; } for (i = 0; i < DUNGEON_MAX_POKEMON; i++) { struct DungeonEntity *target = gDungeonGlobalData->allPokemon[i]; if (EntityExists(target) && pokemon != target) { s32 facingDir = CalculateFacingDir(&pokemon->posWorld, &target->posWorld); if (!gCanAttackInDirection[facingDir] && CanSee(pokemon, target) && IsTargetInLineRange(pokemon, target, maxRange) && CanUseStatusMove(targetingFlags, pokemon, target, move, hasStatusChecker) && IsTargetStraightAhead(pokemon, target, facingDir, maxRange)) { gCanAttackInDirection[facingDir] = TRUE; gPotentialAttackTargetDirections[numPotentialTargets] = facingDir; gPotentialAttackTargetWeights[numPotentialTargets] = WeightMove(pokemon, targetingFlags, target, GetMoveTypeForPokemon(pokemon, move)); gPotentialTargets[numPotentialTargets] = target; numPotentialTargets++; } } } } else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_FLOOR) { s32 i; for (i = 0; i < DUNGEON_MAX_POKEMON; i++) { struct DungeonEntity *target = gDungeonGlobalData->allPokemon[i]; if (EntityExists(target)) { numPotentialTargets = WeightMoveIfUsable(numPotentialTargets, targetingFlags, pokemon, target, move, hasStatusChecker); } } } else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_SELF) { numPotentialTargets = WeightMoveIfUsable(numPotentialTargets, targetingFlags, pokemon, pokemon, move, hasStatusChecker); } if (numPotentialTargets == 0) { moveTargetResults->moveUsable = FALSE; } else { s32 totalWeight = 0; s32 maxWeight = 0; s32 weightCounter; s32 i; for (i = 0; i < numPotentialTargets; i++) { if (maxWeight < gPotentialAttackTargetWeights[i]) { maxWeight = gPotentialAttackTargetWeights[i]; } } for (i = 0; i < numPotentialTargets; i++) { if (maxWeight != gPotentialAttackTargetWeights[i]) { gPotentialAttackTargetWeights[i] = 0; } } moveWeight = maxWeight; for (i = 0; i < numPotentialTargets; i++) { totalWeight += gPotentialAttackTargetWeights[i]; } weightCounter = DungeonRandomCapped(totalWeight); for (i = 0; i < numPotentialTargets; i++) { weightCounter -= gPotentialAttackTargetWeights[i]; if (weightCounter < 0) { break; } } moveTargetResults->moveUsable = TRUE; moveTargetResults->targetDir = gPotentialAttackTargetDirections[i]; moveTargetResults->moveWeight = 8; } return moveWeight; } bool8 IsTargetInLineRange(struct DungeonEntity *user, struct DungeonEntity *target, s32 range) { s32 posDiffX = user->posWorld.x - target->posWorld.x; s32 posDiffY, maxPosDiff; s32 direction; if (posDiffX < 0) { posDiffX = -posDiffX; } posDiffY = user->posWorld.y - target->posWorld.y; if (posDiffY < 0) { posDiffY = -posDiffY; } maxPosDiff = posDiffY; if (posDiffY < posDiffX) { maxPosDiff = posDiffX; } if (maxPosDiff > RANGED_ATTACK_RANGE || maxPosDiff > range) { return FALSE; } direction = -1; if (posDiffX == posDiffY) { if (user->posWorld.x < target->posWorld.x && (user->posWorld.y < target->posWorld.y || user->posWorld.y > target->posWorld.y)) { returnTrue: return TRUE; } if (user->posWorld.x > target->posWorld.x); // Fixes register loading order. direction = DIRECTION_SOUTHWEST; if (user->posWorld.x <= target->posWorld.x || user->posWorld.y <= target->posWorld.y) { goto checkDirectionSet; } goto returnTrue; } else if (user->posWorld.x == target->posWorld.x && user->posWorld.y < target->posWorld.y) { return TRUE; } else if (user->posWorld.x < target->posWorld.x && user->posWorld.y == target->posWorld.y) { return TRUE; } else if (user->posWorld.x == target->posWorld.x && user->posWorld.y > target->posWorld.y) { return TRUE; } else if (user->posWorld.x > target->posWorld.x && user->posWorld.y == target->posWorld.y) { direction = DIRECTION_WEST; } checkDirectionSet: if (direction < 0) { return FALSE; } return TRUE; } s32 WeightMoveIfUsable(s32 numPotentialTargets, s32 targetingFlags, struct DungeonEntity *user, struct DungeonEntity *target, struct PokemonMove *move, bool32 hasStatusChecker) { s32 facingDir; s32 targetingFlags2 = (s16) targetingFlags; bool8 hasStatusChecker2 = hasStatusChecker; struct DungeonEntityData *userData = user->entityData; if ((user->posWorld.x == target->posWorld.x && user->posWorld.y == target->posWorld.y) || (targetingFlags2 & 0xF0) == TARGETING_FLAG_TARGET_ROOM || (targetingFlags2 & 0xF0) == TARGETING_FLAG_TARGET_FLOOR || (targetingFlags2 & 0xF0) == TARGETING_FLAG_TARGET_SELF) { facingDir = userData->action.facingDir; } else { facingDir = CalculateFacingDir(&user->posWorld, &target->posWorld); } if (!gCanAttackInDirection[facingDir] && CanUseStatusMove(targetingFlags2, user, target, move, hasStatusChecker2)) { gCanAttackInDirection[facingDir] = TRUE; do { gPotentialAttackTargetDirections[numPotentialTargets] = facingDir; } while (0); gPotentialAttackTargetWeights[numPotentialTargets] = WeightMove(user, targetingFlags2, target, GetMoveTypeForPokemon(user, move)); gPotentialTargets[numPotentialTargets] = target; numPotentialTargets++; } return numPotentialTargets; } bool8 CanUseStatusMove(s32 targetingFlags, struct DungeonEntity *user, struct DungeonEntity *target, struct PokemonMove *move, bool32 hasStatusChecker) { struct DungeonEntityData *targetData; s32 targetingFlags2 = (s16) targetingFlags; bool8 hasStatusChecker2 = hasStatusChecker; bool8 hasTarget = FALSE; u32 categoryTargetingFlags = targetingFlags2 & 0xF; u32 *categoryTargetingFlags2 = &categoryTargetingFlags; // Fixes a regswap. if (*categoryTargetingFlags2 == TARGETING_FLAG_TARGET_OTHER) { if (CanTarget(user, target, FALSE, TRUE) == TARGET_CAPABILITY_CAN_TARGET) { hasTarget = TRUE; } } else if (categoryTargetingFlags == TARGETING_FLAG_HEAL_TEAM) { goto checkCanTarget; } else if (categoryTargetingFlags == TARGETING_FLAG_LONG_RANGE) { targetData = target->entityData; goto checkThirdParty; } else if (categoryTargetingFlags == TARGETING_FLAG_ATTACK_ALL) { targetData = target->entityData; if (user == target) { goto returnFalse; } checkThirdParty: hasTarget = TRUE; if (targetData->shopkeeperMode == SHOPKEEPER_FRIENDLY || targetData->clientType == CLIENT_TYPE_DONT_MOVE || targetData->clientType == CLIENT_TYPE_CLIENT) { returnFalse: return FALSE; } } else if (categoryTargetingFlags == TARGETING_FLAG_BOOST_TEAM) { if (user == target) { goto returnFalse; } checkCanTarget: if (CanTarget(user, target, FALSE, TRUE) == TARGET_CAPABILITY_CANNOT_ATTACK) { hasTarget = TRUE; } } else if ((u16) (categoryTargetingFlags - 3) <= 1) // categoryTargetingFlags == TARGETING_FLAG_ITEM { hasTarget = TRUE; } if (hasTarget) { if (hasStatusChecker2) { if (!CanUseOnTargetWithStatusChecker(user, target, move)) { goto returnFalse; } if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_SET_TRAP) { goto rollMoveUseChance; } else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_HEAL_HP) { if (!HasQuarterHPOrLess(target)) { if (*categoryTargetingFlags2); goto returnFalse; } } else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_HEAL_STATUS) { if (!HasNegativeStatus(target)) { if (*categoryTargetingFlags2); // Flips the conditional. goto returnFalse; } } else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_DREAM_EATER) { if (!IsSleeping(target)) { if (*categoryTargetingFlags2); // Flips the conditional. goto returnFalse; } } else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_EXPOSE) { targetData = target->entityData; if ((targetData->types[0] != TYPE_GHOST && targetData->types[1] != TYPE_GHOST) || targetData->exposedStatus) { if (*categoryTargetingFlags2); // Flips the conditional. goto returnFalse; } } else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_HEAL_ALL) { if (!HasNegativeStatus(target) && !HasQuarterHPOrLess(target)) { if (*categoryTargetingFlags2); // Flips the conditional. goto returnFalse; } } } else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_SET_TRAP) { s32 useChance; rollMoveUseChance: useChance = GetMoveAccuracy(move, ACCURACY_TYPE_USE_CHANCE); if (DungeonRandomCapped(100) >= useChance) { goto returnFalse; } } } return hasTarget; } s32 WeightMove(struct DungeonEntity *user, s32 targetingFlags, struct DungeonEntity *target, u32 moveType) { #ifndef NONMATCHING register struct DungeonEntityData *targetData asm("r4"); #else struct DungeonEntityData *targetData; #endif s32 targetingFlags2 = (s16) targetingFlags; u8 moveType2 = moveType; u8 weight = 1; struct DungeonEntityData *targetData2; targetData2 = targetData = target->entityData; if (!targetData->isEnemy || (targetingFlags2 & 0xF) != TARGETING_FLAG_TARGET_OTHER) { return 1; } else if (HasIQSkill(user, IQ_SKILL_EXP_GO_GETTER)) { // BUG: expYieldRankings has lower values as the Pokémon's experience yield increases. // This causes Exp. Go-Getter to prioritize Pokémon worth less experience // instead of Pokémon worth more experience. weight = gDungeonGlobalData->expYieldRankings[targetData->entityID]; } else if (HasIQSkill(user, IQ_SKILL_EFFICIENCY_EXPERT)) { weight = -12 - targetData2->HP; if (weight == 0) { weight = 1; } } else if (HasIQSkill(user, IQ_SKILL_WEAK_TYPE_PICKER)) { weight = WeightWeakTypePicker(user, target, moveType2) + 1; } return weight; } bool8 TargetRegularAttack(struct DungeonEntity *pokemon, u32 *targetDir, bool8 checkPetrified) { struct DungeonEntityData *pokemonData = pokemon->entityData; s32 numPotentialTargets = 0; s32 facingDir = pokemonData->action.facingDir; s32 faceTurnLimit = pokemonData->eyesightStatus == EYESIGHT_STATUS_BLINKER ? 1 : 8; s32 i; s32 potentialAttackTargetDirections[NUM_DIRECTIONS]; s32 potentialAttackTargetWeights[NUM_DIRECTIONS]; bool8 hasTargetingIQ = HasIQSkill(pokemon, IQ_SKILL_EXP_GO_GETTER) || HasIQSkill(pokemon, IQ_SKILL_EFFICIENCY_EXPERT); bool8 hasStatusChecker = HasIQSkill(pokemon, IQ_SKILL_STATUS_CHECKER); for (i = 0; i < faceTurnLimit; i++, facingDir++) { struct DungeonEntity *target; facingDir &= DIRECTION_MASK; target = GetMapTile_1(pokemon->posWorld.x + gAdjacentTileOffsets[facingDir].x, pokemon->posWorld.y + gAdjacentTileOffsets[facingDir].y)->pokemon; if (target != NULL && GetEntityType(target) == ENTITY_POKEMON && CanAttackInFront(pokemon, facingDir) && CanTarget(pokemon, target, FALSE, checkPetrified) == TARGET_CAPABILITY_CAN_TARGET && (!hasStatusChecker || target->entityData->immobilizeStatus != IMMOBILIZE_STATUS_FROZEN)) { potentialAttackTargetDirections[numPotentialTargets] = facingDir; potentialAttackTargetWeights[numPotentialTargets] = WeightMove(pokemon, TARGETING_FLAG_TARGET_OTHER, target, TYPE_NONE); if (!hasTargetingIQ) { *targetDir = facingDir; return TRUE; } numPotentialTargets++; } } if (numPotentialTargets == 0) { return FALSE; } else { s32 totalWeight = 0; s32 maxWeight = 0; s32 weightCounter; s32 i; for (i = 0; i < numPotentialTargets; i++) { if (maxWeight < potentialAttackTargetWeights[i]) { maxWeight = potentialAttackTargetWeights[i]; } } for (i = 0; i < numPotentialTargets; i++) { if (maxWeight != potentialAttackTargetWeights[i]) { potentialAttackTargetWeights[i] = 0; } } for (i = 0; i < numPotentialTargets; i++) { totalWeight += potentialAttackTargetWeights[i]; } weightCounter = DungeonRandomCapped(totalWeight); for (i = 0; i < numPotentialTargets; i++) { weightCounter -= potentialAttackTargetWeights[i]; if (weightCounter < 0) { break; } } *targetDir = potentialAttackTargetDirections[i]; return TRUE; } } bool8 IsTargetStraightAhead(struct DungeonEntity *pokemon, struct DungeonEntity *targetPokemon, s32 facingDir, s32 maxRange) { s32 posDiffX = pokemon->posWorld.x - targetPokemon->posWorld.x; s32 effectiveMaxRange; if (posDiffX < 0) { posDiffX = -posDiffX; } effectiveMaxRange = pokemon->posWorld.y - targetPokemon->posWorld.y; if (effectiveMaxRange < 0) { effectiveMaxRange = -effectiveMaxRange; } if (effectiveMaxRange < posDiffX) { effectiveMaxRange = posDiffX; } if (effectiveMaxRange > maxRange) { effectiveMaxRange = maxRange; } if (!HasIQSkill(pokemon, IQ_SKILL_COURSE_CHECKER)) { // BUG: effectiveMaxRange is already capped at maxRange, so this condition always evaluates to TRUE. // The AI also has range checks elsewhere, so this doesn't become an issue in most cases. // If the AI has the Long Toss or Pierce statuses and Course Checker is disabled, // this incorrect check causes the AI to throw items at targets further than 10 tiles away. if (effectiveMaxRange <= maxRange) { return TRUE; } } else { s32 currentPosX = pokemon->posWorld.x; s32 currentPosY = pokemon->posWorld.y; s32 adjacentTileOffsetX = gAdjacentTileOffsets[facingDir].x; s32 adjacentTileOffsetY = gAdjacentTileOffsets[facingDir].y; s32 i; for (i = 0; i <= effectiveMaxRange; i++) { struct MapTile *mapTile; currentPosX += adjacentTileOffsetX; currentPosY += adjacentTileOffsetY; if (currentPosX <= 0 || currentPosY <= 0 || currentPosX >= DUNGEON_MAX_SIZE_X - 1 || currentPosY >= DUNGEON_MAX_SIZE_Y - 1) { break; } while (0); // Extra label needed to swap branch locations in ASM. mapTile = GetMapTile_1(currentPosX, currentPosY); if (!(mapTile->tileType & (TILE_TYPE_FLOOR | TILE_TYPE_LIQUID))) { break; } if (mapTile->pokemon == targetPokemon) { return TRUE; } if (mapTile->pokemon != NULL) { break; } } } return FALSE; }