def from_json(cls, value: dict) -> "TranslatorConfiguration": default = cls.default() params = copy.copy(value) translator_requirement = copy.copy(default.translator_requirement) for key, item in params.pop("translator_requirement").items(): translator_requirement[NodeIdentifier.from_string(key)] = LayoutTranslatorRequirement(item) return cls(translator_requirement, **params)
async def test_add_default_hints_to_patches(echoes_game_description, empty_patches, is_multiworld): # Setup layout_configuration = MagicMock() layout_configuration.game = RandovaniaGame.METROID_PRIME_ECHOES rng = MagicMock() hint_distributor = EchoesHintDistributor() def _light_suit_location_hint(number: int): return Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.LIGHT_SUIT_LOCATION, HintItemPrecision.DETAILED, include_owner=False), PickupIndex(number)) def _guardian_hint(number: int): return Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.GUARDIAN, HintItemPrecision.DETAILED, include_owner=False), PickupIndex(number)) def _keybearer_hint(number: int): return Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.KEYBEARER, HintItemPrecision.BROAD_CATEGORY, include_owner=True), PickupIndex(number)) expected = { # Keybearer 'Temple Grounds/Landing Site/Keybearer Corpse (M-Dhe)': _keybearer_hint(11), 'Temple Grounds/Industrial Site/Keybearer Corpse (J-Fme)': _keybearer_hint(15), 'Temple Grounds/Storage Cavern A/Keybearer Corpse (D-Isl)': _keybearer_hint(19), # Agon 'Agon Wastes/Central Mining Station/Keybearer Corpse (J-Stl)': _keybearer_hint(45), 'Agon Wastes/Main Reactor/Keybearer Corpse (B-Stl)': _keybearer_hint(53), # Torvus 'Torvus Bog/Torvus Lagoon/Keybearer Corpse (S-Dly)': _keybearer_hint(68), 'Torvus Bog/Catacombs/Keybearer Corpse (G-Sch)': _keybearer_hint(91), # Sanctuary 'Sanctuary Fortress/Sanctuary Entrance/Keybearer Corpse (S-Jrs)': _keybearer_hint(117), 'Sanctuary Fortress/Dynamo Works/Keybearer Corpse (C-Rch)': _keybearer_hint(106), # Locations with hints 'Sanctuary Fortress/Sanctuary Energy Controller/Lore Scan': _light_suit_location_hint(24), 'Sanctuary Fortress/Main Gyro Chamber/Lore Scan': _guardian_hint(43), 'Sanctuary Fortress/Watch Station/Lore Scan': _guardian_hint(79), 'Sanctuary Fortress/Main Research/Lore Scan': _guardian_hint(115), # Dark Temple hints 'Sanctuary Fortress/Hall of Combat Mastery/Lore Scan': Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=HintDarkTemple.AGON_WASTES), 'Sanctuary Fortress/Sanctuary Entrance/Lore Scan': Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=HintDarkTemple.TORVUS_BOG), 'Torvus Bog/Catacombs/Lore Scan': Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=HintDarkTemple.SANCTUARY_FORTRESS), # Jokes 'Torvus Bog/Gathering Hall/Lore Scan': Hint(HintType.JOKE, None), 'Torvus Bog/Training Chamber/Lore Scan': Hint(HintType.JOKE, None), } expected = { NodeIdentifier.from_string(ident_s): hint for ident_s, hint in expected.items() } # Run result = await hint_distributor.assign_pre_filler_hints( empty_patches, prefill=PreFillParams( rng=rng, configuration=layout_configuration, game=echoes_game_description, is_multiworld=is_multiworld, ), ) # Assert rng.shuffle.assert_has_calls([call(ANY), call(ANY)]) assert result.hints == expected
def apply_previous_state(self, previous_state: Optional[dict]) -> bool: if previous_state is None: return False starting_location = None needs_starting_location = len( self.game_configuration.starting_location.locations) > 1 configurable_nodes = {} try: pickup_name_to_pickup = { pickup.name: pickup for pickup in self._collected_pickups.keys() } quantity_to_change = { pickup_name_to_pickup[pickup_name]: quantity for pickup_name, quantity in previous_state["collected_pickups"].items() } previous_actions = [ self.game_description.world_list.node_by_identifier( NodeIdentifier.from_string(identifier)) for identifier in previous_state["actions"] ] if needs_starting_location: starting_location = AreaIdentifier.from_json( previous_state["starting_location"]) teleporters: Dict[NodeIdentifier, Optional[AreaIdentifier]] = { NodeIdentifier.from_json(item["teleporter"]): (AreaIdentifier.from_json(item["data"]) if item["data"] is not None else None) for item in previous_state["elevators"] } if self.game_configuration.game == RandovaniaGame.METROID_PRIME_ECHOES: configurable_nodes = { NodeIdentifier.from_string(identifier): (LayoutTranslatorRequirement(item) if item is not None else None) for identifier, item in previous_state["configurable_nodes"].items() } except (KeyError, AttributeError): return False self.setup_starting_location(starting_location) for teleporter, area_location in teleporters.items(): combo = self._elevator_id_to_combo[teleporter] if area_location is None: combo.setCurrentIndex(0) continue for i in range(combo.count()): if area_location == combo.itemData(i): combo.setCurrentIndex(i) break for identifier, requirement in configurable_nodes.items(): combo = self._translator_gate_to_combo[identifier] for i in range(combo.count()): if requirement == combo.itemData(i): combo.setCurrentIndex(i) break self.bulk_change_quantity(quantity_to_change) self._add_new_actions(previous_actions) world_list = self.game_description.world_list state = self.state_for_current_configuration() self.focus_on_world(world_list.nodes_to_world(state.node)) self.focus_on_area(world_list.nodes_to_area(state.node)) return True
def decode_single(player_index: int, all_pools: Dict[int, PoolResults], game: GameDescription, game_modifications: dict, configuration: BaseConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param player_index: :param all_pools: :param game: :param game_modifications: :param configuration: :return: """ world_list = game.world_list weakness_db = game.dock_weakness_database if game_modifications["game"] != game.game.value: raise ValueError( f"Expected '{game.game.value}', got '{game_modifications['game']}'" ) initial_pickup_assignment = all_pools[player_index].assignment # Starting Location starting_location = AreaIdentifier.from_string( game_modifications["starting_location"]) # Initial items starting_items = { find_resource_info_with_long_name(game.resource_database.item, resource_name): quantity for resource_name, quantity in game_modifications["starting_items"].items() } # Elevators elevator_connection: ElevatorConnection = { NodeIdentifier.from_string(source_name): AreaIdentifier.from_string(target_name) for source_name, target_name in game_modifications["teleporters"].items() } # Dock Weakness dock_weakness: dict[NodeIdentifier, DockWeakness] = { NodeIdentifier.from_string(source_name): weakness_db.get_by_weakness( weakness_data["type"], weakness_data["name"], ) for source_name, weakness_data in game_modifications["dock_weakness"].items() } # Configurable Nodes configurable_nodes = { NodeIdentifier.from_string(identifier): data_reader.read_requirement(requirement, game.resource_database) for identifier, requirement in game_modifications["configurable_nodes"].items() } # Pickups target_name_re = re.compile(r"(.*) for Player (\d+)") pickup_assignment: PickupAssignment = {} for world_name, world_data in game_modifications["locations"].items(): for area_node_name, target_name in typing.cast(dict[str, str], world_data).items(): if target_name == _ETM_NAME: continue pickup_name_match = target_name_re.match(target_name) if pickup_name_match is not None: pickup_name = pickup_name_match.group(1) target_player = int(pickup_name_match.group(2)) - 1 else: pickup_name = target_name target_player = 0 node_identifier = NodeIdentifier.create( world_name, *area_node_name.split("/", 1)) node = world_list.node_by_identifier(node_identifier) assert isinstance(node, PickupNode) if node.pickup_index in initial_pickup_assignment: pickup = initial_pickup_assignment[node.pickup_index] if (pickup_name, target_player) != (pickup.name, player_index): raise ValueError( f"{area_node_name} should be vanilla based on configuration" ) elif pickup_name != _ETM_NAME: configuration_item_pool = all_pools[target_player].pickups pickup = _find_pickup_with_name(configuration_item_pool, pickup_name) configuration_item_pool.remove(pickup) else: pickup = None if pickup is not None: pickup_assignment[node.pickup_index] = PickupTarget( pickup, target_player) # Hints hints = {} for identifier_str, hint in game_modifications["hints"].items(): hints[NodeIdentifier.from_string(identifier_str)] = Hint.from_json( hint) return GamePatches( player_index=player_index, configuration=configuration, pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # ElevatorConnection dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness=dock_weakness, configurable_nodes=configurable_nodes, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaIdentifier hints=hints, )
def _patches_with_data(request, echoes_game_description, echoes_game_patches, echoes_item_database): game = echoes_game_description db = game.resource_database data = { "game": echoes_game_description.game.value, "starting_location": "Temple Grounds/Landing Site", "starting_items": {}, "teleporters": { "Temple Grounds/Temple Transport C/Elevator to Great Temple - Temple Transport C": "Great Temple/Temple Transport C", "Temple Grounds/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Temple Grounds": "Agon Wastes/Transport to Temple Grounds", "Temple Grounds/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Temple Grounds": "Torvus Bog/Transport to Temple Grounds", "Temple Grounds/Temple Transport B/Elevator to Great Temple - Temple Transport B": "Great Temple/Temple Transport B", "Temple Grounds/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Temple Grounds": "Sanctuary Fortress/Transport to Temple Grounds", "Temple Grounds/Temple Transport A/Elevator to Great Temple - Temple Transport A": "Great Temple/Temple Transport A", "Great Temple/Temple Transport A/Elevator to Temple Grounds - Temple Transport A": "Temple Grounds/Temple Transport A", "Great Temple/Temple Transport C/Elevator to Temple Grounds - Temple Transport C": "Temple Grounds/Temple Transport C", "Great Temple/Temple Transport B/Elevator to Temple Grounds - Temple Transport B": "Temple Grounds/Temple Transport B", "Temple Grounds/Sky Temple Gateway/Teleport to Great Temple - Sky Temple Energy Controller": "Great Temple/Sky Temple Energy Controller", "Great Temple/Sky Temple Energy Controller/Teleport to Temple Grounds - Sky Temple Gateway": "Temple Grounds/Sky Temple Gateway", "Agon Wastes/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Agon Wastes": "Temple Grounds/Transport to Agon Wastes", "Agon Wastes/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Agon Wastes": "Torvus Bog/Transport to Agon Wastes", "Agon Wastes/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Agon Wastes": "Sanctuary Fortress/Transport to Agon Wastes", "Torvus Bog/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Torvus Bog": "Temple Grounds/Transport to Torvus Bog", "Torvus Bog/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Torvus Bog": "Agon Wastes/Transport to Torvus Bog", "Torvus Bog/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Torvus Bog": "Sanctuary Fortress/Transport to Torvus Bog", "Sanctuary Fortress/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Sanctuary Fortress": "Temple Grounds/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Sanctuary Fortress": "Agon Wastes/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Sanctuary Fortress": "Torvus Bog/Transport to Sanctuary Fortress", "Sanctuary Fortress/Aerie/Elevator to Sanctuary Fortress - Aerie Transport Station": "Sanctuary Fortress/Aerie Transport Station", "Sanctuary Fortress/Aerie Transport Station/Elevator to Sanctuary Fortress - Aerie": "Sanctuary Fortress/Aerie", }, "dock_weakness": {}, "configurable_nodes": {}, "locations": {}, "hints": {} } patches = dataclasses.replace(echoes_game_patches, player_index=0) locations = collections.defaultdict(dict) for world, area, node in game.world_list.all_worlds_areas_nodes: if node.is_resource_node and isinstance(node, PickupNode): world_name = world.dark_name if area.in_dark_aether else world.name locations[world_name][game.world_list.node_name( node)] = game_patches_serializer._ETM_NAME data["locations"] = { world: {area: item for area, item in sorted(locations[world].items())} for world in sorted(locations.keys()) } if request.param.get("starting_item"): item_name = request.param.get("starting_item") patches = patches.assign_extra_initial_items([ (db.get_item_by_name(item_name), 1), ]) data["starting_items"][item_name] = 1 if request.param.get("elevator"): teleporter: NodeIdentifier = request.param.get("elevator") patches = patches.assign_elevators([ (game.world_list.get_teleporter_node(teleporter), game.starting_location), ]) data["teleporters"][ teleporter.as_string] = "Temple Grounds/Landing Site" if request.param.get("configurable_nodes"): gates = [] for identifier, translator in request.param.get("configurable_nodes"): requirement = ResourceRequirement.simple(db.get_item(translator)) gates.append((NodeIdentifier.from_string(identifier), requirement)) data["configurable_nodes"][ identifier] = data_writer.write_requirement(requirement) patches = patches.assign_node_configuration(gates) if request.param.get("pickup"): pickup_name = request.param.get("pickup") pickup = pickup_creator.create_major_item( echoes_item_database.major_items[pickup_name], MajorItemState(), True, game.resource_database, None, False) patches = patches.assign_new_pickups([(PickupIndex(5), PickupTarget(pickup, 0))]) data["locations"]["Temple Grounds"][ 'Transport to Agon Wastes/Pickup (Missile)'] = pickup_name if request.param.get("hint"): identifier, hint = request.param.get("hint") patches = patches.assign_hint(NodeIdentifier.from_string(identifier), Hint.from_json(hint)) data["hints"][identifier] = hint return data, patches