From c2d69cb05eaaa1514b324d1ef4fbd1ff38444130 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 18 Sep 2022 14:30:43 +0200 Subject: [PATCH] Core: add generic interface to add ER data to hints (#1014) --- BaseClasses.py | 7 ++++++ Main.py | 53 ++++++++++++++-------------------------- worlds/AutoWorld.py | 5 ++++ worlds/alttp/Regions.py | 4 +++ worlds/alttp/__init__.py | 18 +++++++++++++- 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7a7abc8b..df8ac020 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -955,6 +955,13 @@ class Region: return True return False + def get_connecting_entrance(self, is_main_entrance: typing.Callable[[Entrance], bool]) -> Entrance: + for entrance in self.entrances: + if is_main_entrance(entrance): + return entrance + for entrance in self.entrances: # BFS might be better here, trying DFS for now. + return entrance.parent_region.get_connecting_entrance(is_main_entrance) + def __repr__(self): return self.__str__() diff --git a/Main.py b/Main.py index acff7459..bbd0c805 100644 --- a/Main.py +++ b/Main.py @@ -12,7 +12,7 @@ from typing import Dict, Tuple, Optional, Set from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location from worlds.alttp.Items import item_name_groups -from worlds.alttp.Regions import lookup_vanilla_location_to_entrance +from worlds.alttp.Regions import is_main_entrance from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned from worlds.alttp.Shops import SHOP_ID_START, total_shop_slots, FillDisabledShopSlots from Utils import output_path, get_options, __version__, version_tuple @@ -249,24 +249,9 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No output_file_futures.append( pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir)) - def get_entrance_to_region(region: Region): - for entrance in region.entrances: - if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic): - return entrance - for entrance in region.entrances: # BFS might be better here, trying DFS for now. - return get_entrance_to_region(entrance.parent_region) - # collect ER hint info - er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if - world.shuffle[player] != "vanilla" or world.retro_caves[player]} - - for region in world.regions: - if region.player in er_hint_data and region.locations: - main_entrance = get_entrance_to_region(region) - for location in region.locations: - if type(location.address) == int: # skips events and crystals - if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: - er_hint_data[region.player][location.address] = main_entrance.name + er_hint_data: Dict[int, Dict[int, str]] = {} + AutoWorld.call_all(world, 'extend_hint_information', er_hint_data) checks_in_area = {player: {area: list() for area in ordered_areas} for player in range(1, world.players + 1)} @@ -276,22 +261,23 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for location in world.get_filled_locations(): if type(location.address) is int: - main_entrance = get_entrance_to_region(location.parent_region) if location.game != "A Link to the Past": checks_in_area[location.player]["Light World"].append(location.address) - elif location.parent_region.dungeon: - dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', - 'Inverted Ganons Tower': 'Ganons Tower'} \ - .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) - checks_in_area[location.player][dungeonname].append(location.address) - elif location.parent_region.type == RegionType.LightWorld: - checks_in_area[location.player]["Light World"].append(location.address) - elif location.parent_region.type == RegionType.DarkWorld: - checks_in_area[location.player]["Dark World"].append(location.address) - elif main_entrance.parent_region.type == RegionType.LightWorld: - checks_in_area[location.player]["Light World"].append(location.address) - elif main_entrance.parent_region.type == RegionType.DarkWorld: - checks_in_area[location.player]["Dark World"].append(location.address) + else: + main_entrance = location.parent_region.get_connecting_entrance(is_main_entrance) + if location.parent_region.dungeon: + dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', + 'Inverted Ganons Tower': 'Ganons Tower'} \ + .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) + checks_in_area[location.player][dungeonname].append(location.address) + elif location.parent_region.type == RegionType.LightWorld: + checks_in_area[location.player]["Light World"].append(location.address) + elif location.parent_region.type == RegionType.DarkWorld: + checks_in_area[location.player]["Dark World"].append(location.address) + elif main_entrance.parent_region.type == RegionType.LightWorld: + checks_in_area[location.player]["Light World"].append(location.address) + elif main_entrance.parent_region.type == RegionType.DarkWorld: + checks_in_area[location.player]["Dark World"].append(location.address) checks_in_area[location.player]["Total"] += 1 oldmancaves = [] @@ -305,7 +291,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No player = region.player location_id = SHOP_ID_START + total_shop_slots + index - main_entrance = get_entrance_to_region(region) + main_entrance = region.get_connecting_entrance(is_main_entrance) if main_entrance.parent_region.type == RegionType.LightWorld: checks_in_area[player]["Light World"].append(location_id) else: @@ -340,7 +326,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for player, world_precollected in world.precollected_items.items()} precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))} - for slot in world.player_ids: slot_data[slot] = world.worlds[slot].fill_slot_data() diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 959bc858..db72ca6a 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -240,6 +240,11 @@ class World(metaclass=AutoWorldRegister): """Fill in the slot_data field in the Connected network package.""" return {} + def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): + """Fill in additional entrance information text into locations, which is displayed when hinted. + structure is {player_id: {location_id: text}} You will need to insert your own player_id.""" + pass + def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata? """For deeper modification of server multidata.""" pass diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index 80c4767d..5f8bd0a4 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -4,6 +4,10 @@ import typing from BaseClasses import Region, Entrance, RegionType +def is_main_entrance(entrance: Entrance) -> bool: + return entrance.parent_region.type in {RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic} + + def create_regions(world, player): world.regions += [ diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 2aeeec39..bbdd9411 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -12,7 +12,8 @@ from .InvertedRegions import create_inverted_regions, mark_dark_world_regions from .ItemPool import generate_itempool, difficulties from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem from .Options import alttp_options, smallkey_shuffle -from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions +from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \ + is_main_entrance from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \ get_hash_string, get_base_rom_path, LttPDeltaPatch from .Rules import set_rules @@ -24,6 +25,7 @@ lttp_logger = logging.getLogger("A Link to the Past") extras_list = sum(difficulties['normal'].extras[0:5], []) + class ALTTPWeb(WebWorld): setup_en = Tutorial( "Multiworld Setup Tutorial", @@ -410,6 +412,20 @@ class ALTTPWorld(World): finally: self.rom_name_available_event.set() # make sure threading continues and errors are collected + @classmethod + def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]): + er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if + world.shuffle[player] != "vanilla" or world.retro_caves[player]} + + for region in world.regions: + if region.player in er_hint_data and region.locations: + main_entrance = region.get_connecting_entrance(is_main_entrance) + for location in region.locations: + if type(location.address) == int: # skips events and crystals + if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: + er_hint_data[region.player][location.address] = main_entrance.name + hint_data.update(er_hint_data) + def modify_multidata(self, multidata: dict): import base64 # wait for self.rom_name to be available.