diff --git a/Main.py b/Main.py index ba1787b0..372cadc5 100644 --- a/Main.py +++ b/Main.py @@ -361,6 +361,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No if game_world.data_version == 0 and game_world.game not in datapackage: datapackage[game_world.game] = worlds.network_data_package["games"][game_world.game] datapackage[game_world.game]["item_name_groups"] = game_world.item_name_groups + datapackage[game_world.game]["location_name_groups"] = game_world.location_name_groups multidata = { "slot_data": slot_data, diff --git a/MultiServer.py b/MultiServer.py index faeb1b22..6c3106a9 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -163,7 +163,9 @@ class Context: item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})') item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]] location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})') + location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]] all_item_and_group_names: typing.Dict[str, typing.Set[str]] + all_location_and_group_names: typing.Dict[str, typing.Set[str]] non_hintable_names: typing.Dict[str, typing.Set[str]] def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int, @@ -232,7 +234,9 @@ class Context: # init empty to satisfy linter, I suppose self.gamespackage = {} self.item_name_groups = {} + self.location_name_groups = {} self.all_item_and_group_names = {} + self.all_location_and_group_names = {} self.non_hintable_names = collections.defaultdict(frozenset) self._load_game_data() @@ -244,6 +248,8 @@ class Context: self.item_name_groups = {world_name: world.item_name_groups for world_name, world in worlds.AutoWorldRegister.world_types.items()} + self.location_name_groups = {world_name: world.location_name_groups for world_name, world in + worlds.AutoWorldRegister.world_types.items()} for world_name, world in worlds.AutoWorldRegister.world_types.items(): self.non_hintable_names[world_name] = world.hint_blacklist @@ -255,6 +261,8 @@ class Context: self.location_names[location_id] = location_name self.all_item_and_group_names[game_name] = \ set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name]) + self.all_location_and_group_names[game_name] = \ + set(game_package["location_name_to_id"]) | set(self.location_name_groups[game_name]) def item_names_for_game(self, game: str) -> typing.Optional[typing.Dict[str, int]]: return self.gamespackage[game]["item_name_to_id"] if game in self.gamespackage else None @@ -428,10 +436,14 @@ class Context: logging.info(f"Loading custom datapackage for game {game_name}") self.gamespackage[game_name] = data self.item_name_groups[game_name] = data["item_name_groups"] + self.location_name_groups[game_name] = data["location_name_groups"] del data["item_name_groups"] # remove from datapackage, but keep in self.item_name_groups + del data["location_name_groups"] self._init_game_data() for game_name, data in self.item_name_groups.items(): self.read_data[f"item_name_groups_{game_name}"] = lambda lgame=game_name: self.item_name_groups[lgame] + for game_name, data in self.location_name_groups.items(): + self.read_data[f"location_name_groups_{game_name}"] = lambda lgame=game_name: self.location_name_groups[lgame] # saving @@ -1403,7 +1415,7 @@ class ClientMessageProcessor(CommonCommandProcessor): if game not in self.ctx.all_item_and_group_names: self.output("Can't look up item/location for unknown game. Hint for ID instead.") return False - names = self.ctx.location_names_for_game(game) \ + names = self.ctx.all_location_and_group_names[game] \ if for_location else \ self.ctx.all_item_and_group_names[game] hint_name, usable, response = get_intended_text(input_text, names) @@ -1419,6 +1431,11 @@ class ClientMessageProcessor(CommonCommandProcessor): hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item_name)) elif not for_location and hint_name in self.ctx.item_names_for_game(game): # item name hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name) + elif hint_name in self.ctx.location_name_groups[game]: # location group name + hints = [] + for loc_name in self.ctx.location_name_groups[game][hint_name]: + if loc_name in self.ctx.location_names_for_game(game): + hints.extend(collect_hint_location_name(self.ctx, self.client.team, self.client.slot, loc_name)) else: # location name hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name) diff --git a/Options.py b/Options.py index 5ce0901a..8c739626 100644 --- a/Options.py +++ b/Options.py @@ -739,6 +739,11 @@ class VerifyKeys: for item_name in self.value: new_value |= world.item_name_groups.get(item_name, {item_name}) self.value = new_value + elif self.convert_name_groups and self.verify_location_name: + new_value = type(self.value)() + for loc_name in self.value: + new_value |= world.location_name_groups.get(loc_name, {loc_name}) + self.value = new_value if self.verify_item_name: for item_name in self.value: if item_name not in world.item_names: diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index f2a639ee..3fb705bd 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -149,6 +149,9 @@ class World(metaclass=AutoWorldRegister): item_name_groups: ClassVar[Dict[str, Set[str]]] = {} """maps item group names to sets of items. Example: {"Weapons": {"Sword", "Bow"}}""" + location_name_groups: ClassVar[Dict[str, Set[str]]] = {} + """maps location group names to sets of locations. Example: {"Sewer": {"Sewer Key Drop 1", "Sewer Key Drop 2"}}""" + data_version: ClassVar[int] = 1 """ increment this every time something in your world's names/id mappings changes. diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 4121dbad..78f12e51 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -117,6 +117,75 @@ class ALTTPWorld(World): option_definitions = alttp_options topology_present = True item_name_groups = item_name_groups + location_name_groups = { + "Blind's Hideout": {"Blind's Hideout - Top", "Blind's Hideout - Left", "Blind's Hideout - Right", + "Blind's Hideout - Far Left", "Blind's Hideout - Far Right"}, + "Kakariko Well": {"Kakariko Well - Top", "Kakariko Well - Left", "Kakariko Well - Middle", + "Kakariko Well - Right", "Kakariko Well - Bottom"}, + "Mini Moldorm Cave": {"Mini Moldorm Cave - Far Left", "Mini Moldorm Cave - Left", "Mini Moldorm Cave - Right", + "Mini Moldorm Cave - Far Right", "Mini Moldorm Cave - Generous Guy"}, + "Paradox Cave": {"Paradox Cave Lower - Far Left", "Paradox Cave Lower - Left", "Paradox Cave Lower - Right", + "Paradox Cave Lower - Far Right", "Paradox Cave Lower - Middle", "Paradox Cave Upper - Left", + "Paradox Cave Upper - Right"}, + "Hype Cave": {"Hype Cave - Top", "Hype Cave - Middle Right", "Hype Cave - Middle Left", + "Hype Cave - Bottom", "Hype Cave - Generous Guy"}, + "Hookshot Cave": {"Hookshot Cave - Top Right", "Hookshot Cave - Top Left", "Hookshot Cave - Bottom Right", + "Hookshot Cave - Bottom Left"}, + "Hyrule Castle": {"Hyrule Castle - Boomerang Chest", "Hyrule Castle - Map Chest", + "Hyrule Castle - Zelda's Chest", "Sewers - Dark Cross", "Sewers - Secret Room - Left", + "Sewers - Secret Room - Middle", "Sewers - Secret Room - Right"}, + "Eastern Palace": {"Eastern Palace - Compass Chest", "Eastern Palace - Big Chest", + "Eastern Palace - Cannonball Chest", "Eastern Palace - Big Key Chest", + "Eastern Palace - Map Chest", "Eastern Palace - Boss"}, + "Desert Palace": {"Desert Palace - Big Chest", "Desert Palace - Torch", "Desert Palace - Map Chest", + "Desert Palace - Compass Chest", "Desert Palace Big Key Chest", "Desert Palace - Boss"}, + "Tower of Hera": {"Tower of Hera - Basement Cage", "Tower of Hera - Map Chest", "Tower of Hera - Big Key Chest", + "Tower of Hera - Compass Chest", "Tower of Hera - Big Chest", "Tower of Hera - Boss"}, + "Palace of Darkness": {"Palace of Darkness - Shooter Room", "Palace of Darkness - The Arena - Bridge", + "Palace of Darkness - Stalfos Basement", "Palace of Darkness - Big Key Chest", + "Palace of Darkness - The Arena - Ledge", "Palace of Darkness - Map Chest", + "Palace of Darkness - Compass Chest", "Palace of Darkness - Dark Basement - Left", + "Palace of Darkness - Dark Basement - Right", "Palace of Darkness - Dark Maze - Top", + "Palace of Darkness - Dark Maze - Bottom", "Palace of Darkness - Big Chest", + "Palace of Darkness - Harmless Hellway", "Palace of Darkness - Boss"}, + "Swamp Palace": {"Swamp Palace - Entrance", "Swamp Palace - Swamp Palace - Map Chest", + "Swamp Palace - Big Chest", "Swamp Palace - Compass Chest", "Swamp Palace - Big Key Chest", + "Swamp Palace - West Chest", "Swamp Palace - Flooded Room - Left", + "Swamp Palace - Flooded Room - Right", "Swamp Palace - Waterfall Room", "Swamp Palace - Boss"}, + "Thieves' Town": {"Thieves' Town - Big Key Chest", "Thieves' Town - Map Chest", "Thieves' Town - Compass Chest", + "Thieves' Town - Ambush Chest", "Thieves' Town - Attic", "Thieves' Town - Big Chest", + "Thieves' Town - Blind's Cell", "Thieves' Town - Boss"}, + "Skull Woods": {"Skull Woods - Map Chest", "Skull Woods - Pinball Room", "Skull Woods - Compass Chest", + "Skull Woods - Pot Prison", "Skull Woods - Big Chest", "Skull Woods - Big Key Chest", + "Skull Woods - Bridge Room", "Skull Woods - Boss"}, + "Ice Palace": {"Ice Palace - Compass Chest", "Ice Palace - Freezor Chest", "Ice Palace - Big Chest", + "Ice Palace - Freezor Chest", "Ice Palace - Big Chest", "Ice Palace - Iced T Room", + "Ice Palace - Spike Room", "Ice Palace - Big Key Chest", "Ice Palace - Map Chest", + "Ice Palace - Boss"}, + "Misery Mire": {"Misery Mire - Big Chest", "Misery Mire - Map Chest", "Misery Mire - Main Lobby", + "Misery Mire - Bridge Chest", "Misery Mire - Spike Chest", "Misery Mire - Compass Chest", + "Misery Mire - Big Key Chest", "Misery Mire - Boss"}, + "Turtle Rock": {"Turtle Rock - Compass Chest", "Turtle Rock - Roller Room - Left", + "Turtle Rock - Roller Room - Right", "Turtle Room - Chain Chomps", "Turtle Rock - Big Key Chest", + "Turtle Rock - Big Chest", "Turtle Rock - Crystaroller Room", + "Turtle Rock - Eye Bridge - Bottom Left", "Turtle Rock - Eye Bridge - Bottom Right", + "Turtle Rock - Eye Bridge - Top Left", "Turtle Rock - Eye Bridge - Top Right", "Turtle Rock - Boss"}, + "Ganons Tower": {"Ganons Tower - Bob's Torch", "Ganon's Tower - Hope Room - Left", + "Ganons Tower - Hope Room - Right", "Ganons Tower - Tile Room", + "Ganons Tower - Compass Room - Top Left", "Ganons Tower - Compass Room - Top Right", + "Ganons Tower - Compass Room - Bottom Left", "Ganons Tower - Compass Room - Bottom Left", + "Ganons Tower - DMs Room - Top Left", "Ganons Tower - DMs Room - Top Right", + "Ganons Tower - DMs Room - Bottom Left", "Ganons Tower - DMs Room - Bottom Right", + "Ganons Tower - Map Chest", "Ganons Tower - Firesnake Room", + "Ganons Tower - Randomizer Room - Top Left", "Ganons Tower - Randomizer Room - Top Right", + "Ganons Tower - Randomizer Room - Bottom Left", "Ganons Tower - Randomizer Room - Bottom Right", + "Ganons Tower - Bob's Chest", "Ganons Tower - Big Chest", "Ganons Tower - Big Key Room - Left", + "Ganons Tower - Big Key Room - Right", "Ganons Tower - Big Key Chest", + "Ganons Tower - Mini Helmasaur Room - Left", "Ganons Tower - Mini Helmasaur Room - Right", + "Ganons Tower - Pre-Moldorm Room", "Ganons Tower - Validation Chest"}, + "Ganons Tower Climb": {"Ganons Tower - Mini Helmasaur Room - Left", "Ganons Tower - Mini Helmasaur Room - Right", + "Ganons Tower - Pre-Moldorm Room", "Ganons Tower - Validation Chest"}, + } hint_blacklist = {"Triforce"} item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int}