diff --git a/BaseClasses.py b/BaseClasses.py index fbba6976..ab8be502 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -10,6 +10,7 @@ from typing import List, Dict, Optional, Set, Iterable, Union, Any, Tuple import secrets import random +import Options import Utils @@ -83,7 +84,6 @@ class MultiWorld(): set_player_attr('item_functionality', 'normal') set_player_attr('timer', False) set_player_attr('goal', 'ganon') - set_player_attr('accessibility', 'items') set_player_attr('required_medallions', ['Ether', 'Quake']) set_player_attr('swamp_patch_required', False) set_player_attr('powder_patch_required', False) @@ -109,9 +109,6 @@ class MultiWorld(): set_player_attr('blue_clock_time', 2) set_player_attr('green_clock_time', 4) set_player_attr('can_take_damage', True) - set_player_attr('progression_balancing', True) - set_player_attr('local_items', set()) - set_player_attr('non_local_items', set()) set_player_attr('triforce_pieces_available', 30) set_player_attr('triforce_pieces_required', 20) set_player_attr('shop_shuffle', 'off') @@ -131,10 +128,21 @@ class MultiWorld(): for player in self.player_ids: self.custom_data[player] = {} world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]] - for option in world_type.options: - setattr(self, option, getattr(args, option, {})) + for option_key in world_type.options: + setattr(self, option_key, getattr(args, option_key, {})) + for option_key in Options.common_options: + setattr(self, option_key, getattr(args, option_key, {})) + for option_key in Options.per_game_common_options: + setattr(self, option_key, getattr(args, option_key, {})) self.worlds[player] = world_type(self, player) + # intended for unittests + def set_default_common_options(self): + for option_key, option in Options.common_options.items(): + setattr(self, option_key, {player_id: option(option.default) for player_id in self.player_ids}) + for option_key, option in Options.per_game_common_options.items(): + setattr(self, option_key, {player_id: option(option.default) for player_id in self.player_ids}) + def secure(self): self.random = secrets.SystemRandom() self.is_race = True @@ -384,17 +392,17 @@ class MultiWorld(): """Check if accessibility rules are fulfilled with current or supplied state.""" if not state: state = CollectionState(self) - players = {"none" : set(), + players = {"minimal" : set(), "items": set(), "locations": set()} for player, access in self.accessibility.items(): - players[access].add(player) + players[access.current_key].add(player) beatable_fulfilled = False def location_conditition(location : Location): """Determine if this location has to be accessible, location is already filtered by location_relevant""" - if location.player in players["none"]: + if location.player in players["minimal"]: return False return True @@ -1003,7 +1011,7 @@ class Spoiler(): self.medallions = {} self.playthrough = {} self.unreachables = [] - self.startinventory = [] + self.start_inventory = [] self.locations = {} self.paths = {} self.shops = [] @@ -1021,7 +1029,7 @@ class Spoiler(): self.medallions[f'Misery Mire ({self.world.get_player_name(player)})'] = self.world.required_medallions[player][0] self.medallions[f'Turtle Rock ({self.world.get_player_name(player)})'] = self.world.required_medallions[player][1] - self.startinventory = list(map(str, self.world.precollected_items)) + self.start_inventory = list(map(str, self.world.precollected_items)) self.locations = OrderedDict() listed_locations = set() @@ -1103,7 +1111,7 @@ class Spoiler(): out = OrderedDict() out['Entrances'] = list(self.entrances.values()) out.update(self.locations) - out['Starting Inventory'] = self.startinventory + out['Starting Inventory'] = self.start_inventory out['Special'] = self.medallions if self.hashes: out['Hashes'] = self.hashes @@ -1123,6 +1131,14 @@ class Spoiler(): return variable return 'Yes' if variable else 'No' + def write_option(option_key: str, option_obj: type(Options.Option)): + res = getattr(self.world, option_key)[player] + displayname = getattr(option_obj, "displayname", option_key) + try: + outfile.write(f'{displayname + ":":33}{res.get_current_option_name()}\n') + except: + raise Exception + with open(filename, 'w', encoding="utf-8-sig") as outfile: outfile.write( 'Archipelago Version %s - Seed: %s\n\n' % ( @@ -1134,16 +1150,14 @@ class Spoiler(): if self.world.players > 1: outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_name(player))) outfile.write('Game: %s\n' % self.world.game[player]) - if self.world.players > 1: - outfile.write('Progression Balanced: %s\n' % ( - 'Yes' if self.world.progression_balancing[player] else 'No')) - outfile.write('Accessibility: %s\n' % self.world.accessibility[player]) + for f_option, option in Options.common_options.items(): + write_option(f_option, option) + for f_option, option in Options.per_game_common_options.items(): + write_option(f_option, option) options = self.world.worlds[player].options if options: for f_option, option in options.items(): - res = getattr(self.world, f_option)[player] - displayname = getattr(option, "displayname", f_option) - outfile.write(f'{displayname + ":":33}{res.get_current_option_name()}\n') + write_option(f_option, option) if player in self.world.get_game_players("A Link to the Past"): outfile.write('%s%s\n' % ('Hash: ', self.hashes[player])) @@ -1201,9 +1215,9 @@ class Spoiler(): for recipe in self.world.worlds[player].custom_recipes.values(): outfile.write(f"\n{recipe.name} ({name}): {recipe.ingredients} -> {recipe.products}") - if self.startinventory: + if self.start_inventory: outfile.write('\n\nStarting Inventory:\n\n') - outfile.write('\n'.join(self.startinventory)) + outfile.write('\n'.join(self.start_inventory)) outfile.write('\n\nLocations:\n\n') outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()])) diff --git a/Fill.py b/Fill.py index f5eb974f..f2f00027 100644 --- a/Fill.py +++ b/Fill.py @@ -36,7 +36,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, has_beaten_game = world.has_beaten_game(maximum_exploration_state) for item_to_place in items_to_place: - if world.accessibility[item_to_place.player] == 'none': + if world.accessibility[item_to_place.player] == 'minimal': perform_access_check = not world.has_beaten_game(maximum_exploration_state, item_to_place.player) if single_player_placement else not has_beaten_game else: @@ -52,7 +52,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations, else: # we filled all reachable spots. Maybe the game can be beaten anyway? unplaced_items.append(item_to_place) - if world.accessibility[item_to_place.player] != 'none' and world.can_beat_game(): + if world.accessibility[item_to_place.player] != 'minimal' and world.can_beat_game(): logging.warning( f'Not all items placed. Game beatable anyway. (Could not place {item_to_place})') continue @@ -87,9 +87,9 @@ def distribute_items_restrictive(world: MultiWorld, fill_locations=None): progitempool.append(item) elif item.never_exclude: # this only gets nonprogression items which should not appear in excluded locations nonexcludeditempool.append(item) - elif item.name in world.local_items[item.player]: + elif item.name in world.local_items[item.player].value: localrestitempool[item.player].append(item) - elif item.name in world.non_local_items[item.player]: + elif item.name in world.non_local_items[item.player].value: nonlocalrestitempool.append(item) else: restitempool.append(item) diff --git a/Generate.py b/Generate.py index e7876791..77fef3ac 100644 --- a/Generate.py +++ b/Generate.py @@ -424,6 +424,32 @@ def get_plando_bosses(boss_shuffle: str, plando_options: typing.Set[str]) -> str raise Exception(f"Boss Shuffle {boss_shuffle} is unknown and boss plando is turned off.") +def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, option: type(Options.Option)): + if option_key in game_weights: + try: + if not option.supports_weighting: + player_option = option.from_any(game_weights[option_key]) + else: + player_option = option.from_any(get_choice(option_key, game_weights)) + setattr(ret, option_key, player_option) + except Exception as e: + raise Exception(f"Error generating option {option_key} in {ret.game}") from e + else: + # verify item names existing + if getattr(player_option, "verify_item_name", False): + for item_name in player_option.value: + if item_name not in AutoWorldRegister.world_types[ret.game].item_names: + raise Exception(f"Item {item_name} from option {player_option} " + f"is not a valid item name from {ret.game}") + elif getattr(player_option, "verify_location_name", False): + for location_name in player_option.value: + if location_name not in AutoWorldRegister.world_types[ret.game].location_names: + raise Exception(f"Location {location_name} from option {player_option} " + f"is not a valid location name from {ret.game}") + else: + setattr(ret, option_key, option(option.default)) + + def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("bosses",))): if "linked_options" in weights: weights = roll_linked_options(weights) @@ -450,63 +476,24 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b f"which are not enabled.") ret = argparse.Namespace() + for option_key in Options.per_game_common_options: + if option_key in weights: + raise Exception(f"Option {option_key} has to be in a game's section, not on its own.") + ret.name = get_choice('name', weights) - ret.accessibility = get_choice('accessibility', weights) - ret.progression_balancing = get_choice('progression_balancing', weights, True) + for option_key, option in Options.common_options.items(): + setattr(ret, option_key, option.from_any(get_choice(option_key, weights, option.default))) ret.game = get_choice("game", weights) if ret.game not in weights: raise Exception(f"No game options for selected game \"{ret.game}\" found.") world_type = AutoWorldRegister.world_types[ret.game] game_weights = weights[ret.game] - ret.local_items = set() - for item_name in game_weights.get('local_items', []): - items = world_type.item_name_groups.get(item_name, {item_name}) - for item in items: - if item in world_type.item_names: - ret.local_items.add(item) - else: - raise Exception(f"Could not force item {item} to be world-local, as it was not recognized.") - - ret.non_local_items = set() - for item_name in game_weights.get('non_local_items', []): - items = world_type.item_name_groups.get(item_name, {item_name}) - for item in items: - if item in world_type.item_names: - ret.non_local_items.add(item) - else: - raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.") - - inventoryweights = game_weights.get('start_inventory', {}) - startitems = [] - for item in inventoryweights.keys(): - itemvalue = get_choice_legacy(item, inventoryweights) - if isinstance(itemvalue, int): - for i in range(int(itemvalue)): - startitems.append(item) - elif itemvalue: - startitems.append(item) - ret.startinventory = startitems - ret.start_hints = set(game_weights.get('start_hints', [])) - - ret.excluded_locations = set() - for location in game_weights.get('exclude_locations', []): - if location in world_type.location_names: - ret.excluded_locations.add(location) - else: - raise Exception(f"Could not exclude location {location}, as it was not recognized.") if ret.game in AutoWorldRegister.world_types: - for option_name, option in world_type.options.items(): - if option_name in game_weights: - try: - if issubclass(option, Options.OptionDict) or issubclass(option, Options.OptionList): - setattr(ret, option_name, option.from_any(game_weights[option_name])) - else: - setattr(ret, option_name, option.from_any(get_choice(option_name, game_weights))) - except Exception as e: - raise Exception(f"Error generating option {option_name} in {ret.game}") from e - else: - setattr(ret, option_name, option(option.default)) + for option_key, option in world_type.options.items(): + handle_option(ret, game_weights, option_key, option) + for option_key, option in Options.per_game_common_options.items(): + handle_option(ret, game_weights, option_key, option) if "items" in plando_options: ret.plando_items = roll_item_plando(world_type, game_weights) if ret.game == "Minecraft": @@ -530,6 +517,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b def roll_item_plando(world_type, weights): plando_items = [] + def add_plando_item(item: str, location: str): if item not in world_type.item_name_to_id: raise Exception(f"Could not plando item {item} as the item was not recognized") diff --git a/Main.py b/Main.py index d2ddde92..e9ca59b0 100644 --- a/Main.py +++ b/Main.py @@ -54,14 +54,13 @@ def main(args, seed=None): world.item_functionality = args.item_functionality.copy() world.timer = args.timer.copy() world.goal = args.goal.copy() - world.local_items = args.local_items.copy() + if hasattr(args, "algorithm"): # current GUI options world.algorithm = args.algorithm world.shuffleganon = args.shuffleganon world.custom = args.custom world.customitemarray = args.customitemarray - world.accessibility = args.accessibility.copy() world.open_pyramid = args.open_pyramid.copy() world.boss_shuffle = args.shufflebosses.copy() world.enemy_health = args.enemy_health.copy() @@ -76,7 +75,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.progression_balancing = args.progression_balancing.copy() world.shuffle_prizes = args.shuffle_prizes.copy() world.sprite_pool = args.sprite_pool.copy() world.dark_room_logic = args.dark_room_logic.copy() @@ -115,21 +113,21 @@ def main(args, seed=None): logger.info('') for player in world.player_ids: - for item_name in args.startinventory[player]: + for item_name in world.start_inventory[player].value: world.push_precollected(world.create_item(item_name, player)) for player in world.player_ids: if player in world.get_game_players("A Link to the Past"): # enforce pre-defined local items. if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: - world.local_items[player].add('Triforce Piece') + world.local_items[player].value.add('Triforce Piece') # Not possible to place pendants/crystals out side of boss prizes yet. - world.non_local_items[player] -= item_name_groups['Pendants'] - world.non_local_items[player] -= item_name_groups['Crystals'] + world.non_local_items[player].value -= item_name_groups['Pendants'] + world.non_local_items[player].value -= item_name_groups['Crystals'] # items can't be both local and non-local, prefer local - world.non_local_items[player] -= world.local_items[player] + world.non_local_items[player].value -= world.local_items[player].value logger.info('Creating World.') AutoWorld.call_all(world, "create_regions") @@ -142,13 +140,13 @@ def main(args, seed=None): for player in world.player_ids: locality_rules(world, player) else: - world.non_local_items[1] = set() - world.local_items[1] = set() + world.non_local_items[1].value = set() + world.local_items[1].value = set() AutoWorld.call_all(world, "set_rules") for player in world.player_ids: - exclusion_rules(world, player, args.excluded_locations[player]) + exclusion_rules(world, player, world.exclude_locations[player].value) AutoWorld.call_all(world, "generate_basic") @@ -386,7 +384,7 @@ def create_playthrough(world): logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % ( location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) - if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): + if any([world.accessibility[location.item.player] != 'minimal' for location in sphere_candidates]): raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). ' f'Something went terribly wrong here.') else: diff --git a/Options.py b/Options.py index 871b4d66..c2824197 100644 --- a/Options.py +++ b/Options.py @@ -43,6 +43,9 @@ class Option(metaclass=AssembleOptions): # Handled in get_option_name() autodisplayname = False + # can be weighted between selections + supports_weighting = True + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.get_current_option_name()})" @@ -81,6 +84,7 @@ class Toggle(Option): default = 0 def __init__(self, value: int): + assert value == 0 or value == 1 self.value = value @classmethod @@ -119,6 +123,7 @@ class Toggle(Option): def get_option_name(cls, value): return ["No", "Yes"][int(value)] + class DefaultOnToggle(Toggle): default = 1 @@ -158,7 +163,7 @@ class Choice(Option): elif isinstance(other, int): assert other in self.name_lookup return other == self.value - elif isinstance(other, bool): + elif isinstance(other, bool): return other == bool(self.value) else: raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") @@ -177,6 +182,7 @@ class Choice(Option): else: raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") + class Range(Option, int): range_start = 0 range_end = 1 @@ -234,6 +240,7 @@ class OptionNameSet(Option): class OptionDict(Option): default = {} + supports_weighting = False def __init__(self, value: typing.Dict[str, typing.Any]): self.value: typing.Dict[str, typing.Any] = value @@ -246,14 +253,17 @@ class OptionDict(Option): raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") def get_option_name(self, value): - return str(value) + return ", ".join(f"{key}: {value}" for key, value in self.value.items()) -class OptionList(Option): +class OptionList(Option, list): default = [] + supports_weighting = False + value: list def __init__(self, value: typing.List[str, typing.Any]): self.value = value + super(OptionList, self).__init__() @classmethod def from_text(cls, text: str): @@ -266,23 +276,106 @@ class OptionList(Option): return cls.from_text(str(data)) def get_option_name(self, value): - return str(value) + return ", ".join(self.value) +class OptionSet(Option, set): + default = frozenset() + supports_weighting = False + value: set + + def __init__(self, value: typing.Union[typing.Set[str, typing.Any], typing.List[str, typing.Any]]): + self.value = set(value) + super(OptionSet, self).__init__() + + @classmethod + def from_text(cls, text: str): + return cls([option.strip() for option in text.split(",")]) + + @classmethod + def from_any(cls, data: typing.Any): + if type(data) == list: + return cls(data) + elif type(data) == set: + return cls(data) + return cls.from_text(str(data)) + + def get_option_name(self, value): + return ", ".join(self.value) + local_objective = Toggle # local triforce pieces, local dungeon prizes etc. class Accessibility(Choice): + """Set rules for reachability of your items/locations. + Locations: ensure everything can be reached and acquired. + Items: ensure all logically relevant items can be acquired. + Minimal: ensure what is needed to reach your goal can be acquired.""" + option_locations = 0 option_items = 1 - option_beatable = 2 + option_minimal = 2 + alias_none = 2 + default = 1 +class ProgressionBalancing(DefaultOnToggle): + """A system that moves progression earlier, to try and prevent the player from getting stuck and bored early.""" + + +common_options = { + "progression_balancing": ProgressionBalancing, + "accessibility": Accessibility +} + + +class ItemSet(OptionSet): + # implemented by Generate + verify_item_name = True + + +class LocalItems(ItemSet): + """Forces these items to be in their native world.""" + displayname = "Local Items" + + +class NonLocalItems(ItemSet): + """Forces these items to be outside their native world.""" + displayname = "Not Local Items" + + +class StartInventory(OptionDict): + """Start with these items.""" + verify_item_name = True + displayname = "Start Inventory" + + +class StartHints(ItemSet): + """Start with these item's locations prefilled into the !hint command.""" + displayname = "Start Hints" + + +class ExcludeLocations(OptionSet): + """Prevent these locations from having an important item""" + displayname = "Excluded Locations" + verify_location_name = True + + +per_game_common_options = { + # placeholder until they're actually implemented + "local_items": LocalItems, + "non_local_items": NonLocalItems, + "start_inventory": StartInventory, + "start_hints": StartHints, + "exclude_locations": OptionSet +} + if __name__ == "__main__": from worlds.alttp.Options import Logic import argparse + map_shuffle = Toggle compass_shuffle = Toggle keyshuffle = Toggle diff --git a/Utils.py b/Utils.py index 5f3cc0cb..d853513b 100644 --- a/Utils.py +++ b/Utils.py @@ -13,7 +13,7 @@ class Version(typing.NamedTuple): build: int -__version__ = "0.1.7" +__version__ = "0.1.8" version_tuple = tuplize_version(__version__) import builtins diff --git a/WebHostLib/options.py b/WebHostLib/options.py index eab7ec05..9010a015 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -5,6 +5,7 @@ import yaml import json from worlds.AutoWorld import AutoWorldRegister +import Options target_folder = os.path.join("WebHostLib", "static", "generated") @@ -18,10 +19,17 @@ def create(): option.range_end: "maximum value" } return data, notes + + def default_converter(default_value): + if isinstance(default_value, (set, frozenset)): + return list(default_value) + return default_value + for game_name, world in AutoWorldRegister.world_types.items(): res = Template(open(os.path.join("WebHostLib", "templates", "options.yaml")).read()).render( - options=world.options, __version__=__version__, game=game_name, yaml_dump=yaml.dump, - dictify_range=dictify_range + options={**world.options, **Options.per_game_common_options}, + __version__=__version__, game=game_name, yaml_dump=yaml.dump, + dictify_range=dictify_range, default_converter=default_converter, ) with open(os.path.join(target_folder, game_name + ".yaml"), "w") as f: diff --git a/WebHostLib/templates/options.yaml b/WebHostLib/templates/options.yaml index 6fc06c46..5f7c901c 100644 --- a/WebHostLib/templates/options.yaml +++ b/WebHostLib/templates/options.yaml @@ -36,22 +36,7 @@ accessibility: progression_balancing: on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch/sequence break around missing items. - # The following 4 options can be uncommented and moved into a game's section they should affect - # start_inventory: # Begin the file with the listed items/upgrades - # Please only use items for the correct game, use triggers if need to be have seperated lists. - # Pegasus Boots: on - # Bomb Upgrade (+10): 4 - # Arrow Upgrade (+10): 4 -# start_hints: # Begin the game with these items' locations revealed to you at the start of the game. Get the info via !hint in your client. -# - Moon Pearl -# 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" -# non_local_items: # Force certain items to appear outside your world only, unless in single-player. Recognizes some group names, like "Swords" -# - "Progressive Weapons" -# exclude_locations: # Force certain locations to never contain progression items, and always be filled with junk. -# - "Master Sword Pedestal" + {%- macro range_option(option) %} # you can add additional values between minimum and maximum {%- set data, notes = dictify_range(option) %} @@ -69,7 +54,7 @@ progression_balancing: {{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %} {%- endfor -%} {%- else %} - {{ yaml_dump(option.default) | indent(4, first=False) }} + {{ yaml_dump(default_converter(option.default)) | indent(4, first=False) }} {%- endif -%} {%- endfor %} {% if not options %}{}{% endif %} diff --git a/test/dungeons/TestDungeon.py b/test/dungeons/TestDungeon.py index be59c359..27ba8628 100644 --- a/test/dungeons/TestDungeon.py +++ b/test/dungeons/TestDungeon.py @@ -19,6 +19,7 @@ class TestDungeon(unittest.TestCase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.starting_regions = [] # Where to start exploring self.remove_exits = [] # Block dungeon exits self.world.difficulty_requirements[1] = difficulties['normal'] diff --git a/test/inverted/TestInverted.py b/test/inverted/TestInverted.py index 39d142b1..586eb579 100644 --- a/test/inverted/TestInverted.py +++ b/test/inverted/TestInverted.py @@ -19,6 +19,7 @@ class TestInverted(TestBase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.difficulty_requirements[1] = difficulties['normal'] self.world.mode[1] = "inverted" create_inverted_regions(self.world, 1) diff --git a/test/inverted/TestInvertedBombRules.py b/test/inverted/TestInvertedBombRules.py index fc7e2ad0..cca252e3 100644 --- a/test/inverted/TestInvertedBombRules.py +++ b/test/inverted/TestInvertedBombRules.py @@ -20,6 +20,7 @@ class TestInvertedBombRules(unittest.TestCase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.difficulty_requirements[1] = difficulties['normal'] create_inverted_regions(self.world, 1) create_dungeons(self.world, 1) diff --git a/test/inverted_minor_glitches/TestInvertedMinor.py b/test/inverted_minor_glitches/TestInvertedMinor.py index 570de3fc..d737f21a 100644 --- a/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/test/inverted_minor_glitches/TestInvertedMinor.py @@ -20,6 +20,7 @@ class TestInvertedMinor(TestBase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.mode[1] = "inverted" self.world.logic[1] = "minorglitches" self.world.difficulty_requirements[1] = difficulties['normal'] diff --git a/test/inverted_owg/TestInvertedOWG.py b/test/inverted_owg/TestInvertedOWG.py index ba779043..486c3cb9 100644 --- a/test/inverted_owg/TestInvertedOWG.py +++ b/test/inverted_owg/TestInvertedOWG.py @@ -21,6 +21,7 @@ class TestInvertedOWG(TestBase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.logic[1] = "owglitches" self.world.mode[1] = "inverted" self.world.difficulty_requirements[1] = difficulties['normal'] diff --git a/test/minor_glitches/TestMinor.py b/test/minor_glitches/TestMinor.py index f86216ae..ac277205 100644 --- a/test/minor_glitches/TestMinor.py +++ b/test/minor_glitches/TestMinor.py @@ -20,6 +20,7 @@ class TestMinor(TestBase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.logic[1] = "minorglitches" self.world.difficulty_requirements[1] = difficulties['normal'] create_regions(self.world, 1) diff --git a/test/owg/TestVanillaOWG.py b/test/owg/TestVanillaOWG.py index 81acd492..a4e584f8 100644 --- a/test/owg/TestVanillaOWG.py +++ b/test/owg/TestVanillaOWG.py @@ -21,6 +21,7 @@ class TestVanillaOWG(TestBase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.difficulty_requirements[1] = difficulties['normal'] self.world.logic[1] = "owglitches" create_regions(self.world, 1) diff --git a/test/vanilla/TestVanilla.py b/test/vanilla/TestVanilla.py index 0e6208d8..4ffddc07 100644 --- a/test/vanilla/TestVanilla.py +++ b/test/vanilla/TestVanilla.py @@ -19,6 +19,7 @@ class TestVanilla(TestBase): for name, option in AutoWorld.AutoWorldRegister.world_types["A Link to the Past"].options.items(): setattr(args, name, {1: option.from_any(option.default)}) self.world.set_options(args) + self.world.set_default_common_options() self.world.logic[1] = "noglitches" self.world.difficulty_requirements[1] = difficulties['normal'] create_regions(self.world, 1) diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 02efe463..421f0bf6 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -194,19 +194,8 @@ def parse_arguments(argv, no_defaults=False): time). ''', type=int) - 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('--non_local_items', default=defval(''), - help='Specifies a list of items that will 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='''\ - Select Item/Location Accessibility. (default: %(default)s) - Items: You can reach all unique inventory items. No guarantees about - reaching all locations or all keys. - Locations: You will be able to reach every location in the game. - None: You will be able to reach enough locations to beat the game. - ''') # included for backwards compatibility parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(True)) parser.add_argument('--no-shuffleganon', help='''\ @@ -222,8 +211,7 @@ def parse_arguments(argv, no_defaults=False): sprite that will be extracted. ''') parser.add_argument('--gui', help='Launch the GUI', action='store_true') - parser.add_argument('--progression_balancing', action='store_true', default=defval(False), - help="Enable Multiworld Progression balancing.") + parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core')) parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos', "singularity"]) @@ -285,10 +273,10 @@ def parse_arguments(argv, no_defaults=False): for name in ['logic', 'mode', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'open_pyramid', 'timer', 'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time', - 'local_items', 'non_local_items', 'accessibility', 'beemizer', + 'beemizer', 'shufflebosses', 'enemy_health', 'enemy_damage', 'sprite', - "progression_balancing", "triforce_pieces_available", + "triforce_pieces_available", "triforce_pieces_required", "shop_shuffle", "required_medallions", "start_hints", "plando_items", "plando_texts", "plando_connections", "er_seeds", diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 2b207988..1f39a169 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -244,7 +244,7 @@ def generate_itempool(world): world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False) if world.goal[player] == 'icerodhunt': - world.progression_balancing[player] = False + world.progression_balancing[player].value = False loc = world.get_location('Turtle Rock - Boss', player) world.push_item(loc, ItemFactory('Triforce Piece', player), False) world.treasure_hunt_count[player] = 1 diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index ca0857cd..35762fe2 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -27,8 +27,8 @@ def set_rules(world): else: # Set access rules according to max glitches for multiworld progression. # Set accessibility to none, and shuffle assuming the no logic players can always win - world.accessibility[player] = 'none' - world.progression_balancing[player] = False + world.accessibility[player] = world.accessibility[player].from_text("minimal") + world.progression_balancing[player].value = False else: world.completion_condition[player] = lambda state: state.has('Triforce', player) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 27e0e19d..5257178a 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -74,9 +74,9 @@ class ALTTPWorld(World): for dungeon_item in ["smallkey_shuffle", "bigkey_shuffle", "compass_shuffle", "map_shuffle"]: option = getattr(world, dungeon_item)[player] if option == "own_world": - world.local_items[player] |= self.item_name_groups[option.item_name_group] + world.local_items[player].value |= self.item_name_groups[option.item_name_group] elif option == "different_world": - world.non_local_items[player] |= self.item_name_groups[option.item_name_group] + world.non_local_items[player].value |= self.item_name_groups[option.item_name_group] elif option.in_dungeon: self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group] if option == "original_dungeon": diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index 06177c48..cc07d491 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -1,16 +1,16 @@ def locality_rules(world, player): - if world.local_items[player]: + if world.local_items[player].value: for location in world.get_locations(): if location.player != player: - forbid_items_for_player(location, world.local_items[player], player) - if world.non_local_items[player]: + forbid_items_for_player(location, world.local_items[player].value, player) + if world.non_local_items[player].value: for location in world.get_locations(): if location.player == player: - forbid_items_for_player(location, world.non_local_items[player], player) + forbid_items_for_player(location, world.non_local_items[player].value, player) -def exclusion_rules(world, player: int, excluded_locations: set): - for loc_name in excluded_locations: +def exclusion_rules(world, player: int, exclude_locations: set): + for loc_name in exclude_locations: location = world.get_location(loc_name, player) add_item_rule(location, lambda i: not (i.advancement or i.never_exclude)) location.excluded = True