Pokemon R/B: Bug fixes and add trap weights (#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
This commit is contained in:
parent
6173bc6e03
commit
32820ba653
|
@ -15,6 +15,7 @@ from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandP
|
||||||
get_base_parser
|
get_base_parser
|
||||||
|
|
||||||
from worlds.pokemon_rb.locations import location_data
|
from worlds.pokemon_rb.locations import location_data
|
||||||
|
from worlds.pokemon_rb.rom import RedDeltaPatch, BlueDeltaPatch
|
||||||
|
|
||||||
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}}
|
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}}
|
||||||
location_bytes_bits = {}
|
location_bytes_bits = {}
|
||||||
|
@ -265,8 +266,16 @@ async def run_game(romfile):
|
||||||
async def patch_and_run_game(game_version, patch_file, ctx):
|
async def patch_and_run_game(game_version, patch_file, ctx):
|
||||||
base_name = os.path.splitext(patch_file)[0]
|
base_name = os.path.splitext(patch_file)[0]
|
||||||
comp_path = base_name + '.gb'
|
comp_path = base_name + '.gb'
|
||||||
with open(Utils.local_path(Utils.get_options()["pokemon_rb_options"][f"{game_version}_rom_file"]), "rb") as stream:
|
if game_version == "blue":
|
||||||
base_rom = bytes(stream.read())
|
delta_patch = BlueDeltaPatch
|
||||||
|
else:
|
||||||
|
delta_patch = RedDeltaPatch
|
||||||
|
|
||||||
|
try:
|
||||||
|
base_rom = delta_patch.get_source_data()
|
||||||
|
except Exception as msg:
|
||||||
|
logger.info(msg, extra={'compact_gui': True})
|
||||||
|
ctx.gui_error('Error', msg)
|
||||||
|
|
||||||
with zipfile.ZipFile(patch_file, 'r') as patch_archive:
|
with zipfile.ZipFile(patch_file, 'r') as patch_archive:
|
||||||
with patch_archive.open('delta.bsdiff4', 'r') as stream:
|
with patch_archive.open('delta.bsdiff4', 'r') as stream:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
|
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
|
||||||
from Fill import fill_restrictive, FillError, sweep_from_pool
|
from Fill import fill_restrictive, FillError, sweep_from_pool
|
||||||
|
@ -62,6 +63,8 @@ class PokemonRedBlueWorld(World):
|
||||||
self.learnsets = None
|
self.learnsets = None
|
||||||
self.trainer_name = None
|
self.trainer_name = None
|
||||||
self.rival_name = None
|
self.rival_name = None
|
||||||
|
self.type_chart = None
|
||||||
|
self.traps = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_assert_generate(cls, world):
|
def stage_assert_generate(cls, world):
|
||||||
|
@ -108,6 +111,69 @@ class PokemonRedBlueWorld(World):
|
||||||
|
|
||||||
process_pokemon_data(self)
|
process_pokemon_data(self)
|
||||||
|
|
||||||
|
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
|
||||||
|
chart = deepcopy(poke_data.type_chart)
|
||||||
|
elif self.multiworld.randomize_type_chart[self.player] == "randomize":
|
||||||
|
types = poke_data.type_names.values()
|
||||||
|
matchups = []
|
||||||
|
for type1 in types:
|
||||||
|
for type2 in types:
|
||||||
|
matchups.append([type1, type2])
|
||||||
|
self.multiworld.random.shuffle(matchups)
|
||||||
|
immunities = self.multiworld.immunity_matchups[self.player].value
|
||||||
|
super_effectives = self.multiworld.super_effective_matchups[self.player].value
|
||||||
|
not_very_effectives = self.multiworld.not_very_effective_matchups[self.player].value
|
||||||
|
normals = self.multiworld.normal_matchups[self.player].value
|
||||||
|
while super_effectives + not_very_effectives + normals < 225 - immunities:
|
||||||
|
super_effectives += self.multiworld.super_effective_matchups[self.player].value
|
||||||
|
not_very_effectives += self.multiworld.not_very_effective_matchups[self.player].value
|
||||||
|
normals += self.multiworld.normal_matchups[self.player].value
|
||||||
|
if super_effectives + not_very_effectives + normals > 225 - immunities:
|
||||||
|
total = super_effectives + not_very_effectives + normals
|
||||||
|
excess = total - (225 - immunities)
|
||||||
|
subtract_amounts = (
|
||||||
|
int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
|
||||||
|
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
|
||||||
|
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
|
||||||
|
super_effectives -= subtract_amounts[0]
|
||||||
|
not_very_effectives -= subtract_amounts[1]
|
||||||
|
normals -= subtract_amounts[2]
|
||||||
|
while super_effectives + not_very_effectives + normals > 225 - immunities:
|
||||||
|
r = self.multiworld.random.randint(0, 2)
|
||||||
|
if r == 0:
|
||||||
|
super_effectives -= 1
|
||||||
|
elif r == 1:
|
||||||
|
not_very_effectives -= 1
|
||||||
|
else:
|
||||||
|
normals -= 1
|
||||||
|
chart = []
|
||||||
|
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
|
||||||
|
[0, 10, 20, 5]):
|
||||||
|
for _ in range(matchup_list):
|
||||||
|
matchup = matchups.pop()
|
||||||
|
matchup.append(matchup_value)
|
||||||
|
chart.append(matchup)
|
||||||
|
elif self.multiworld.randomize_type_chart[self.player] == "chaos":
|
||||||
|
types = poke_data.type_names.values()
|
||||||
|
matchups = []
|
||||||
|
for type1 in types:
|
||||||
|
for type2 in types:
|
||||||
|
matchups.append([type1, type2])
|
||||||
|
chart = []
|
||||||
|
values = list(range(21))
|
||||||
|
self.multiworld.random.shuffle(matchups)
|
||||||
|
self.multiworld.random.shuffle(values)
|
||||||
|
for matchup in matchups:
|
||||||
|
value = values.pop(0)
|
||||||
|
values.append(value)
|
||||||
|
matchup.append(value)
|
||||||
|
chart.append(matchup)
|
||||||
|
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
|
||||||
|
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
|
||||||
|
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
|
||||||
|
# to the way effectiveness messages are generated.
|
||||||
|
self.type_chart = sorted(chart, key=lambda matchup: -matchup[2])
|
||||||
|
|
||||||
def create_items(self) -> None:
|
def create_items(self) -> None:
|
||||||
start_inventory = self.multiworld.start_inventory[self.player].value.copy()
|
start_inventory = self.multiworld.start_inventory[self.player].value.copy()
|
||||||
if self.multiworld.randomize_pokedex[self.player] == "start_with":
|
if self.multiworld.randomize_pokedex[self.player] == "start_with":
|
||||||
|
@ -128,8 +194,7 @@ class PokemonRedBlueWorld(World):
|
||||||
item = self.create_item(location.original_item)
|
item = self.create_item(location.original_item)
|
||||||
if (item.classification == ItemClassification.filler and self.multiworld.random.randint(1, 100)
|
if (item.classification == ItemClassification.filler and self.multiworld.random.randint(1, 100)
|
||||||
<= self.multiworld.trap_percentage[self.player].value):
|
<= self.multiworld.trap_percentage[self.player].value):
|
||||||
item = self.create_item(self.multiworld.random.choice([item for item in item_table if
|
item = self.create_item(self.select_trap())
|
||||||
item_table[item].classification == ItemClassification.trap]))
|
|
||||||
if location.event:
|
if location.event:
|
||||||
self.multiworld.get_location(location.name, self.player).place_locked_item(item)
|
self.multiworld.get_location(location.name, self.player).place_locked_item(item)
|
||||||
elif "Badge" not in item.name or self.multiworld.badgesanity[self.player].value:
|
elif "Badge" not in item.name or self.multiworld.badgesanity[self.player].value:
|
||||||
|
@ -255,13 +320,21 @@ class PokemonRedBlueWorld(World):
|
||||||
|
|
||||||
def get_filler_item_name(self) -> str:
|
def get_filler_item_name(self) -> str:
|
||||||
if self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value:
|
if self.multiworld.random.randint(1, 100) <= self.multiworld.trap_percentage[self.player].value:
|
||||||
return self.multiworld.random.choice([item for item in item_table if
|
return self.select_trap()
|
||||||
item_table[item].classification == ItemClassification.trap])
|
|
||||||
|
|
||||||
return self.multiworld.random.choice([item for item in item_table if item_table[
|
return self.multiworld.random.choice([item for item in item_table if item_table[
|
||||||
item].classification == ItemClassification.filler and item not in item_groups["Vending Machine Drinks"] +
|
item].classification == ItemClassification.filler and item not in item_groups["Vending Machine Drinks"] +
|
||||||
item_groups["Unique"]])
|
item_groups["Unique"]])
|
||||||
|
|
||||||
|
def select_trap(self):
|
||||||
|
if self.traps is None:
|
||||||
|
self.traps = []
|
||||||
|
self.traps += ["Poison Trap"] * self.multiworld.poison_trap_weight[self.player].value
|
||||||
|
self.traps += ["Fire Trap"] * self.multiworld.fire_trap_weight[self.player].value
|
||||||
|
self.traps += ["Paralyze Trap"] * self.multiworld.paralyze_trap_weight[self.player].value
|
||||||
|
self.traps += ["Ice Trap"] * self.multiworld.ice_trap_weight[self.player].value
|
||||||
|
return self.multiworld.random.choice(self.traps)
|
||||||
|
|
||||||
def fill_slot_data(self) -> dict:
|
def fill_slot_data(self) -> dict:
|
||||||
return {
|
return {
|
||||||
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
|
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
|
||||||
|
|
|
@ -487,6 +487,7 @@ class BetterShops(Choice):
|
||||||
class MasterBallPrice(Range):
|
class MasterBallPrice(Range):
|
||||||
"""Price for Master Balls. Can only be bought if better_shops is set to add_master_ball, but this will affect the
|
"""Price for Master Balls. Can only be bought if better_shops is set to add_master_ball, but this will affect the
|
||||||
sell price regardless. Vanilla is 0"""
|
sell price regardless. Vanilla is 0"""
|
||||||
|
display_name = "Master Ball Price"
|
||||||
range_end = 999999
|
range_end = 999999
|
||||||
default = 5000
|
default = 5000
|
||||||
|
|
||||||
|
@ -506,14 +507,42 @@ class LoseMoneyOnBlackout(Toggle):
|
||||||
|
|
||||||
|
|
||||||
class TrapPercentage(Range):
|
class TrapPercentage(Range):
|
||||||
"""Chance for each filler item to be replaced with trap items: Poison Trap, Paralyze Trap, Ice Trap, and
|
"""Chance for each filler item to be replaced with trap items. Keep in mind that trainersanity vastly increases the
|
||||||
Fire Trap. These traps apply the status to your entire party! Keep in mind that trainersanity vastly increases the
|
number of filler items. The trap weight options will determine which traps can be chosen from and at what likelihood."""
|
||||||
number of filler items. Make sure to stock up on Ice Heals!"""
|
|
||||||
display_name = "Trap Percentage"
|
display_name = "Trap Percentage"
|
||||||
range_end = 100
|
range_end = 100
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
|
class TrapWeight(Choice):
|
||||||
|
option_low = 1
|
||||||
|
option_medium = 3
|
||||||
|
option_high = 5
|
||||||
|
default = 3
|
||||||
|
|
||||||
|
|
||||||
|
class PoisonTrapWeight(TrapWeight):
|
||||||
|
"""Weights for Poison Traps. These apply the Poison status to all your party members."""
|
||||||
|
display_name = "Poison Trap Weight"
|
||||||
|
|
||||||
|
|
||||||
|
class FireTrapWeight(TrapWeight):
|
||||||
|
"""Weights for Fire Traps. These apply the Burn status to all your party members."""
|
||||||
|
display_name = "Fire Trap Weight"
|
||||||
|
|
||||||
|
|
||||||
|
class ParalyzeTrapWeight(TrapWeight):
|
||||||
|
"""Weights for Paralyze Traps. These apply the Paralyze status to all your party members."""
|
||||||
|
display_name = "Paralyze Trap Weight"
|
||||||
|
|
||||||
|
|
||||||
|
class IceTrapWeight(TrapWeight):
|
||||||
|
"""Weights for Ice Traps. These apply the Ice status to all your party members. Don't forget to buy Ice Heals!"""
|
||||||
|
display_name = "Ice Trap Weight"
|
||||||
|
option_disabled = 0
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
pokemon_rb_options = {
|
pokemon_rb_options = {
|
||||||
"game_version": GameVersion,
|
"game_version": GameVersion,
|
||||||
"trainer_name": TrainerName,
|
"trainer_name": TrainerName,
|
||||||
|
@ -571,5 +600,9 @@ pokemon_rb_options = {
|
||||||
"starting_money": StartingMoney,
|
"starting_money": StartingMoney,
|
||||||
"lose_money_on_blackout": LoseMoneyOnBlackout,
|
"lose_money_on_blackout": LoseMoneyOnBlackout,
|
||||||
"trap_percentage": TrapPercentage,
|
"trap_percentage": TrapPercentage,
|
||||||
|
"poison_trap_weight": PoisonTrapWeight,
|
||||||
|
"fire_trap_weight": FireTrapWeight,
|
||||||
|
"paralyze_trap_weight": ParalyzeTrapWeight,
|
||||||
|
"ice_trap_weight": IceTrapWeight,
|
||||||
"death_link": DeathLink
|
"death_link": DeathLink
|
||||||
}
|
}
|
|
@ -447,70 +447,8 @@ def generate_output(self, output_directory: str):
|
||||||
if badge not in written_badges:
|
if badge not in written_badges:
|
||||||
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
|
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
|
||||||
|
|
||||||
if self.multiworld.randomize_type_chart[self.player] == "vanilla":
|
|
||||||
chart = deepcopy(poke_data.type_chart)
|
|
||||||
elif self.multiworld.randomize_type_chart[self.player] == "randomize":
|
|
||||||
types = poke_data.type_names.values()
|
|
||||||
matchups = []
|
|
||||||
for type1 in types:
|
|
||||||
for type2 in types:
|
|
||||||
matchups.append([type1, type2])
|
|
||||||
self.multiworld.random.shuffle(matchups)
|
|
||||||
immunities = self.multiworld.immunity_matchups[self.player].value
|
|
||||||
super_effectives = self.multiworld.super_effective_matchups[self.player].value
|
|
||||||
not_very_effectives = self.multiworld.not_very_effective_matchups[self.player].value
|
|
||||||
normals = self.multiworld.normal_matchups[self.player].value
|
|
||||||
while super_effectives + not_very_effectives + normals < 225 - immunities:
|
|
||||||
super_effectives += self.multiworld.super_effective_matchups[self.player].value
|
|
||||||
not_very_effectives += self.multiworld.not_very_effective_matchups[self.player].value
|
|
||||||
normals += self.multiworld.normal_matchups[self.player].value
|
|
||||||
if super_effectives + not_very_effectives + normals > 225 - immunities:
|
|
||||||
total = super_effectives + not_very_effectives + normals
|
|
||||||
excess = total - (225 - immunities)
|
|
||||||
subtract_amounts = (int((excess / (super_effectives + not_very_effectives + normals)) * super_effectives),
|
|
||||||
int((excess / (super_effectives + not_very_effectives + normals)) * not_very_effectives),
|
|
||||||
int((excess / (super_effectives + not_very_effectives + normals)) * normals))
|
|
||||||
super_effectives -= subtract_amounts[0]
|
|
||||||
not_very_effectives -= subtract_amounts[1]
|
|
||||||
normals -= subtract_amounts[2]
|
|
||||||
while super_effectives + not_very_effectives + normals > 225 - immunities:
|
|
||||||
r = self.multiworld.random.randint(0, 2)
|
|
||||||
if r == 0:
|
|
||||||
super_effectives -= 1
|
|
||||||
elif r == 1:
|
|
||||||
not_very_effectives -= 1
|
|
||||||
else:
|
|
||||||
normals -= 1
|
|
||||||
chart = []
|
|
||||||
for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives],
|
|
||||||
[0, 10, 20, 5]):
|
|
||||||
for _ in range(matchup_list):
|
|
||||||
matchup = matchups.pop()
|
|
||||||
matchup.append(matchup_value)
|
|
||||||
chart.append(matchup)
|
|
||||||
elif self.multiworld.randomize_type_chart[self.player] == "chaos":
|
|
||||||
types = poke_data.type_names.values()
|
|
||||||
matchups = []
|
|
||||||
for type1 in types:
|
|
||||||
for type2 in types:
|
|
||||||
matchups.append([type1, type2])
|
|
||||||
chart = []
|
|
||||||
values = list(range(21))
|
|
||||||
self.multiworld.random.shuffle(matchups)
|
|
||||||
self.multiworld.random.shuffle(values)
|
|
||||||
for matchup in matchups:
|
|
||||||
value = values.pop(0)
|
|
||||||
values.append(value)
|
|
||||||
matchup.append(value)
|
|
||||||
chart.append(matchup)
|
|
||||||
# sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
|
|
||||||
# matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
|
|
||||||
# damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
|
|
||||||
# to the way effectiveness messages are generated.
|
|
||||||
chart = sorted(chart, key=lambda matchup: -matchup[2])
|
|
||||||
|
|
||||||
type_loc = rom_addresses["Type_Chart"]
|
type_loc = rom_addresses["Type_Chart"]
|
||||||
for matchup in chart:
|
for matchup in self.type_chart:
|
||||||
if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10
|
if matchup[2] != 10: # don't needlessly divide damage by 10 and multiply by 10
|
||||||
data[type_loc] = poke_data.type_ids[matchup[0]]
|
data[type_loc] = poke_data.type_ids[matchup[0]]
|
||||||
data[type_loc + 1] = poke_data.type_ids[matchup[1]]
|
data[type_loc + 1] = poke_data.type_ids[matchup[1]]
|
||||||
|
@ -520,8 +458,6 @@ def generate_output(self, output_directory: str):
|
||||||
data[type_loc + 1] = 0xFF
|
data[type_loc + 1] = 0xFF
|
||||||
data[type_loc + 2] = 0xFF
|
data[type_loc + 2] = 0xFF
|
||||||
|
|
||||||
self.type_chart = chart
|
|
||||||
|
|
||||||
if self.multiworld.normalize_encounter_chances[self.player].value:
|
if self.multiworld.normalize_encounter_chances[self.player].value:
|
||||||
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
|
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
|
||||||
for i, chance in enumerate(chances):
|
for i, chance in enumerate(chances):
|
||||||
|
@ -652,8 +588,8 @@ def get_base_rom_bytes(game_version: str, hash: str="") -> bytes:
|
||||||
basemd5 = hashlib.md5()
|
basemd5 = hashlib.md5()
|
||||||
basemd5.update(base_rom_bytes)
|
basemd5.update(base_rom_bytes)
|
||||||
if hash != basemd5.hexdigest():
|
if hash != basemd5.hexdigest():
|
||||||
raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
|
raise Exception(f"Supplied Base Rom does not match known MD5 for Pokémon {game_version.title()} UE "
|
||||||
'Get the correct game and version, then dump it')
|
"release. Get the correct game and version, then dump it")
|
||||||
return base_rom_bytes
|
return base_rom_bytes
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue