From e55726efca5ec5846b7fdda29946d6ffea8ae8cb Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Wed, 3 Jun 2020 22:13:58 +0200 Subject: [PATCH] make world-local items available as a general option --- BaseClasses.py | 1 + EntranceRandomizer.py | 7 +++++-- Items.py | 27 ++++++++++++++++++++++++--- Main.py | 1 + Mystery.py | 14 +++++++++++++- Rules.py | 5 ++++- easy.yaml | 5 +++++ 7 files changed, 53 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 87b62d66..3eed0a3e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -113,6 +113,7 @@ class World(object): set_player_attr('can_take_damage', True) set_player_attr('glitch_boots', True) set_player_attr('progression_balancing', True) + set_player_attr('local_items', set()) def get_name_string_for_object(self, obj) -> str: return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index f6689afc..ee4cadf7 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -236,7 +236,10 @@ def parse_arguments(argv, no_defaults=False): Keys are universal, shooting arrows costs rupees, and a few other little things make this more like Zelda-1. ''', action='store_true') - parser.add_argument('--startinventory', default=defval(''), help='Specifies a list of items that will be in your starting inventory (separated by commas)') + parser.add_argument('--startinventory', default=defval(''), + help='Specifies a list of items that will be in your starting inventory (separated by commas)') + parser.add_argument('--local_items', default=defval(''), + help='Specifies a list of items that will not spread across the multiworld (separated by commas)') parser.add_argument('--custom', default=defval(False), help='Not supported.') parser.add_argument('--customitemarray', default=defval(False), help='Not supported.') parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ @@ -323,7 +326,7 @@ def parse_arguments(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'timer', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'retro', 'accessibility', 'hints', 'beemizer', + 'local_items', 'retro', 'accessibility', 'hints', 'beemizer', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', "skip_progression_balancing", diff --git a/Items.py b/Items.py index 9447bb4d..8c1eafeb 100644 --- a/Items.py +++ b/Items.py @@ -128,11 +128,11 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla 'Compass (Escape)': (False, True, 'Compass', 0x8F, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Hyrule Castle'), 'Map (Escape)': (False, True, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'), 'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'), - # doors-specific items, baserom will not be able to understand these + # doors-specific items, baserom will not be able to understand these 'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'), 'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'), 'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'), - # end of doors-specific items + # end of doors-specific items 'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'), 'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'), 'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'), @@ -183,4 +183,25 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla lookup_id_to_name = {data[3]: name for name, data in item_table.items()} -hint_blacklist = {"Triforce"} \ No newline at end of file +hint_blacklist = {"Triforce"} + +item_name_groups = {"Bows": {"Bow", "Silver Arrows", "Progressive Bow (Alt)", "Progressive Bow"}} +# generic groups, (Name, substring) +_simple_groups = {("Swords", "Sword"), + + ("Small Keys", "Small Key"), + ("Big Keys", "Big Key"), + ("Compasses", "Compass"), + ("Maps", "Map"), + + ("Bottles", "Bottle"), + ("Potions", "Potion"), + ("Rupees", "Rupee") + } +for basename, substring in _simple_groups: + tempset = item_name_groups[basename] = set() + for itemname in item_table: + if substring in itemname: + tempset.add(itemname) + +del (_simple_groups) diff --git a/Main.py b/Main.py index 125414d7..cd878fab 100644 --- a/Main.py +++ b/Main.py @@ -85,6 +85,7 @@ def main(args, seed=None): item = ItemFactory(tok.strip(), player) if item: world.push_precollected(item) + world.local_items[player] = {item.strip() for item in args.local_items[player].split(',')} if world.mode[player] != 'inverted': create_regions(world, player) diff --git a/Mystery.py b/Mystery.py index a862baa2..55e91f85 100644 --- a/Mystery.py +++ b/Mystery.py @@ -14,6 +14,7 @@ from Utils import parse_yaml from Rom import get_sprite_from_name from EntranceRandomizer import parse_arguments from Main import main as ERmain +from Items import item_name_groups, item_table @@ -355,11 +356,22 @@ def roll_settings(weights): startitems.append(item) elif itemvalue: startitems.append(item) - ret.glitch_boots = get_choice('glitch_boots', weights) if 'glitch_boots' in weights else True ret.startinventory = ','.join(startitems) + ret.glitch_boots = get_choice('glitch_boots', weights) if 'glitch_boots' in weights else True + ret.remote_items = get_choice('remote_items', weights) if 'remote_items' in weights else False + ret.local_items = set() + for item_name in weights.get('local_items', []): + items = item_name_groups.get(item_name, {item_name}) + for item in items: + if item in item_table: + ret.local_items.add(item) + else: + logging.warning(f"Could not force item {item} to be world-local, as it was not recognized.") + ret.local_items = ",".join(ret.local_items) + if 'rom' in weights: romweights = weights['rom'] ret.sprite = get_choice('sprite', romweights) diff --git a/Rules.py b/Rules.py index 7dfcf31c..420f2d35 100644 --- a/Rules.py +++ b/Rules.py @@ -138,9 +138,12 @@ def global_rules(world, player): # ganon can only carry triforce add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) if world.goal[player] == "localtriforcehunt": + world.local_items[player].add('Triforce Piece') + if world.local_items[player]: for location in world.get_locations(): if location.player != player: - forbid_item(location, 'Triforce Piece', player) + for item in world.local_items[player]: + forbid_item(location, item, player) # determines which S&Q locations are available - hide from paths since it isn't an in-game location world.get_region('Menu', player).can_reach_private = lambda state: True for exit in world.get_region('Menu', player).exits: diff --git a/easy.yaml b/easy.yaml index 2fe3909f..bab35eb3 100644 --- a/easy.yaml +++ b/easy.yaml @@ -151,6 +151,11 @@ timer: ohko: 0 timed_countdown: 0 display: 0 +#can be uncommented to use it +#local_items: #force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords" +# - "Moon Pearl" +# - "Small Keys" +# - "Big Keys" glitch_boots: on: 1 # enables that you start with Pegasus Boots in any glitched logic mode off: 0