mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00

## What is this fixing or adding? Adds a large number of new options, including: - Door Shuffle - Sphere-based level scaling - Key Item and Pokedex requirement options to reach the Elite Four - Split Card Key option - Dexsanity option can be set to a percentage of Pokémon that will be checks - Stonesanity: remove the stones from the Celadon Department Store and shuffle them into the item pool, replacing 4 of the 5 Moon Stone items - Sleep Trap items option - Randomize Move Types option - Town Map Fly Location option, to unlock a flight location when finding/receiving the Town Map Many enhancements have been made, including: - Game allows you to continue your save file _from Pallet Town_ as a way to save warp back to the beginning of the game. The one-way drop from Diglett's Cave to north Route 2 that had been added to the randomizer has been removed. - Client auto-hints some locations when you are able to see the item before you can obtain it (but would only show AP Item if it is for another player), including Bike Shop, Oak's Aides, Celadon Prize Corner, and the unchosen Fossil location. Various bugs have been fixed, including: - Route 13 wild Pokémon not correctly logically requiring Cut - Vanilla tm/hm compatibility options giving compatibility for many TMs/HMs erroneously - If an item that exists in multiple quantities in the item pool is chosen for one of the locations that are pre-filled with local items, it will continue placing that same item in the remaining locations as long as more of that item exist - `start_with` option for `randomize_pokedex` still shuffling a Pokédex into the item pool - The obedience threshold levels being incorrect with 0-2 badges, with Pokémon up to level 30 obeying with 0-1 badges and up to 10 with 2 badges - Receiving a DeathLink trigger in the Safari Zone causing issues. Now, you will have your steps remaining set to 0 instead of blacking out when you're in the Safari Zone. Many location names have been changed, as location names are automatically prepended using the Region name and a large number of areas have been split into new regions as part of the overhaul to add Door Shuffle.
275 lines
16 KiB
Python
275 lines
16 KiB
Python
from copy import deepcopy
|
|
from . import poke_data
|
|
from .locations import location_data
|
|
|
|
|
|
def get_encounter_slots(self):
|
|
encounter_slots = deepcopy([location for location in location_data if location.type == "Wild Encounter"])
|
|
|
|
for location in encounter_slots:
|
|
if isinstance(location.original_item, list):
|
|
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value]
|
|
return encounter_slots
|
|
|
|
|
|
def get_base_stat_total(mon):
|
|
return (poke_data.pokemon_data[mon]["atk"] + poke_data.pokemon_data[mon]["def"]
|
|
+ poke_data.pokemon_data[mon]["hp"] + poke_data.pokemon_data[mon]["spd"]
|
|
+ poke_data.pokemon_data[mon]["spc"])
|
|
|
|
|
|
def randomize_pokemon(self, mon, mons_list, randomize_type, random):
|
|
if randomize_type in [1, 3]:
|
|
type_mons = [pokemon for pokemon in mons_list if any([poke_data.pokemon_data[mon][
|
|
"type1"] in [self.local_poke_data[pokemon]["type1"], self.local_poke_data[pokemon]["type2"]],
|
|
poke_data.pokemon_data[mon]["type2"] in [self.local_poke_data[pokemon]["type1"],
|
|
self.local_poke_data[pokemon]["type2"]]])]
|
|
if not type_mons:
|
|
type_mons = mons_list.copy()
|
|
if randomize_type == 3:
|
|
stat_base = get_base_stat_total(mon)
|
|
type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
|
mon = type_mons[round(random.triangular(0, len(type_mons) - 1, 0))]
|
|
if randomize_type == 2:
|
|
stat_base = get_base_stat_total(mon)
|
|
mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
|
mon = mons_list[round(random.triangular(0, 50, 0))]
|
|
elif randomize_type == 4:
|
|
mon = random.choice(mons_list)
|
|
return mon
|
|
|
|
|
|
def process_trainer_data(self):
|
|
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
|
or self.multiworld.trainer_legendaries[self.player].value]
|
|
unevolved_mons = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
|
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
|
evolved_mons = [mon for mon in mons_list if mon not in unevolved_mons]
|
|
rival_map = {
|
|
"Charmander": self.multiworld.get_location("Oak's Lab - Starter 1", self.player).item.name[9:], # strip the
|
|
"Squirtle": self.multiworld.get_location("Oak's Lab - Starter 2", self.player).item.name[9:], # 'Missable'
|
|
"Bulbasaur": self.multiworld.get_location("Oak's Lab - Starter 3", self.player).item.name[9:], # from the name
|
|
}
|
|
|
|
def add_evolutions():
|
|
for a, b in rival_map.copy().items():
|
|
if a in poke_data.evolves_to and poke_data.evolves_to[a] not in rival_map:
|
|
if b in poke_data.evolves_to:
|
|
rival_map[poke_data.evolves_to[a]] = poke_data.evolves_to[b]
|
|
else:
|
|
rival_map[poke_data.evolves_to[a]] = b
|
|
add_evolutions()
|
|
add_evolutions()
|
|
parties_objs = [location for location in self.multiworld.get_locations(self.player)
|
|
if location.type == "Trainer Parties"]
|
|
# Process Rival parties in order "Route 22 " is not a typo
|
|
parties_objs.sort(key=lambda i: 0 if "Oak's Lab" in i.name else 1 if "Route 22 " in i.name else 2 if "Cerulean City"
|
|
in i.name else 3 if "Anne" in i.name else 4 if "Pokemon Tower" in i.name else 5 if "Silph" in
|
|
i.name else 6 if "Route 22-F" in i.name else 7 if "Champion" in i.name else 8)
|
|
for parties in parties_objs:
|
|
parties_data = parties.party_data
|
|
for party in parties_data:
|
|
if party["party"] and isinstance(party["party"][0], list):
|
|
# only for Rival parties
|
|
for rival_party in party["party"]:
|
|
for i, mon in enumerate(rival_party):
|
|
if mon in ("Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard",
|
|
"Squirtle", "Wartortle", "Blastoise"):
|
|
if self.multiworld.randomize_starter_pokemon[self.player]:
|
|
rival_party[i] = rival_map[mon]
|
|
elif self.multiworld.randomize_trainer_parties[self.player]:
|
|
if mon in rival_map:
|
|
rival_party[i] = rival_map[mon]
|
|
else:
|
|
new_mon = randomize_pokemon(self, mon,
|
|
unevolved_mons if mon in unevolved_mons else evolved_mons,
|
|
self.multiworld.randomize_trainer_parties[self.player].value,
|
|
self.multiworld.random)
|
|
rival_map[mon] = new_mon
|
|
rival_party[i] = new_mon
|
|
add_evolutions()
|
|
else:
|
|
if self.multiworld.randomize_trainer_parties[self.player]:
|
|
for i, mon in enumerate(party["party"]):
|
|
party["party"][i] = randomize_pokemon(self, mon, mons_list,
|
|
self.multiworld.randomize_trainer_parties[self.player].value,
|
|
self.multiworld.random)
|
|
|
|
|
|
def process_pokemon_locations(self):
|
|
starter_slots = deepcopy([location for location in location_data if location.type == "Starter Pokemon"])
|
|
legendary_slots = deepcopy([location for location in location_data if location.type == "Legendary Pokemon"])
|
|
static_slots = deepcopy([location for location in location_data if location.type in
|
|
["Static Pokemon", "Missable Pokemon"]])
|
|
legendary_mons = deepcopy([slot.original_item for slot in legendary_slots])
|
|
|
|
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
|
|
|
|
mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
|
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
|
if self.multiworld.randomize_legendary_pokemon[self.player] == "vanilla":
|
|
for slot in legendary_slots:
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
location.place_locked_item(self.create_item("Static " + slot.original_item))
|
|
elif self.multiworld.randomize_legendary_pokemon[self.player] == "shuffle":
|
|
self.multiworld.random.shuffle(legendary_mons)
|
|
for slot in legendary_slots:
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
mon = legendary_mons.pop()
|
|
location.place_locked_item(self.create_item("Static " + mon))
|
|
placed_mons[mon] += 1
|
|
elif self.multiworld.randomize_legendary_pokemon[self.player] == "static":
|
|
static_slots = static_slots + legendary_slots
|
|
self.multiworld.random.shuffle(static_slots)
|
|
static_slots.sort(key=lambda s: s.name != "Pokemon Tower 6F - Restless Soul")
|
|
while legendary_slots:
|
|
swap_slot = legendary_slots.pop()
|
|
slot = static_slots.pop()
|
|
slot_type = slot.type.split()[0]
|
|
if slot_type == "Legendary":
|
|
slot_type = "Static"
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
|
|
swap_slot.original_item = slot.original_item
|
|
elif self.multiworld.randomize_legendary_pokemon[self.player] == "any":
|
|
static_slots = static_slots + legendary_slots
|
|
|
|
for slot in static_slots:
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value
|
|
slot_type = slot.type.split()[0]
|
|
if slot_type == "Legendary":
|
|
slot_type = "Static"
|
|
if not randomize_type:
|
|
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
|
else:
|
|
mon = self.create_item(slot_type + " " +
|
|
randomize_pokemon(self, slot.original_item, mons_list, randomize_type,
|
|
self.multiworld.random))
|
|
location.place_locked_item(mon)
|
|
if slot_type != "Missable":
|
|
placed_mons[mon.name.replace("Static ", "")] += 1
|
|
|
|
chosen_mons = set()
|
|
for slot in starter_slots:
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value
|
|
slot_type = "Missable"
|
|
if not randomize_type:
|
|
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
|
else:
|
|
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
|
|
randomize_type, self.multiworld.random))
|
|
while mon.name in chosen_mons:
|
|
mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
|
|
randomize_type, self.multiworld.random))
|
|
chosen_mons.add(mon.name)
|
|
location.place_locked_item(mon)
|
|
|
|
encounter_slots_master = get_encounter_slots(self)
|
|
encounter_slots = encounter_slots_master.copy()
|
|
|
|
zone_mapping = {}
|
|
if self.multiworld.randomize_wild_pokemon[self.player]:
|
|
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
|
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
|
self.multiworld.random.shuffle(encounter_slots)
|
|
locations = []
|
|
for slot in encounter_slots:
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
zone = " - ".join(location.name.split(" - ")[:-1])
|
|
if zone not in zone_mapping:
|
|
zone_mapping[zone] = {}
|
|
original_mon = slot.original_item
|
|
if self.multiworld.area_1_to_1_mapping[self.player] and original_mon in zone_mapping[zone]:
|
|
mon = zone_mapping[zone][original_mon]
|
|
else:
|
|
mon = randomize_pokemon(self, original_mon, mons_list,
|
|
self.multiworld.randomize_wild_pokemon[self.player].value, self.multiworld.random)
|
|
#
|
|
while ("Pokemon Tower 6F" in slot.name and
|
|
self.multiworld.get_location("Pokemon Tower 6F - Restless Soul", self.player).item.name
|
|
== f"Missable {mon}"):
|
|
# If you're fighting the Pokémon defined as the Restless Soul, and you're on the 6th floor of the tower,
|
|
# the battle is treates as the Restless Soul battle and you cannot catch it. So, prevent any wild mons
|
|
# from being the same species as the Restless Soul.
|
|
# to account for the possibility that only one ground type Pokemon exists, match only stats for this fix
|
|
mon = randomize_pokemon(self, original_mon, mons_list, 2, self.multiworld.random)
|
|
placed_mons[mon] += 1
|
|
location.item = self.create_item(mon)
|
|
location.event = True
|
|
location.locked = True
|
|
location.item.location = location
|
|
locations.append(location)
|
|
zone_mapping[zone][original_mon] = mon
|
|
|
|
mons_to_add = []
|
|
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
|
|
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
|
if self.multiworld.catch_em_all[self.player] == "first_stage":
|
|
mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
|
|
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
|
elif self.multiworld.catch_em_all[self.player] == "all_pokemon":
|
|
mons_to_add = remaining_pokemon.copy()
|
|
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value,
|
|
self.multiworld.oaks_aide_rt_11[self.player].value,
|
|
self.multiworld.oaks_aide_rt_15[self.player].value)
|
|
if self.multiworld.accessibility[self.player] == "minimal":
|
|
logic_needed_mons = 0
|
|
|
|
self.multiworld.random.shuffle(remaining_pokemon)
|
|
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
|
|
+ len(mons_to_add) < logic_needed_mons):
|
|
mons_to_add.append(remaining_pokemon.pop())
|
|
for mon in mons_to_add:
|
|
stat_base = get_base_stat_total(mon)
|
|
candidate_locations = encounter_slots_master.copy()
|
|
if self.multiworld.randomize_wild_pokemon[self.player].current_key in ["match_base_stats", "match_types_and_base_stats"]:
|
|
candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.original_item) - stat_base))
|
|
if self.multiworld.randomize_wild_pokemon[self.player].current_key in ["match_types", "match_types_and_base_stats"]:
|
|
candidate_locations.sort(key=lambda slot: not any([poke_data.pokemon_data[slot.original_item]["type1"] in
|
|
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
|
|
poke_data.pokemon_data[slot.original_item]["type2"] in
|
|
[self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]]]))
|
|
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
|
|
for location in candidate_locations:
|
|
zone = " - ".join(location.name.split(" - ")[:-1])
|
|
if self.multiworld.catch_em_all[self.player] == "all_pokemon" and self.multiworld.area_1_to_1_mapping[self.player]:
|
|
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
|
|
if (not l.name.startswith(zone)) and
|
|
self.multiworld.get_location(l.name, self.player).item.name == location.item.name]:
|
|
continue
|
|
if self.multiworld.catch_em_all[self.player] == "first_stage" and self.multiworld.area_1_to_1_mapping[self.player]:
|
|
if not [self.multiworld.get_location(l.name, self.player) for l in encounter_slots_master
|
|
if (not l.name.startswith(zone)) and
|
|
self.multiworld.get_location(l.name, self.player).item.name == location.item.name and l.name
|
|
not in poke_data.evolves_from]:
|
|
continue
|
|
|
|
if placed_mons[location.item.name] < 2 and (location.item.name in poke_data.first_stage_pokemon
|
|
or self.multiworld.catch_em_all[self.player]):
|
|
continue
|
|
|
|
if self.multiworld.area_1_to_1_mapping[self.player]:
|
|
place_locations = [place_location for place_location in candidate_locations if
|
|
place_location.name.startswith(zone) and
|
|
place_location.item.name == location.item.name]
|
|
else:
|
|
place_locations = [location]
|
|
for place_location in place_locations:
|
|
placed_mons[place_location.item.name] -= 1
|
|
place_location.item = self.create_item(mon)
|
|
place_location.item.location = place_location
|
|
placed_mons[mon] += 1
|
|
break
|
|
else:
|
|
raise Exception
|
|
|
|
else:
|
|
for slot in encounter_slots:
|
|
location = self.multiworld.get_location(slot.name, self.player)
|
|
location.item = self.create_item(slot.original_item)
|
|
location.event = True
|
|
location.locked = True
|
|
location.item.location = location
|
|
placed_mons[location.item.name] += 1 |