mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Pokémon R/B: Version 3 (#1520)
* Coin items received or found in the Game Corner are now shuffled, locations require Coin Case * Prizesanity option (shuffle Game Corner Prizes) * DexSanity option: location checks for marking Pokémon as caught in your Pokédex. Also an option to set all Pokémon in your Pokédex as seen from the start, to aid in locating them. * Option to randomize the layout of the Rock Tunnel. * Area 1-to-1 mapping: When one instance of a Wild Pokémon in a given area is randomized, all instances of that Pokémon will be the same. So that if a route had 3 different Pokémon before, it will have 3 after randomization. * Option to randomize the moves taught by TMs. * Exact controls for TM/HM compatibility chances. * Option to randomize Pokémon's pallets or set them based on primary type. * Added Cinnabar Gym trainers to Trainersanity and randomized the quiz questions and answers. Getting a correct answer will flag the trainer as defeated so that you can obtain the Trainersanity check without defeating the trainer if you answer correctly.
This commit is contained in:
@@ -15,7 +15,7 @@ from .options import pokemon_rb_options
|
||||
from .rom_addresses import rom_addresses
|
||||
from .text import encode_text
|
||||
from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, process_pokemon_data, process_wild_pokemon,\
|
||||
process_static_pokemon
|
||||
process_static_pokemon, process_move_data
|
||||
from .rules import set_rules
|
||||
|
||||
import worlds.pokemon_rb.poke_data as poke_data
|
||||
@@ -40,13 +40,14 @@ class PokemonRedBlueWorld(World):
|
||||
game = "Pokemon Red and Blue"
|
||||
option_definitions = pokemon_rb_options
|
||||
|
||||
data_version = 5
|
||||
required_client_version = (0, 3, 7)
|
||||
data_version = 7
|
||||
required_client_version = (0, 3, 9)
|
||||
|
||||
topology_present = False
|
||||
|
||||
item_name_to_id = {name: data.id for name, data in item_table.items()}
|
||||
location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"}
|
||||
location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"
|
||||
and location.address is not None}
|
||||
item_name_groups = item_groups
|
||||
|
||||
web = PokemonWebWorld()
|
||||
@@ -58,11 +59,14 @@ class PokemonRedBlueWorld(World):
|
||||
self.extra_badges = {}
|
||||
self.type_chart = None
|
||||
self.local_poke_data = None
|
||||
self.local_move_data = None
|
||||
self.local_tms = None
|
||||
self.learnsets = None
|
||||
self.trainer_name = None
|
||||
self.rival_name = None
|
||||
self.type_chart = None
|
||||
self.traps = None
|
||||
self.trade_mons = {}
|
||||
|
||||
@classmethod
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
||||
@@ -94,6 +98,12 @@ class PokemonRedBlueWorld(World):
|
||||
if len(self.multiworld.player_name[self.player].encode()) > 16:
|
||||
raise Exception(f"Player name too long for {self.multiworld.get_player_name(self.player)}. Player name cannot exceed 16 bytes for Pokémon Red and Blue.")
|
||||
|
||||
if (self.multiworld.dexsanity[self.player] and self.multiworld.accessibility[self.player] == "locations"
|
||||
and (self.multiworld.catch_em_all[self.player] != "all_pokemon"
|
||||
or self.multiworld.randomize_wild_pokemon[self.player] == "vanilla"
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player] != "any")):
|
||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("items")
|
||||
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
@@ -107,6 +117,7 @@ class PokemonRedBlueWorld(World):
|
||||
for badge in badges_to_add:
|
||||
self.extra_badges[hm_moves.pop()] = badge
|
||||
|
||||
process_move_data(self)
|
||||
process_pokemon_data(self)
|
||||
|
||||
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
|
||||
@@ -178,8 +189,13 @@ class PokemonRedBlueWorld(World):
|
||||
if self.multiworld.randomize_pokedex[self.player] == "start_with":
|
||||
start_inventory["Pokedex"] = 1
|
||||
self.multiworld.push_precollected(self.create_item("Pokedex"))
|
||||
|
||||
locations = [location for location in location_data if location.type == "Item"]
|
||||
item_pool = []
|
||||
combined_traps = (self.multiworld.poison_trap_weight[self.player].value
|
||||
+ self.multiworld.fire_trap_weight[self.player].value
|
||||
+ self.multiworld.paralyze_trap_weight[self.player].value
|
||||
+ self.multiworld.ice_trap_weight[self.player].value)
|
||||
for location in locations:
|
||||
if not location.inclusion(self.multiworld, self.player):
|
||||
continue
|
||||
@@ -189,9 +205,18 @@ class PokemonRedBlueWorld(World):
|
||||
item = self.create_filler()
|
||||
elif location.original_item is None:
|
||||
item = self.create_filler()
|
||||
elif location.original_item == "Pokedex":
|
||||
if self.multiworld.randomize_pokedex[self.player] == "vanilla":
|
||||
self.multiworld.get_location(location.name, self.player).event = True
|
||||
location.event = True
|
||||
item = self.create_item("Pokedex")
|
||||
elif location.original_item.startswith("TM"):
|
||||
if self.multiworld.randomize_tm_moves[self.player]:
|
||||
item = self.create_item(location.original_item.split(" ")[0])
|
||||
else:
|
||||
item = self.create_item(location.original_item)
|
||||
else:
|
||||
item = self.create_item(location.original_item)
|
||||
combined_traps = self.multiworld.poison_trap_weight[self.player].value + self.multiworld.fire_trap_weight[self.player].value + self.multiworld.paralyze_trap_weight[self.player].value + self.multiworld.ice_trap_weight[self.player].value
|
||||
if (item.classification == ItemClassification.filler and self.multiworld.random.randint(1, 100)
|
||||
<= self.multiworld.trap_percentage[self.player].value and combined_traps != 0):
|
||||
item = self.create_item(self.select_trap())
|
||||
@@ -205,9 +230,62 @@ class PokemonRedBlueWorld(World):
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def pre_fill(self) -> None:
|
||||
|
||||
process_wild_pokemon(self)
|
||||
process_static_pokemon(self)
|
||||
pokemon_locs = [location.name for location in location_data if location.type != "Item"]
|
||||
for location in self.multiworld.get_locations(self.player):
|
||||
if location.name in pokemon_locs:
|
||||
location.show_in_spoiler = False
|
||||
|
||||
def intervene(move):
|
||||
accessible_slots = [loc for loc in self.multiworld.get_reachable_locations(test_state, self.player) if loc.type == "Wild Encounter"]
|
||||
move_bit = pow(2, poke_data.hm_moves.index(move) + 2)
|
||||
viable_mons = [mon for mon in self.local_poke_data if self.local_poke_data[mon]["tms"][6] & move_bit]
|
||||
placed_mons = [slot.item.name for slot in accessible_slots]
|
||||
# this sort method doesn't seem to work if you reference the same list being sorted in the lambda
|
||||
placed_mons_copy = placed_mons.copy()
|
||||
placed_mons.sort(key=lambda i: placed_mons_copy.count(i))
|
||||
placed_mon = placed_mons.pop()
|
||||
if self.multiworld.area_1_to_1_mapping[self.player]:
|
||||
zone = " - ".join(placed_mon.split(" - ")[:-1])
|
||||
replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name ==
|
||||
placed_mon]
|
||||
else:
|
||||
replace_slots = [self.multiworld.random.choice([slot for slot in accessible_slots if slot.item.name ==
|
||||
placed_mon])]
|
||||
replace_mon = self.multiworld.random.choice(viable_mons)
|
||||
for replace_slot in replace_slots:
|
||||
replace_slot.item = self.create_item(replace_mon)
|
||||
last_intervene = None
|
||||
while True:
|
||||
intervene_move = None
|
||||
test_state = self.multiworld.get_all_state(False)
|
||||
if not self.multiworld.badgesanity[self.player]:
|
||||
for badge in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge",
|
||||
"Marsh Badge", "Volcano Badge", "Earth Badge"]:
|
||||
test_state.collect(self.create_item(badge))
|
||||
if not test_state.pokemon_rb_can_surf(self.player):
|
||||
intervene_move = "Surf"
|
||||
if not test_state.pokemon_rb_can_strength(self.player):
|
||||
intervene_move = "Strength"
|
||||
# cut may not be needed if accessibility is minimal, unless you need all 8 badges and badgesanity is off,
|
||||
# as you will require cut to access celadon gyn
|
||||
if (self.multiworld.accessibility[self.player] != "minimal" or ((not
|
||||
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_condition[self.player],
|
||||
self.multiworld.victory_road_condition[self.player]) > 7)):
|
||||
if not test_state.pokemon_rb_can_cut(self.player):
|
||||
intervene_move = "Cut"
|
||||
if (self.multiworld.accessibility[self.player].current_key != "minimal" and
|
||||
(self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player])):
|
||||
if not test_state.pokemon_rb_can_flash(self.player):
|
||||
intervene_move = "Flash"
|
||||
if intervene_move:
|
||||
if intervene_move == last_intervene:
|
||||
raise Exception(f"Caught in infinite loop attempting to ensure {intervene_move} is available to player {self.player}")
|
||||
intervene(intervene_move)
|
||||
last_intervene = intervene_move
|
||||
else:
|
||||
break
|
||||
|
||||
if self.multiworld.old_man[self.player].value == 1:
|
||||
self.multiworld.local_early_items[self.player]["Oak's Parcel"] = 1
|
||||
@@ -237,17 +315,26 @@ class PokemonRedBlueWorld(World):
|
||||
else:
|
||||
raise FillError(f"Failed to place badges for player {self.player}")
|
||||
|
||||
locs = [self.multiworld.get_location("Fossil - Choice A", self.player),
|
||||
self.multiworld.get_location("Fossil - Choice B", self.player)]
|
||||
for loc in locs:
|
||||
add_item_rule(loc, lambda i: i.advancement or i.name in self.item_name_groups["Unique"]
|
||||
or i.name == "Master Ball")
|
||||
# Place local items in some locations to prevent save-scumming. Also Oak's PC to prevent an "AP Item" from
|
||||
# entering the player's inventory.
|
||||
|
||||
locs = {self.multiworld.get_location("Fossil - Choice A", self.player),
|
||||
self.multiworld.get_location("Fossil - Choice B", self.player)}
|
||||
|
||||
if self.multiworld.dexsanity[self.player]:
|
||||
for mon in ([" ".join(self.multiworld.get_location(
|
||||
f"Pallet Town - Starter {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 4)]
|
||||
+ [" ".join(self.multiworld.get_location(
|
||||
f"Fighting Dojo - Gift {i}", self.player).item.name.split(" ")[1:]) for i in range(1, 3)]):
|
||||
loc = self.multiworld.get_location(f"Pokedex - {mon}", self.player)
|
||||
if loc.item is None:
|
||||
locs.add(loc)
|
||||
|
||||
loc = self.multiworld.get_location("Pallet Town - Player's PC", self.player)
|
||||
if loc.item is None:
|
||||
locs.append(loc)
|
||||
locs.add(loc)
|
||||
|
||||
for loc in locs:
|
||||
for loc in sorted(locs):
|
||||
unplaced_items = []
|
||||
if loc.name in self.multiworld.priority_locations[self.player].value:
|
||||
add_item_rule(loc, lambda i: i.advancement)
|
||||
@@ -262,21 +349,6 @@ class PokemonRedBlueWorld(World):
|
||||
unplaced_items.append(item)
|
||||
self.multiworld.itempool += unplaced_items
|
||||
|
||||
intervene = False
|
||||
test_state = self.multiworld.get_all_state(False)
|
||||
if not test_state.pokemon_rb_can_surf(self.player) or not test_state.pokemon_rb_can_strength(self.player):
|
||||
intervene = True
|
||||
elif self.multiworld.accessibility[self.player].current_key != "minimal":
|
||||
if not test_state.pokemon_rb_can_cut(self.player) or not test_state.pokemon_rb_can_flash(self.player):
|
||||
intervene = True
|
||||
if intervene:
|
||||
# the way this is handled will be improved significantly in the future when I add options to
|
||||
# let you choose the exact weights for HM compatibility
|
||||
logging.warning(
|
||||
f"HM-compatible Pokémon possibly missing, placing Mew on Route 1 for player {self.player}")
|
||||
loc = self.multiworld.get_location("Route 1 - Wild Pokemon - 1", self.player)
|
||||
loc.item = self.create_item("Mew")
|
||||
|
||||
def create_regions(self):
|
||||
if self.multiworld.free_fly_location[self.player].value:
|
||||
if self.multiworld.old_man[self.player].value == 0:
|
||||
@@ -317,6 +389,12 @@ class PokemonRedBlueWorld(World):
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n")
|
||||
for matchup in self.type_chart:
|
||||
spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
|
||||
spoiler_handle.write(f"\n\nPokémon locations ({self.multiworld.player_name[self.player]}):\n\n")
|
||||
pokemon_locs = [location.name for location in location_data if location.type != "Item"]
|
||||
for location in self.multiworld.get_locations(self.player):
|
||||
if location.name in pokemon_locs:
|
||||
spoiler_handle.write(location.name + ": " + location.item.name + "\n")
|
||||
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
combined_traps = self.multiworld.poison_trap_weight[self.player].value + self.multiworld.fire_trap_weight[self.player].value + self.multiworld.paralyze_trap_weight[self.player].value + self.multiworld.ice_trap_weight[self.player].value
|
||||
@@ -336,6 +414,21 @@ class PokemonRedBlueWorld(World):
|
||||
self.traps += ["Ice Trap"] * self.multiworld.ice_trap_weight[self.player].value
|
||||
return self.multiworld.random.choice(self.traps)
|
||||
|
||||
def extend_hint_information(self, hint_data):
|
||||
if self.multiworld.dexsanity[self.player]:
|
||||
hint_data[self.player] = {}
|
||||
mon_locations = {mon: set() for mon in poke_data.pokemon_data.keys()}
|
||||
for loc in location_data: #self.multiworld.get_locations(self.player):
|
||||
if loc.type in ["Wild Encounter", "Static Pokemon", "Legendary Pokemon", "Missable Pokemon"]:
|
||||
mon = self.multiworld.get_location(loc.name, self.player).item.name
|
||||
if mon.startswith("Static ") or mon.startswith("Missable "):
|
||||
mon = " ".join(mon.split(" ")[1:])
|
||||
mon_locations[mon].add(loc.name.split(" -")[0])
|
||||
for mon in mon_locations:
|
||||
if mon_locations[mon]:
|
||||
hint_data[self.player][self.multiworld.get_location(f"Pokedex - {mon}", self.player).address] = \
|
||||
", ".join(mon_locations[mon])
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
return {
|
||||
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
|
||||
@@ -358,7 +451,8 @@ class PokemonRedBlueWorld(World):
|
||||
"type_chart": self.type_chart,
|
||||
"randomize_pokedex": self.multiworld.randomize_pokedex[self.player].value,
|
||||
"trainersanity": self.multiworld.trainersanity[self.player].value,
|
||||
"death_link": self.multiworld.death_link[self.player].value
|
||||
"death_link": self.multiworld.death_link[self.player].value,
|
||||
"prizesanity": self.multiworld.prizesanity[self.player].value
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user