Archipelago/worlds/pokemon_emerald/opponents.py

117 lines
4.8 KiB
Python
Raw Normal View History

2024-03-14 11:37:10 +00:00
from typing import TYPE_CHECKING, Dict, List, Set
from .data import NUM_REAL_SPECIES, UNEVOLVED_POKEMON, TrainerPokemonData, data
from .options import RandomizeTrainerParties
from .pokemon import filter_species_by_nearby_bst
from .util import int_to_bool_array
if TYPE_CHECKING:
from . import PokemonEmeraldWorld
def randomize_opponent_parties(world: "PokemonEmeraldWorld") -> None:
if world.options.trainer_parties == RandomizeTrainerParties.option_vanilla:
return
from collections import defaultdict
should_match_bst = world.options.trainer_parties in {
RandomizeTrainerParties.option_match_base_stats,
RandomizeTrainerParties.option_match_base_stats_and_type,
}
should_match_type = world.options.trainer_parties in {
RandomizeTrainerParties.option_match_type,
RandomizeTrainerParties.option_match_base_stats_and_type,
}
per_species_tmhm_moves: Dict[int, List[int]] = {}
for trainer in world.modified_trainers:
new_party = []
for pokemon in trainer.party.pokemon:
original_species = data.species[pokemon.species_id]
# Construct progressive tiers of blacklists that can be peeled back if they
# collectively cover too much of the pokedex. A lower index in `blacklists`
# indicates a more important set of species to avoid. Entries at `0` will
# always be blacklisted.
blacklists: Dict[int, List[Set[int]]] = defaultdict(list)
# Blacklist unevolved species
if pokemon.level >= world.options.force_fully_evolved:
blacklists[0].append(UNEVOLVED_POKEMON)
# Blacklist from player options
blacklists[2].append(world.blacklisted_opponent_pokemon)
# Type matching blacklist
if should_match_type:
blacklists[3].append({
species.species_id
for species in world.modified_species.values()
if not bool(set(species.types) & set(original_species.types))
})
merged_blacklist: Set[int] = set()
for max_priority in reversed(sorted(blacklists.keys())):
merged_blacklist = set()
for priority in blacklists.keys():
if priority <= max_priority:
for blacklist in blacklists[priority]:
merged_blacklist |= blacklist
if len(merged_blacklist) < NUM_REAL_SPECIES:
break
else:
raise RuntimeError("This should never happen")
candidates = [
species
for species in world.modified_species.values()
if species.species_id not in merged_blacklist
]
if should_match_bst:
candidates = filter_species_by_nearby_bst(candidates, sum(original_species.base_stats))
new_species = world.random.choice(candidates)
if new_species.species_id not in per_species_tmhm_moves:
per_species_tmhm_moves[new_species.species_id] = sorted({
world.modified_tmhm_moves[i]
for i, is_compatible in enumerate(int_to_bool_array(new_species.tm_hm_compatibility))
if is_compatible and world.modified_tmhm_moves[i] not in world.blacklisted_moves
2024-03-14 11:37:10 +00:00
})
# TMs and HMs compatible with the species
tm_hm_movepool = per_species_tmhm_moves[new_species.species_id]
# Moves the pokemon could have learned by now
level_up_movepool = sorted({
move.move_id
for move in new_species.learnset
if move.move_id != 0 and move.level <= pokemon.level
})
if len(level_up_movepool) < 4:
level_up_moves = [level_up_movepool[i] if i < len(level_up_movepool) else 0 for i in range(4)]
else:
level_up_moves = world.random.sample(level_up_movepool, 4)
if len(tm_hm_movepool) < 4:
hm_moves = list(reversed(list(tm_hm_movepool[i] if i < len(tm_hm_movepool) else 0 for i in range(4))))
else:
hm_moves = world.random.sample(tm_hm_movepool, 4)
# 25% chance to pick a move from TMs or HMs
new_moves = (
hm_moves[0] if world.random.random() < 0.25 else level_up_moves[0],
hm_moves[1] if world.random.random() < 0.25 else level_up_moves[1],
hm_moves[2] if world.random.random() < 0.25 else level_up_moves[2],
hm_moves[3] if world.random.random() < 0.25 else level_up_moves[3]
)
new_party.append(TrainerPokemonData(new_species.species_id, pokemon.level, new_moves))
trainer.party.pokemon = new_party