Skip to content

Commit 32820ba

Browse files
authored
Pokemon R/B: Bug fixes and add trap weights (ArchipelagoMW#1319)
* [Pokemon R/B] Move type chart rando to generate_early and add trap weights * [Pokemon R/B] Update patching process on client to verify hash
1 parent 6173bc6 commit 32820ba

File tree

4 files changed

+127
-76
lines changed

4 files changed

+127
-76
lines changed

PokemonClient.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
get_base_parser
1616

1717
from worlds.pokemon_rb.locations import location_data
18+
from worlds.pokemon_rb.rom import RedDeltaPatch, BlueDeltaPatch
1819

1920
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}}
2021
location_bytes_bits = {}
@@ -265,8 +266,16 @@ async def run_game(romfile):
265266
async def patch_and_run_game(game_version, patch_file, ctx):
266267
base_name = os.path.splitext(patch_file)[0]
267268
comp_path = base_name + '.gb'
268-
with open(Utils.local_path(Utils.get_options()["pokemon_rb_options"][f"{game_version}_rom_file"]), "rb") as stream:
269-
base_rom = bytes(stream.read())
269+
if game_version == "blue":
270+
delta_patch = BlueDeltaPatch
271+
else:
272+
delta_patch = RedDeltaPatch
273+
274+
try:
275+
base_rom = delta_patch.get_source_data()
276+
except Exception as msg:
277+
logger.info(msg, extra={'compact_gui': True})
278+
ctx.gui_error('Error', msg)
270279

271280
with zipfile.ZipFile(patch_file, 'r') as patch_archive:
272281
with patch_archive.open('delta.bsdiff4', 'r') as stream:

worlds/pokemon_rb/__init__.py

+77-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import TextIO
22
import os
33
import logging
4+
from copy import deepcopy
45

56
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
67
from Fill import fill_restrictive, FillError, sweep_from_pool
@@ -62,6 +63,8 @@ def __init__(self, world: MultiWorld, player: int):
6263
self.learnsets = None
6364
self.trainer_name = None
6465
self.rival_name = None
66+
self.type_chart = None
67+
self.traps = None
6568

6669
@classmethod
6770
def stage_assert_generate(cls, world):
@@ -108,6 +111,69 @@ def encode_name(name, t):
108111

109112
process_pokemon_data(self)
110113

114+
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
115+
chart = deepcopy(poke_data.type_chart)
116+
elif self.multiworld.randomize_type_chart[self.player] == "randomize":
117+
types = poke_data.type_names.values()
118+
matchups = []
119+
for type1 in types:
120+
for type2 in types:
121+
matchups.append([type1, type2])
122+
self.multiworld.random.shuffle(matchups)
123+
immunities = self.multiworld.immunity_matchups[self.player].value
124+
super_effectives = self.multiworld.super_effective_matchups[self.player].value
125+
not_very_effectives = self.multiworld.not_very_effective_matchups[self.player].value
126+
normals = self.multiworld.normal_matchups[self.player].value
127+
while super_effectives + not_very_effectives + normals < 225 - immunities:
128+
super_effectives += self.multiworld.super_effective_matchups[self.player].value
129+
not_very_effectives += self.multiworld.not_very_effective_matchups[self.player].value
130+
normals += self.multiworld.normal_matchups[self.player].value
131+
if super_effectives + not_very_effectives + normals > 225 - immunities:
132+
total = super_effectives + not_very_effectives + normals
133+
excess = total - (225 - immunities)
134+
subtract_amounts = (
135+
int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
136+
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
137+
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
138+
super_effectives -= subtract_amounts[0]
139+
not_very_effectives -= subtract_amounts[1]
140+
normals -= subtract_amounts[2]
141+
while super_effectives + not_very_effectives + normals > 225 - immunities:
142+
r = self.multiworld.random.randint(0, 2)
143+
if r == 0:
144+
super_effectives -= 1
145+
elif r == 1:
146+
not_very_effectives -= 1
147+
else:
148+
normals -= 1
149+
chart = []
150+
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
151+
[0, 10, 20, 5]):
152+
for _ in range(matchup_list):
153+
matchup = matchups.pop()
154+
matchup.append(matchup_value)
155+
chart.append(matchup)
156+
elif self.multiworld.randomize_type_chart[self.player] == "chaos":
157+
types = poke_data.type_names.values()
158+
matchups = []
159+
for type1 in types:
160+
for type2 in types:
161+
matchups.append([type1, type2])
162+
chart = []
163+
values = list(range(21))
164+
self.multiworld.random.shuffle(matchups)
165+
self.multiworld.random.shuffle(values)
166+
for matchup in matchups:
167+
value = values.pop(0)
168+
values.append(value)
169+
matchup.append(value)
170+
chart.append(matchup)
171+
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
172+
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
173+
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
174+
# to the way effectiveness messages are generated.
175+
self.type_chart = sorted(chart, key=lambda matchup: -matchup[2])
176+
111177
def create_items(self) -> None:
112178
start_inventory = self.multiworld.start_inventory[self.player].value.copy()
113179
if self.multiworld.randomize_pokedex[self.player] == "start_with":
@@ -128,8 +194,7 @@ def create_items(self) -> None:
128194
item = self.create_item(location.original_item)
129195
if (item.classification == ItemClassification.filler and self.multiworld.random.randint(1, 100)
130196
<= self.multiworld.trap_percentage[self.player].value):
131-
item = self.create_item(self.multiworld.random.choice([item for item in item_table if
132-
item_table[item].classification == ItemClassification.trap]))
197+
item = self.create_item(self.select_trap())
133198
if location.event:
134199
self.multiworld.get_location(location.name, self.player).place_locked_item(item)
135200
elif "Badge" not in item.name or self.multiworld.badgesanity[self.player].value:
@@ -255,13 +320,21 @@ def write_spoiler(self, spoiler_handle):
255320

