From 5ea03c71c01b34eb2d37b981b1cbea6d802d2618 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 8 Jun 2021 21:58:11 +0200 Subject: [PATCH] start moving some alttp options over to the new system --- BaseClasses.py | 12 +++------- Gui.py | 2 +- Main.py | 13 ++++------- Mystery.py | 14 ++++++----- Options.py | 37 +++++++++++++++++++++++++----- playerSettings.yaml | 6 ++--- worlds/alttp/EntranceRandomizer.py | 25 ++------------------ worlds/alttp/Rom.py | 4 ++-- worlds/alttp/Shops.py | 2 +- 9 files changed, 55 insertions(+), 60 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 0f5e5e6b..37a42068 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -113,8 +113,6 @@ class MultiWorld(): set_player_attr('bush_shuffle', False) set_player_attr('beemizer', 0) set_player_attr('escape_assist', []) - set_player_attr('crystals_needed_for_ganon', 7) - set_player_attr('crystals_needed_for_gt', 7) set_player_attr('open_pyramid', False) set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) @@ -131,7 +129,6 @@ class MultiWorld(): set_player_attr('triforce_pieces_available', 30) set_player_attr('triforce_pieces_required', 20) set_player_attr('shop_shuffle', 'off') - set_player_attr('shop_shuffle_slots', 0) set_player_attr('shuffle_prizes', "g") set_player_attr('sprite_pool', []) set_player_attr('dark_room_logic', "lamp") @@ -141,9 +138,6 @@ class MultiWorld(): set_player_attr('plando_connections', []) set_player_attr('game', "A Link to the Past") set_player_attr('completion_condition', lambda state: True) - import Options - for hk_option in Options.hollow_knight_options: - set_player_attr(hk_option, False) self.custom_data = {} for player in range(1, players+1): self.custom_data[player] = {} @@ -1448,7 +1442,7 @@ class Spoiler(object): 'triforce_pieces_available': self.world.triforce_pieces_available, 'triforce_pieces_required': self.world.triforce_pieces_required, 'shop_shuffle': self.world.shop_shuffle, - 'shop_shuffle_slots': self.world.shop_shuffle_slots, + 'shop_item_slots': self.world.shop_item_slots, 'shuffle_prizes': self.world.shuffle_prizes, 'sprite_pool': self.world.sprite_pool, 'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss, @@ -1565,8 +1559,8 @@ class Spoiler(object): "f" in self.metadata["shop_shuffle"][player])) outfile.write('Custom Potion Shop: %s\n' % bool_to_text("w" in self.metadata["shop_shuffle"][player])) - outfile.write('Shop Slots: %s\n' % - self.metadata["shop_shuffle_slots"][player]) + outfile.write('Shop Item Slots: %s\n' % + self.metadata["shop_item_slots"][player]) outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player]) outfile.write( 'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player])) diff --git a/Gui.py b/Gui.py index 73f57882..0dcbd479 100755 --- a/Gui.py +++ b/Gui.py @@ -468,7 +468,7 @@ def guiMain(args=None): if shopWitchShuffleVar.get(): guiargs.shop_shuffle += "w" if shopPoolShuffleVar.get(): - guiargs.shop_shuffle_slots = 30 + guiargs.shop_item_slots = 30 guiargs.shuffle_prizes = {"none": "", "bonk": "b", "general": "g", diff --git a/Main.py b/Main.py index 0eeee312..ca76c7fe 100644 --- a/Main.py +++ b/Main.py @@ -94,8 +94,6 @@ def main(args, seed=None): world.compassshuffle = args.compassshuffle.copy() world.keyshuffle = args.keyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.crystals_needed_for_ganon = args.crystals_ganon.copy() - world.crystals_needed_for_gt = args.crystals_gt.copy() world.open_pyramid = args.open_pyramid.copy() world.boss_shuffle = args.shufflebosses.copy() world.enemy_shuffle = args.enemy_shuffle.copy() @@ -117,7 +115,6 @@ def main(args, seed=None): world.triforce_pieces_available = args.triforce_pieces_available.copy() world.triforce_pieces_required = args.triforce_pieces_required.copy() world.shop_shuffle = args.shop_shuffle.copy() - world.shop_shuffle_slots = args.shop_shuffle_slots.copy() world.progression_balancing = args.progression_balancing.copy() world.shuffle_prizes = args.shuffle_prizes.copy() world.sprite_pool = args.sprite_pool.copy() @@ -130,12 +127,10 @@ def main(args, seed=None): world.required_medallions = args.required_medallions.copy() world.game = args.game.copy() import Options - for hk_option in Options.hollow_knight_options: - setattr(world, hk_option, getattr(args, hk_option, {})) - for factorio_option in Options.factorio_options: - setattr(world, factorio_option, getattr(args, factorio_option, {})) - for minecraft_option in Options.minecraft_options: - setattr(world, minecraft_option, getattr(args, minecraft_option, {})) + for option_set in Options.option_sets: + for option in option_set: + setattr(world, option, getattr(args, option, {})) + world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)} diff --git a/Mystery.py b/Mystery.py index a4fd82f2..b1818f00 100644 --- a/Mystery.py +++ b/Mystery.py @@ -581,6 +581,12 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): + for option_name, option in Options.alttp_options.items(): + if option_name in weights: + setattr(ret, option_name, option.from_any(get_choice(option_name, weights))) + else: + setattr(ret, option_name, option(option.default)) + glitches_required = get_choice('glitches_required', weights) if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'minor_glitches']: logging.warning("Only NMG, OWG and No Logic supported") @@ -632,12 +638,9 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): # fast ganon + ganon at hole ret.open_pyramid = get_choice('open_pyramid', weights, 'goal') - ret.crystals_gt = Options.Crystals.from_any(get_choice('tower_open', weights)).value - ret.crystals_ganon = Options.Crystals.from_any(get_choice('ganon_open', weights)).value - extra_pieces = get_choice('triforce_pieces_mode', weights, 'available') - ret.triforce_pieces_required = Options.TriforcePieces.from_any(get_choice('triforce_pieces_required', weights)).value + ret.triforce_pieces_required = Options.TriforcePieces.from_any(get_choice('triforce_pieces_required', weights)) # sum a percentage to required if extra_pieces == 'percentage': @@ -645,7 +648,7 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): ret.triforce_pieces_available = int(round(ret.triforce_pieces_required * percentage, 0)) # vanilla mode (specify how many pieces are) elif extra_pieces == 'available': - ret.triforce_pieces_available = Options.TriforcePieces.from_any(get_choice('triforce_pieces_available', weights)).value + ret.triforce_pieces_available = Options.TriforcePieces.from_any(get_choice('triforce_pieces_available', weights)) # required pieces + fixed extra elif extra_pieces == 'extra': extra_pieces = max(0, int(get_choice('triforce_pieces_extra', weights, 10))) @@ -653,7 +656,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): # change minimum to required pieces to avoid problems ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90) - ret.shop_shuffle_slots = Options.TriforcePieces.from_any(get_choice('shop_shuffle_slots', weights)).value ret.shop_shuffle = get_choice('shop_shuffle', weights, '') if not ret.shop_shuffle: diff --git a/Options.py b/Options.py index 5200bed0..92213f96 100644 --- a/Options.py +++ b/Options.py @@ -8,8 +8,9 @@ class AssembleOptions(type): options = attrs["options"] = {} name_lookup = attrs["name_lookup"] = {} for base in bases: - options.update(base.options) - name_lookup.update(name_lookup) + if hasattr(base, "options"): + options.update(base.options) + name_lookup.update(name_lookup) new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if name.startswith("option_")} attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()}) @@ -110,7 +111,7 @@ class Choice(Option): return cls.from_text(str(data)) -class Range(Option): +class Range(Option, int): range_start = 0 range_end = 1 @@ -119,7 +120,7 @@ class Range(Option): raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}") elif value > self.range_end: raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__}") - self.value: int = value + self.value = value @classmethod def from_text(cls, text: str) -> Range: @@ -139,6 +140,9 @@ class Range(Option): return cls(data) return cls.from_text(str(data)) + def __str__(self): + return str(self.value) + class OptionNameSet(Option): default = frozenset() @@ -210,12 +214,20 @@ class Crystals(Range): range_end = 7 +class CrystalsTower(Crystals): + pass + + +class CrystalsGanon(Crystals): + default = 7 + + class TriforcePieces(Range): range_start = 1 range_end = 90 -class ShopShuffleSlots(Range): +class ShopItemSlots(Range): range_start = 0 range_end = 30 @@ -240,6 +252,12 @@ class Enemies(Choice): option_chaos = 2 +alttp_options: typing.Dict[str, type(Option)] = { + "crystals_needed_for_gt": CrystalsTower, + "crystals_needed_for_ganon": CrystalsGanon, + "shop_item_slots": ShopItemSlots, +} + mapshuffle = Toggle compassshuffle = Toggle keyshuffle = Toggle @@ -268,7 +286,7 @@ RandomizeLoreTablets = Toggle RandomizeLifebloodCocoons = Toggle RandomizeFlames = Toggle -hollow_knight_randomize_options: typing.Dict[str, Option] = { +hollow_knight_randomize_options: typing.Dict[str, type(Option)] = { "RandomizeDreamers": RandomizeDreamers, "RandomizeSkills": RandomizeSkills, "RandomizeCharms": RandomizeCharms, @@ -409,6 +427,13 @@ minecraft_options: typing.Dict[str, type(Option)] = { "shuffle_structures": Toggle } +option_sets = ( + minecraft_options, + factorio_options, + alttp_options, + hollow_knight_options +) + if __name__ == "__main__": import argparse diff --git a/playerSettings.yaml b/playerSettings.yaml index 2bc168c4..3f09f9d8 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -220,7 +220,7 @@ triforce_pieces_required: # Set to how many out of X triforce pieces you need to 30: 0 40: 0 50: 0 -tower_open: # Crystals required to open GT +crystals_needed_for_gt: # Crystals required to open GT 0: 0 1: 0 2: 0 @@ -232,7 +232,7 @@ tower_open: # Crystals required to open GT random: 0 random-low: 50 # any valid number, weighted towards the lower end random-high: 0 -ganon_open: # Crystals required to hurt Ganon +crystals_needed_for_ganon: # Crystals required to hurt Ganon 0: 0 1: 0 2: 0 @@ -317,7 +317,7 @@ beemizer: # Remove items from the global item pool and replace them with single 4: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 90% are traps and 10% single bees 5: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 100% are traps and 0% single bees ### Shop Settings ### -shop_shuffle_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl) +shop_item_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl) 0: 50 5: 0 15: 0 diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 2673a41a..e0e0dcf9 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -196,22 +196,6 @@ def parse_arguments(argv, no_defaults=False): The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla. ''') - parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'], - help='''\ - How many crystals are needed to defeat ganon. Any other - requirements for ganon for the selected goal still apply. - This setting does not apply when the all dungeons goal is - selected. (default: %(default)s) - Random: Picks a random value between 0 and 7 (inclusive). - 0-7: Number of crystals needed - ''') - parser.add_argument('--crystals_gt', default=defval('7'), const='7', nargs='?', - choices=['0', '1', '2', '3', '4', '5', '6', '7'], - help='''\ - How many crystals are needed to open GT. For inverted mode - this applies to the castle tower door instead. (default: %(default)s) - 0-7: Number of crystals needed - ''') parser.add_argument('--open_pyramid', default=defval('auto'), help='''\ Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon. @@ -337,11 +321,6 @@ def parse_arguments(argv, no_defaults=False): u: shuffle capacity upgrades into the item pool w: consider witch's hut like any other shop and shuffle/randomize it too ''') - parser.add_argument('--shop_shuffle_slots', default=defval(0), - type=lambda value: min(max(int(value), 1), 96), - help=''' - Maximum amount of shop slots able to be filled by items from the item pool. - ''') parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb']) parser.add_argument('--sprite_pool', help='''\ Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''') @@ -397,14 +376,14 @@ def parse_arguments(argv, no_defaults=False): playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True) for name in ['logic', 'mode', 'swordless', 'goal', 'difficulty', 'item_functionality', - 'shuffle', 'crystals_ganon', 'crystals_gt', 'open_pyramid', 'timer', + 'shuffle', 'open_pyramid', 'timer', 'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer', 'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', "progression_balancing", "triforce_pieces_available", - "triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots", + "triforce_pieces_required", "shop_shuffle", "required_medallions", "start_hints", "plando_items", "plando_texts", "plando_connections", "er_seeds", 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves', diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 92ed48f4..dcc906cd 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -885,7 +885,7 @@ def patch_rom(world, rom, player, team, enemized): credits_total = 216 if world.retro[player]: # Old man cave and Take any caves will count towards collection rate. credits_total += 5 - if world.shop_shuffle_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle. + if world.shop_item_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle. credits_total += 30 if 'w' in world.shop_shuffle[player] else 27 rom.write_byte(0x187010, credits_total) # dynamic credits @@ -1705,7 +1705,7 @@ def write_custom_shops(rom, world, player): slot = 0 if shop.type == ShopType.TakeAny else index if item is None: break - if world.shop_shuffle_slots[player] or shop.type == ShopType.TakeAny: + if world.shop_item_slots[player] or shop.type == ShopType.TakeAny: count_shop = (shop.region.name != 'Potion Shop' or 'w' in world.shop_shuffle[player]) and \ shop.region.name != 'Capacity Upgrade' rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0) diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 3514d48a..64cdafc2 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -243,7 +243,7 @@ def create_shops(world, player: int): else: dynamic_shop_slots = total_dynamic_shop_slots - num_slots = min(dynamic_shop_slots, max(0, int(world.shop_shuffle_slots[player]))) # 0 to 30 + num_slots = min(dynamic_shop_slots, world.shop_item_slots[player]) single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots) world.random.shuffle(single_purchase_slots)