Pokémon Red and Blue: Version 4 update (#1963)
## 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.
2023-07-23 22:46:54 +00:00
|
|
|
from copy import deepcopy
|
2024-04-13 15:58:50 +00:00
|
|
|
from . import poke_data, logic
|
Pokémon Red and Blue: Version 4 update (#1963)
## 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.
2023-07-23 22:46:54 +00:00
|
|
|
from .rom_addresses import rom_addresses
|
|
|
|
|
|
|
|
|
|
|
|
def set_mon_palettes(self, random, data):
|
|
|
|
if self.multiworld.randomize_pokemon_palettes[self.player] == "vanilla":
|
|
|
|
return
|
|
|
|
pallet_map = {
|
|
|
|
"Poison": 0x0F,
|
|
|
|
"Normal": 0x10,
|
|
|
|
"Ice": 0x11,
|
|
|
|
"Fire": 0x12,
|
|
|
|
"Water": 0x13,
|
|
|
|
"Ghost": 0x14,
|
|
|
|
"Ground": 0x15,
|
|
|
|
"Grass": 0x16,
|
|
|
|
"Psychic": 0x17,
|
|
|
|
"Electric": 0x18,
|
|
|
|
"Rock": 0x19,
|
|
|
|
"Dragon": 0x1F,
|
|
|
|
"Flying": 0x20,
|
|
|
|
"Fighting": 0x21,
|
|
|
|
"Bug": 0x22
|
|
|
|
}
|
|
|
|
palettes = []
|
|
|
|
for mon in poke_data.pokemon_data:
|
|
|
|
if self.multiworld.randomize_pokemon_palettes[self.player] == "primary_type":
|
|
|
|
pallet = pallet_map[self.local_poke_data[mon]["type1"]]
|
|
|
|
elif (self.multiworld.randomize_pokemon_palettes[self.player] == "follow_evolutions" and mon in
|
|
|
|
poke_data.evolves_from and poke_data.evolves_from[mon] != "Eevee"):
|
|
|
|
pallet = palettes[-1]
|
|
|
|
else: # completely_random or follow_evolutions and it is not an evolved form (except eeveelutions)
|
|
|
|
pallet = random.choice(list(pallet_map.values()))
|
|
|
|
palettes.append(pallet)
|
|
|
|
address = rom_addresses["Mon_Palettes"]
|
|
|
|
for pallet in palettes:
|
|
|
|
data[address] = pallet
|
|
|
|
address += 1
|
|
|
|
|
|
|
|
|
|
|
|
def choose_forced_type(chances, random):
|
|
|
|
n = random.randint(1, 100)
|
|
|
|
for chance in chances:
|
|
|
|
if chance[0] >= n:
|
|
|
|
return chance[1]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def filter_moves(local_move_data, moves, type, random):
|
|
|
|
ret = []
|
|
|
|
for move in moves:
|
|
|
|
if local_move_data[move]["type"] == type or type is None:
|
|
|
|
ret.append(move)
|
|
|
|
random.shuffle(ret)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def get_move(local_move_data, moves, chances, random, starting_move=False):
|
|
|
|
type = choose_forced_type(chances, random)
|
|
|
|
filtered_moves = filter_moves(local_move_data, moves, type, random)
|
|
|
|
for move in filtered_moves:
|
|
|
|
if (not starting_move) or (local_move_data[move]["accuracy"] > 80 and local_move_data[move]["power"] > 0):
|
|
|
|
moves.remove(move)
|
|
|
|
return move
|
|
|
|
else:
|
|
|
|
return get_move(local_move_data, moves, [], random, starting_move)
|
|
|
|
|
|
|
|
|
|
|
|
def move_power(move_data):
|
|
|
|
power = move_data["power"]
|
|
|
|
if move_data["effect"] in (29, 42):
|
|
|
|
# 29: two-to-five attacks. 42: trapping effect, two-to-five turns.
|
|
|
|
power *= 3
|
|
|
|
elif move_data["effect"] in (77, 44):
|
|
|
|
# 77: Twineedle. Two attacks and poison chance. 44: Just two attacks
|
|
|
|
power *= 2
|
|
|
|
elif move_data["effect"] == 48:
|
|
|
|
# 25% recoil damage taken. Reduce power considered by that amount
|
|
|
|
power *= 0.75
|
|
|
|
elif move_data["effect"] == 3:
|
|
|
|
# 50% absorb. Increase power considered by that amount
|
|
|
|
power *= 1.5
|
|
|
|
elif move_data["effect"] == 39 and move_data["id"] != 91:
|
|
|
|
# Takes two turns while vulnerable. Dig uses this effect ID but is semi-invulnerable
|
|
|
|
power *= 0.66
|
|
|
|
elif move_data["effect"] == 7:
|
|
|
|
# Faint user
|
|
|
|
power *= 0.5
|
|
|
|
elif move_data["id"] in (2, 75, 152, 163,):
|
|
|
|
# High critical strike moves: Karate Chop, Razor Leaf, Crabhammer, Slash
|
|
|
|
power *= 2
|
|
|
|
return power
|
|
|
|
|
|
|
|
|
|
|
|
def process_move_data(self):
|
|
|
|
self.local_move_data = deepcopy(poke_data.moves)
|
|
|
|
|
|
|
|
if self.multiworld.randomize_move_types[self.player]:
|
|
|
|
for move, data in self.local_move_data.items():
|
|
|
|
if move == "No Move":
|
|
|
|
continue
|
|
|
|
# The chance of randomized moves choosing a normal type move is high, so we want to retain having a higher
|
|
|
|
# rate of normal type moves
|
|
|
|
data["type"] = self.multiworld.random.choice(list(poke_data.type_ids) + (["Normal"] * 4))
|
|
|
|
|
|
|
|
if self.multiworld.move_balancing[self.player]:
|
|
|
|
self.local_move_data["Sing"]["accuracy"] = 30
|
|
|
|
self.local_move_data["Sleep Powder"]["accuracy"] = 40
|
|
|
|
self.local_move_data["Spore"]["accuracy"] = 50
|
|
|
|
self.local_move_data["Sonicboom"]["effect"] = 0
|
|
|
|
self.local_move_data["Sonicboom"]["power"] = 50
|
|
|
|
self.local_move_data["Dragon Rage"]["effect"] = 0
|
|
|
|
self.local_move_data["Dragon Rage"]["power"] = 80
|
|
|
|
self.local_move_data["Horn Drill"]["effect"] = 0
|
|
|
|
self.local_move_data["Horn Drill"]["power"] = 70
|
|
|
|
self.local_move_data["Horn Drill"]["accuracy"] = 90
|
|
|
|
self.local_move_data["Guillotine"]["effect"] = 0
|
|
|
|
self.local_move_data["Guillotine"]["power"] = 70
|
|
|
|
self.local_move_data["Guillotine"]["accuracy"] = 90
|
|
|
|
self.local_move_data["Fissure"]["effect"] = 0
|
|
|
|
self.local_move_data["Fissure"]["power"] = 70
|
|
|
|
self.local_move_data["Fissure"]["accuracy"] = 90
|
|
|
|
self.local_move_data["Blizzard"]["accuracy"] = 70
|
|
|
|
if self.multiworld.randomize_tm_moves[self.player]:
|
|
|
|
self.local_tms = self.multiworld.random.sample([move for move in poke_data.moves.keys() if move not in
|
|
|
|
["No Move"] + poke_data.hm_moves], 50)
|
|
|
|
else:
|
|
|
|
self.local_tms = poke_data.tm_moves.copy()
|
|
|
|
|
|
|
|
|
|
|
|
def process_pokemon_data(self):
|
|
|
|
|
|
|
|
local_poke_data = deepcopy(poke_data.pokemon_data)
|
|
|
|
learnsets = deepcopy(poke_data.learnsets)
|
|
|
|
tms_hms = self.local_tms + poke_data.hm_moves
|
|
|
|
|
|
|
|
compat_hms = set()
|
|
|
|
|
|
|
|
for mon, mon_data in local_poke_data.items():
|
|
|
|
if self.multiworld.randomize_pokemon_stats[self.player] == "shuffle":
|
|
|
|
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
|
|
|
|
if mon in poke_data.evolves_from:
|
|
|
|
stat_shuffle_map = local_poke_data[poke_data.evolves_from[mon]]["stat_shuffle_map"]
|
|
|
|
else:
|
|
|
|
stat_shuffle_map = self.multiworld.random.sample(range(0, 5), 5)
|
|
|
|
|
|
|
|
mon_data["stat_shuffle_map"] = stat_shuffle_map
|
|
|
|
mon_data["hp"] = stats[stat_shuffle_map[0]]
|
|
|
|
mon_data["atk"] = stats[stat_shuffle_map[1]]
|
|
|
|
mon_data["def"] = stats[stat_shuffle_map[2]]
|
|
|
|
mon_data["spd"] = stats[stat_shuffle_map[3]]
|
|
|
|
mon_data["spc"] = stats[stat_shuffle_map[4]]
|
|
|
|
elif self.multiworld.randomize_pokemon_stats[self.player] == "randomize":
|
|
|
|
first_run = True
|
|
|
|
while (mon_data["hp"] > 255 or mon_data["atk"] > 255 or mon_data["def"] > 255 or mon_data["spd"] > 255
|
|
|
|
or mon_data["spc"] > 255 or first_run):
|
|
|
|
first_run = False
|
|
|
|
total_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"]
|
|
|
|
for stat in ("hp", "atk", "def", "spd", "spc"):
|
|
|
|
if mon in poke_data.evolves_from:
|
|
|
|
mon_data[stat] = local_poke_data[poke_data.evolves_from[mon]][stat]
|
|
|
|
total_stats -= mon_data[stat]
|
|
|
|
elif stat == "hp":
|
|
|
|
mon_data[stat] = 20
|
|
|
|
total_stats -= 20
|
|
|
|
else:
|
|
|
|
mon_data[stat] = 10
|
|
|
|
total_stats -= 10
|
|
|
|
assert total_stats >= 0, f"Error distributing stats for {mon} for player {self.player}"
|
|
|
|
dist = [self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
|
|
|
|
self.multiworld.random.randint(1, 101) / 100, self.multiworld.random.randint(1, 101) / 100,
|
|
|
|
self.multiworld.random.randint(1, 101) / 100]
|
|
|
|
total_dist = sum(dist)
|
|
|
|
|
|
|
|
mon_data["hp"] += int(round(dist[0] / total_dist * total_stats))
|
|
|
|
mon_data["atk"] += int(round(dist[1] / total_dist * total_stats))
|
|
|
|
mon_data["def"] += int(round(dist[2] / total_dist * total_stats))
|
|
|
|
mon_data["spd"] += int(round(dist[3] / total_dist * total_stats))
|
|
|
|
mon_data["spc"] += int(round(dist[4] / total_dist * total_stats))
|
|
|
|
if self.multiworld.randomize_pokemon_types[self.player]:
|
|
|
|
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
|
|
|
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
|
|
|
|
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
|
|
|
|
if type1 == type2:
|
|
|
|
if self.multiworld.secondary_type_chance[self.player].value == -1:
|
|
|
|
if mon_data["type1"] != mon_data["type2"]:
|
|
|
|
while type2 == type1:
|
|
|
|
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
|
|
|
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value:
|
|
|
|
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
|
|
|
else:
|
|
|
|
type1 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
|
|
|
type2 = type1
|
|
|
|
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
|
|
|
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100)
|
|
|
|
<= self.multiworld.secondary_type_chance[self.player].value):
|
|
|
|
while type2 == type1:
|
|
|
|
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
|
|
|
|
|
|
|
mon_data["type1"] = type1
|
|
|
|
mon_data["type2"] = type2
|
|
|
|
if self.multiworld.randomize_pokemon_movesets[self.player]:
|
|
|
|
if self.multiworld.randomize_pokemon_movesets[self.player] == "prefer_types":
|
|
|
|
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
|
|
|
|
chances = [[75, "Normal"]]
|
|
|
|
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
|
|
|
|
if mon_data["type1"] == "Normal":
|
|
|
|
second_type = mon_data["type2"]
|
|
|
|
else:
|
|
|
|
second_type = mon_data["type1"]
|
|
|
|
chances = [[30, "Normal"], [85, second_type]]
|
|
|
|
elif mon_data["type1"] == mon_data["type2"]:
|
|
|
|
chances = [[60, mon_data["type1"]], [80, "Normal"]]
|
|
|
|
else:
|
|
|
|
chances = [[50, mon_data["type1"]], [80, mon_data["type2"]], [85, "Normal"]]
|
|
|
|
else:
|
|
|
|
chances = []
|
|
|
|
moves = list(poke_data.moves.keys())
|
|
|
|
for move in ["No Move"] + poke_data.hm_moves:
|
|
|
|
moves.remove(move)
|
|
|
|
if self.multiworld.confine_transform_to_ditto[self.player]:
|
|
|
|
moves.remove("Transform")
|
|
|
|
if self.multiworld.start_with_four_moves[self.player]:
|
|
|
|
num_moves = 4
|
|
|
|
else:
|
|
|
|
num_moves = len([i for i in [mon_data["start move 1"], mon_data["start move 2"],
|
|
|
|
mon_data["start move 3"], mon_data["start move 4"]] if i != "No Move"])
|
|
|
|
if mon in learnsets:
|
|
|
|
num_moves += len(learnsets[mon])
|
|
|
|
non_power_moves = []
|
|
|
|
learnsets[mon] = []
|
|
|
|
for i in range(num_moves):
|
|
|
|
if i == 0 and mon == "Ditto" and self.multiworld.confine_transform_to_ditto[self.player]:
|
|
|
|
move = "Transform"
|
|
|
|
else:
|
|
|
|
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
|
|
|
|
while move == "Transform" and self.multiworld.confine_transform_to_ditto[self.player]:
|
|
|
|
move = get_move(self.local_move_data, moves, chances, self.multiworld.random)
|
|
|
|
if self.local_move_data[move]["power"] < 5:
|
|
|
|
non_power_moves.append(move)
|
|
|
|
else:
|
|
|
|
learnsets[mon].append(move)
|
|
|
|
learnsets[mon].sort(key=lambda move: move_power(self.local_move_data[move]))
|
|
|
|
if learnsets[mon]:
|
|
|
|
for move in non_power_moves:
|
|
|
|
learnsets[mon].insert(self.multiworld.random.randint(1, len(learnsets[mon])), move)
|
|
|
|
else:
|
|
|
|
learnsets[mon] = non_power_moves
|
|
|
|
for i in range(1, 5):
|
|
|
|
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[self.player]:
|
|
|
|
mon_data[f"start move {i}"] = learnsets[mon].pop(0)
|
|
|
|
|
|
|
|
if self.multiworld.randomize_pokemon_catch_rates[self.player]:
|
|
|
|
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player],
|
|
|
|
255)
|
|
|
|
else:
|
|
|
|
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"])
|
|
|
|
|
|
|
|
def roll_tm_compat(roll_move):
|
|
|
|
if self.local_move_data[roll_move]["type"] in [mon_data["type1"], mon_data["type2"]]:
|
|
|
|
if roll_move in poke_data.hm_moves:
|
|
|
|
if self.multiworld.hm_same_type_compatibility[self.player].value == -1:
|
|
|
|
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
|
|
|
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_same_type_compatibility[self.player].value
|
|
|
|
if r and mon not in poke_data.legendary_pokemon:
|
|
|
|
compat_hms.add(roll_move)
|
|
|
|
return r
|
|
|
|
else:
|
|
|
|
if self.multiworld.tm_same_type_compatibility[self.player].value == -1:
|
|
|
|
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
|
|
|
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_same_type_compatibility[self.player].value
|
|
|
|
elif self.local_move_data[roll_move]["type"] == "Normal" and "Normal" not in [mon_data["type1"], mon_data["type2"]]:
|
|
|
|
if roll_move in poke_data.hm_moves:
|
|
|
|
if self.multiworld.hm_normal_type_compatibility[self.player].value == -1:
|
|
|
|
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
|
|
|
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_normal_type_compatibility[self.player].value
|
|
|
|
if r and mon not in poke_data.legendary_pokemon:
|
|
|
|
compat_hms.add(roll_move)
|
|
|
|
return r
|
|
|
|
else:
|
|
|
|
if self.multiworld.tm_normal_type_compatibility[self.player].value == -1:
|
|
|
|
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
|
|
|
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_normal_type_compatibility[self.player].value
|
|
|
|
else:
|
|
|
|
if roll_move in poke_data.hm_moves:
|
|
|
|
if self.multiworld.hm_other_type_compatibility[self.player].value == -1:
|
|
|
|
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
|
|
|
r = self.multiworld.random.randint(1, 100) <= self.multiworld.hm_other_type_compatibility[self.player].value
|
|
|
|
if r and mon not in poke_data.legendary_pokemon:
|
|
|
|
compat_hms.add(roll_move)
|
|
|
|
return r
|
|
|
|
else:
|
|
|
|
if self.multiworld.tm_other_type_compatibility[self.player].value == -1:
|
|
|
|
return mon_data["tms"][int(flag / 8)] & 1 << (flag % 8)
|
|
|
|
return self.multiworld.random.randint(1, 100) <= self.multiworld.tm_other_type_compatibility[self.player].value
|
|
|
|
|
|
|
|
for flag, tm_move in enumerate(tms_hms):
|
|
|
|
if mon in poke_data.evolves_from and self.multiworld.inherit_tm_hm_compatibility[self.player]:
|
|
|
|
|
|
|
|
if local_poke_data[poke_data.evolves_from[mon]]["tms"][int(flag / 8)] & 1 << (flag % 8):
|
|
|
|
# always inherit learnable tms/hms
|
|
|
|
bit = 1
|
|
|
|
else:
|
|
|
|
if self.local_move_data[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]] and \
|
|
|
|
self.local_move_data[tm_move]["type"] not in [
|
|
|
|
local_poke_data[poke_data.evolves_from[mon]]["type1"],
|
|
|
|
local_poke_data[poke_data.evolves_from[mon]]["type2"]]:
|
|
|
|
# the tm/hm is for a move whose type matches current mon, but not pre-evolved form
|
|
|
|
# so this gets full chance roll
|
|
|
|
bit = roll_tm_compat(tm_move)
|
|
|
|
# otherwise 50% reduced chance to add compatibility over pre-evolved form
|
|
|
|
elif self.multiworld.random.randint(1, 100) > 50 and roll_tm_compat(tm_move):
|
|
|
|
bit = 1
|
|
|
|
else:
|
|
|
|
bit = 0
|
|
|
|
else:
|
|
|
|
bit = roll_tm_compat(tm_move)
|
|
|
|
if bit:
|
|
|
|
mon_data["tms"][int(flag / 8)] |= 1 << (flag % 8)
|
|
|
|
else:
|
|
|
|
mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
|
|
|
|
|
|
|
|
hm_verify = ["Surf", "Strength"]
|
2024-04-13 15:58:50 +00:00
|
|
|
if self.multiworld.accessibility[self.player] != "minimal" or ((not
|
Pokémon Red and Blue: Version 4 update (#1963)
## 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.
2023-07-23 22:46:54 +00:00
|
|
|
self.multiworld.badgesanity[self.player]) and max(self.multiworld.elite_four_badges_condition[self.player],
|
|
|
|
self.multiworld.route_22_gate_condition[self.player], self.multiworld.victory_road_condition[self.player])
|
|
|
|
> 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")):
|
|
|
|
hm_verify += ["Cut"]
|
2024-04-13 15:58:50 +00:00
|
|
|
if self.multiworld.accessibility[self.player] != "minimal" or (not
|
Pokémon Red and Blue: Version 4 update (#1963)
## 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.
2023-07-23 22:46:54 +00:00
|
|
|
self.multiworld.dark_rock_tunnel_logic[self.player]) and ((self.multiworld.trainersanity[self.player] or
|
|
|
|
self.multiworld.extra_key_items[self.player])
|
|
|
|
or self.multiworld.door_shuffle[self.player]):
|
|
|
|
hm_verify += ["Flash"]
|
2024-04-13 15:58:50 +00:00
|
|
|
# Fly does not need to be verified. Full/Insanity/Decoupled door shuffle connects reachable regions to unreachable
|
|
|
|
# regions, so if Fly is available and can be learned, the towns you can fly to would be considered reachable for
|
|
|
|
# door shuffle purposes, but if no Pokémon can learn it, that connection would just be out of logic and it would
|
|
|
|
# ensure connections to those towns.
|
Pokémon Red and Blue: Version 4 update (#1963)
## 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.
2023-07-23 22:46:54 +00:00
|
|
|
|
|
|
|
for hm_move in hm_verify:
|
|
|
|
if hm_move not in compat_hms:
|
|
|
|
mon = self.multiworld.random.choice([mon for mon in poke_data.pokemon_data if mon not in
|
|
|
|
poke_data.legendary_pokemon])
|
|
|
|
flag = tms_hms.index(hm_move)
|
|
|
|
local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
|
|
|
|
|
|
|
|
self.local_poke_data = local_poke_data
|
|
|
|
self.learnsets = learnsets
|
2024-04-13 15:58:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
def verify_hm_moves(multiworld, world, player):
|
|
|
|
def intervene(move, test_state):
|
|
|
|
move_bit = pow(2, poke_data.hm_moves.index(move) + 2)
|
|
|
|
viable_mons = [mon for mon in world.local_poke_data if world.local_poke_data[mon]["tms"][6] & move_bit]
|
|
|
|
if multiworld.randomize_wild_pokemon[player] and viable_mons:
|
|
|
|
accessible_slots = [loc for loc in multiworld.get_reachable_locations(test_state, player) if
|
|
|
|
loc.type == "Wild Encounter"]
|
|
|
|
|
|
|
|
def number_of_zones(mon):
|
|
|
|
zones = set()
|
|
|
|
for loc in [slot for slot in accessible_slots if slot.item.name == mon]:
|
|
|
|
zones.add(loc.name.split(" - ")[0])
|
|
|
|
return len(zones)
|
|
|
|
|
|
|
|
placed_mons = [slot.item.name for slot in accessible_slots]
|
|
|
|
|
|
|
|
if multiworld.area_1_to_1_mapping[player]:
|
|
|
|
placed_mons.sort(key=lambda i: number_of_zones(i))
|
|
|
|
else:
|
|
|
|
# this sort method doesn't 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()
|
|
|
|
replace_mon = multiworld.random.choice(viable_mons)
|
|
|
|
replace_slot = multiworld.random.choice([slot for slot in accessible_slots if slot.item.name
|
|
|
|
== placed_mon])
|
|
|
|
if multiworld.area_1_to_1_mapping[player]:
|
|
|
|
zone = " - ".join(replace_slot.name.split(" - ")[:-1])
|
|
|
|
replace_slots = [slot for slot in accessible_slots if slot.name.startswith(zone) and slot.item.name
|
|
|
|
== placed_mon]
|
|
|
|
for replace_slot in replace_slots:
|
|
|
|
replace_slot.item = world.create_item(replace_mon)
|
|
|
|
else:
|
|
|
|
replace_slot.item = world.create_item(replace_mon)
|
|
|
|
else:
|
|
|
|
tms_hms = world.local_tms + poke_data.hm_moves
|
|
|
|
flag = tms_hms.index(move)
|
|
|
|
mon_list = [mon for mon in poke_data.pokemon_data.keys() if test_state.has(mon, player)]
|
|
|
|
multiworld.random.shuffle(mon_list)
|
|
|
|
mon_list.sort(key=lambda mon: world.local_move_data[move]["type"] not in
|
|
|
|
[world.local_poke_data[mon]["type1"], world.local_poke_data[mon]["type2"]])
|
|
|
|
for mon in mon_list:
|
|
|
|
if test_state.has(mon, player):
|
|
|
|
world.local_poke_data[mon]["tms"][int(flag / 8)] |= 1 << (flag % 8)
|
|
|
|
break
|
|
|
|
|
|
|
|
last_intervene = None
|
|
|
|
while True:
|
|
|
|
intervene_move = None
|
|
|
|
test_state = multiworld.get_all_state(False)
|
|
|
|
if not logic.can_learn_hm(test_state, "Surf", player):
|
|
|
|
intervene_move = "Surf"
|
|
|
|
elif not logic.can_learn_hm(test_state, "Strength", 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
|
|
|
|
elif ((not logic.can_learn_hm(test_state, "Cut", player)) and
|
|
|
|
(multiworld.accessibility[player] != "minimal" or ((not
|
|
|
|
multiworld.badgesanity[player]) and max(
|
|
|
|
multiworld.elite_four_badges_condition[player],
|
|
|
|
multiworld.route_22_gate_condition[player],
|
|
|
|
multiworld.victory_road_condition[player])
|
|
|
|
> 7) or (multiworld.door_shuffle[player] not in ("off", "simple")))):
|
|
|
|
intervene_move = "Cut"
|
|
|
|
elif ((not logic.can_learn_hm(test_state, "Flash", player))
|
|
|
|
and multiworld.dark_rock_tunnel_logic[player]
|
|
|
|
and (multiworld.accessibility[player] != "minimal"
|
|
|
|
or multiworld.door_shuffle[player])):
|
|
|
|
intervene_move = "Flash"
|
|
|
|
# If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps
|
|
|
|
# as reachable, and if on no door shuffle or simple, fly is simply never necessary.
|
|
|
|
# We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been
|
|
|
|
# considered in door shuffle.
|
|
|
|
elif ((not logic.can_learn_hm(test_state, "Fly", player))
|
|
|
|
and multiworld.door_shuffle[player] not in
|
|
|
|
("off", "simple") and [world.fly_map, world.town_map_fly_map] != ["Pallet Town", "Pallet Town"]):
|
|
|
|
intervene_move = "Fly"
|
|
|
|
if intervene_move:
|
|
|
|
if intervene_move == last_intervene:
|
|
|
|
raise Exception(f"Caught in infinite loop attempting to ensure {intervene_move} is available to player {player}")
|
|
|
|
intervene(intervene_move, test_state)
|
|
|
|
last_intervene = intervene_move
|
|
|
|
else:
|
|
|
|
break
|