256321
def get_filler_item_name(self) -> str:
257322
if self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value:
258-
return self.multiworld.random.choice([item for item in item_table if
259-
item_table[item].classification == ItemClassification.trap])
323+
return self.select_trap()
260324

261325
return self.multiworld.random.choice([item for item in item_table if item_table[
262326
item].classification == ItemClassification.filler and item not in item_groups["Vending Machine Drinks"] +
263327
item_groups["Unique"]])
264328

329+
def select_trap(self):
330+
if self.traps is None:
331+
self.traps = []
332+
self.traps += ["Poison Trap"] * self.multiworld.poison_trap_weight[self.player].value
333+
self.traps += ["Fire Trap"] * self.multiworld.fire_trap_weight[self.player].value
334+
self.traps += ["Paralyze Trap"] * self.multiworld.paralyze_trap_weight[self.player].value
335+
self.traps += ["Ice Trap"] * self.multiworld.ice_trap_weight[self.player].value
336+
return self.multiworld.random.choice(self.traps)
337+
265338
def fill_slot_data(self) -> dict:
266339
return {
267340
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,

worlds/pokemon_rb/options.py

+36-3
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ class BetterShops(Choice):
487487
class MasterBallPrice(Range):
488488
"""Price for Master Balls. Can only be bought if better_shops is set to add_master_ball, but this will affect the
489489
sell price regardless. Vanilla is 0"""
490+
display_name = "Master Ball Price"
490491
range_end = 999999
491492
default = 5000
492493

@@ -506,14 +507,42 @@ class LoseMoneyOnBlackout(Toggle):
506507

507508

508509
class TrapPercentage(Range):
509-
"""Chance for each filler item to be replaced with trap items: Poison Trap, Paralyze Trap, Ice Trap, and
510-
Fire Trap. These traps apply the status to your entire party! Keep in mind that trainersanity vastly increases the
511-
number of filler items. Make sure to stock up on Ice Heals!"""
510+
"""Chance for each filler item to be replaced with trap items. Keep in mind that trainersanity vastly increases the
511+
number of filler items. The trap weight options will determine which traps can be chosen from and at what likelihood."""
512512
display_name = "Trap Percentage"
513513
range_end = 100
514514
default = 0
515515

516516

517+
class TrapWeight(Choice):
518+
option_low = 1
519+
option_medium = 3
520+
option_high = 5
521+
default = 3
522+
523+
524+
class PoisonTrapWeight(TrapWeight):
525+
"""Weights for Poison Traps. These apply the Poison status to all your party members."""
526+
display_name = "Poison Trap Weight"
527+
528+
529+
class FireTrapWeight(TrapWeight):
530+
"""Weights for Fire Traps. These apply the Burn status to all your party members."""
531+
display_name = "Fire Trap Weight"
532+
533+
534+
class ParalyzeTrapWeight(TrapWeight):
535+
"""Weights for Paralyze Traps. These apply the Paralyze status to all your party members."""
536+
display_name = "Paralyze Trap Weight"
537+
538+
539+
class IceTrapWeight(TrapWeight):
540+
"""Weights for Ice Traps. These apply the Ice status to all your party members. Don't forget to buy Ice Heals!"""
541+
display_name = "Ice Trap Weight"
542+
option_disabled = 0
543+
default = 0
544+
545+
517546
pokemon_rb_options = {
518547
"game_version": GameVersion,
519548
"trainer_name": TrainerName,
@@ -571,5 +600,9 @@ class TrapPercentage(Range):
571600
"starting_money": StartingMoney,
572601
"lose_money_on_blackout": LoseMoneyOnBlackout,
573602
"trap_percentage": TrapPercentage,
603+
"poison_trap_weight": PoisonTrapWeight,
604+
"fire_trap_weight": FireTrapWeight,
605+
"paralyze_trap_weight": ParalyzeTrapWeight,
606+
"ice_trap_weight": IceTrapWeight,
574607
"death_link": DeathLink
575608
}

worlds/pokemon_rb/rom.py

+3-67
Original file line numberDiff line numberDiff line change
@@ -447,70 +447,8 @@ def generate_output(self, output_directory: str):
447447
if badge not in written_badges:
448448
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
449449

450-
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
451-
chart = deepcopy(poke_data.type_chart)
452-
elif self.multiworld.randomize_type_chart[self.player] == "randomize":
453-
types = poke_data.type_names.values()
454-
matchups = []
455-
for type1 in types:
456-
for type2 in types:
457-
matchups.append([type1, type2])
458-
self.multiworld.random.shuffle(matchups)
459-
immunities = self.multiworld.immunity_matchups[self.player].value
460-
super_effectives = self.multiworld.super_effective_matchups[self.player].value
461-
not_very_effectives = self.multiworld.not_very_effective_matchups[self.player].value
462-
normals = self.multiworld.normal_matchups[self.player].value
463-
while super_effectives + not_very_effectives + normals < 225 - immunities:
464-
super_effectives += self.multiworld.super_effective_matchups[self.player].value
465-
not_very_effectives += self.multiworld.not_very_effective_matchups[self.player].value
466-
normals += self.multiworld.normal_matchups[self.player].value
467-
if super_effectives + not_very_effectives + normals > 225 - immunities:
468-
total = super_effectives + not_very_effectives + normals
469-
excess = total - (225 - immunities)
470-
subtract_amounts = (int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
471-
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
472-
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
473-
super_effectives -= subtract_amounts[0]
474-
not_very_effectives -= subtract_amounts[1]
475-
normals -= subtract_amounts[2]
476-
while super_effectives + not_very_effectives + normals > 225 - immunities:
477-
r = self.multiworld.random.randint(0, 2)
478-
if r == 0:
479-
super_effectives -= 1
480-
elif r == 1:
481-
not_very_effectives -= 1
482-
else:
483-
normals -= 1
484-
chart = []
485-
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
486-
[0, 10, 20, 5]):
487-
for _ in range(matchup_list):
488-
matchup = matchups.pop()
489-
matchup.append(matchup_value)
490-
chart.append(matchup)
491-
elif self.multiworld.randomize_type_chart[self.player] == "chaos":
492-
types = poke_data.type_names.values()
493-
matchups = []
494-
for type1 in types:
495-
for type2 in types:
496-
matchups.append([type1, type2])
497-
chart = []
498-
values = list(range(21))
499-
self.multiworld.random.shuffle(matchups)
500-
self.multiworld.random.shuffle(values)
501-
for matchup in matchups:
502-
value = values.pop(0)
503-
values.append(value)
504-
matchup.append(value)
505-
chart.append(matchup)
506-
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
507-
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
508-
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
509-
# to the way effectiveness messages are generated.
510-
chart = sorted(chart, key=lambda matchup: -matchup[2])
511-
512450
type_loc = rom_addresses["Type_Chart"]
513-
for matchup in chart:
451+
for matchup in self.type_chart:
514452
if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10
515453
data[type_loc] = poke_data.type_ids[matchup[0]]
516454
data[type_loc + 1] = poke_data.type_ids[matchup[1]]
@@ -520,8 +458,6 @@ def generate_output(self, output_directory: str):
520458
data[type_loc + 1] = 0xFF
521459
data[type_loc + 2] = 0xFF
522460

523-
self.type_chart = chart
524-
525461
if self.multiworld.normalize_encounter_chances[self.player].value:
526462
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
527463
for i, chance in enumerate(chances):
@@ -652,8 +588,8 @@ def get_base_rom_bytes(game_version: str, hash: str="") -> bytes:
652588
basemd5 = hashlib.md5()
653589
basemd5.update(base_rom_bytes)
654590
if hash != basemd5.hexdigest():
655-
raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
656-
'Get the correct game and version, then dump it')
591+
raise Exception(f"Supplied Base Rom does not match known MD5 for Pokémon {game_version.title()} UE "
592+
"release. Get the correct game and version, then dump it")
657593
return base_rom_bytes
658594

659595

0 commit comments

Comments
 (0)