Core: add generic interface to add ER data to hints (#1014)

This commit is contained in:
Fabian Dill 2022-09-18 14:30:43 +02:00 committed by GitHub
parent 58f66e0f42
commit c2d69cb05e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 35 deletions

View File

@ -955,6 +955,13 @@ class Region:
return True return True
return False 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): def __repr__(self):
return self.__str__() return self.__str__()

29
Main.py
View File

@ -12,7 +12,7 @@ from typing import Dict, Tuple, Optional, Set
from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location
from worlds.alttp.Items import item_name_groups 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 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 worlds.alttp.Shops import SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
from Utils import output_path, get_options, __version__, version_tuple 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( output_file_futures.append(
pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir)) 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 # collect ER hint info
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if er_hint_data: Dict[int, Dict[int, str]] = {}
world.shuffle[player] != "vanilla" or world.retro_caves[player]} AutoWorld.call_all(world, 'extend_hint_information', er_hint_data)
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
checks_in_area = {player: {area: list() for area in ordered_areas} checks_in_area = {player: {area: list() for area in ordered_areas}
for player in range(1, world.players + 1)} for player in range(1, world.players + 1)}
@ -276,10 +261,11 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
for location in world.get_filled_locations(): for location in world.get_filled_locations():
if type(location.address) is int: if type(location.address) is int:
main_entrance = get_entrance_to_region(location.parent_region)
if location.game != "A Link to the Past": if location.game != "A Link to the Past":
checks_in_area[location.player]["Light World"].append(location.address) checks_in_area[location.player]["Light World"].append(location.address)
elif location.parent_region.dungeon: else:
main_entrance = location.parent_region.get_connecting_entrance(is_main_entrance)
if location.parent_region.dungeon:
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
'Inverted Ganons Tower': 'Ganons Tower'} \ 'Inverted Ganons Tower': 'Ganons Tower'} \
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
@ -305,7 +291,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
player = region.player player = region.player
location_id = SHOP_ID_START + total_shop_slots + index 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: if main_entrance.parent_region.type == RegionType.LightWorld:
checks_in_area[player]["Light World"].append(location_id) checks_in_area[player]["Light World"].append(location_id)
else: 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()} for player, world_precollected in world.precollected_items.items()}
precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))} precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))}
for slot in world.player_ids: for slot in world.player_ids:
slot_data[slot] = world.worlds[slot].fill_slot_data() slot_data[slot] = world.worlds[slot].fill_slot_data()

View File

@ -240,6 +240,11 @@ class World(metaclass=AutoWorldRegister):
"""Fill in the slot_data field in the Connected network package.""" """Fill in the slot_data field in the Connected network package."""
return {} 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? def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata?
"""For deeper modification of server multidata.""" """For deeper modification of server multidata."""
pass pass

View File

@ -4,6 +4,10 @@ import typing
from BaseClasses import Region, Entrance, RegionType 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): def create_regions(world, player):
world.regions += [ world.regions += [

View File

@ -12,7 +12,8 @@ from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
from .ItemPool import generate_itempool, difficulties from .ItemPool import generate_itempool, difficulties
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
from .Options import alttp_options, smallkey_shuffle 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, \ from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
get_hash_string, get_base_rom_path, LttPDeltaPatch get_hash_string, get_base_rom_path, LttPDeltaPatch
from .Rules import set_rules 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], []) extras_list = sum(difficulties['normal'].extras[0:5], [])
class ALTTPWeb(WebWorld): class ALTTPWeb(WebWorld):
setup_en = Tutorial( setup_en = Tutorial(
"Multiworld Setup Tutorial", "Multiworld Setup Tutorial",
@ -410,6 +412,20 @@ class ALTTPWorld(World):
finally: finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected 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): def modify_multidata(self, multidata: dict):
import base64 import base64
# wait for self.rom_name to be available. # wait for self.rom_name to be available.