def test_add_hints_precision(empty_patches, mocker): failed_relative_provider = MagicMock(return_value=None) relative_hint_provider = MagicMock() mocker.patch( "randovania.generator.filler.runner._get_relative_hint_providers", return_value=[failed_relative_provider, relative_hint_provider]) player_state = MagicMock() rng = MagicMock() hints = [ Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.DETAILED, HintItemPrecision.DETAILED, include_owner=False), PickupIndex(1)), Hint(HintType.LOCATION, None, PickupIndex(2)), Hint(HintType.LOCATION, None, PickupIndex(3)), ] initial_patches = empty_patches for i, hint in enumerate(hints): initial_patches = initial_patches.assign_hint(LogbookAsset(i), hint) # Run result = runner.add_hints_precision(player_state, initial_patches, rng) # Assert failed_relative_provider.assert_called_once_with(player_state, initial_patches, rng, PickupIndex(2)) relative_hint_provider.assert_called_once_with(player_state, initial_patches, rng, PickupIndex(3)) assert result.hints == { LogbookAsset(0): Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.DETAILED, HintItemPrecision.DETAILED, include_owner=False), PickupIndex(1)), LogbookAsset(1): Hint( HintType.LOCATION, PrecisionPair(HintLocationPrecision.WORLD_ONLY, HintItemPrecision.PRECISE_CATEGORY, include_owner=False), PickupIndex(2)), LogbookAsset(2): relative_hint_provider.return_value, }
class MissingRng(Exception): pass # M-Dhe -> E3B417BF -> Temple Grounds - Landing Site -> Defiled Shrine -> 11 # J-Fme -> 65206511 -> Temple Grounds - Industrial Site -> Accursed Lake -> 15 # D-Isl -> 28E8C41A -> Temple Grounds - Storage Cavern A -> Ing Reliquary -> 19 # J-Stl -> 150E8DB8 -> Agon Wastes - Central Mining Station -> Battleground -> 45 # B-Stl -> DE525E1D -> Agon Wastes - Main Reactor -> Dark Oasis -> 53 # S-Dly -> 58C62CB3 -> Torvus Bog - Torvus Lagoon -> Poisoned Bog -> 68 # G-Sch -> 939AFF16 -> Torvus Bog - Catacombs -> Dungeon -> 91 # C-Rch -> A9909E66 -> Sanctuary Fortress - Dynamo Works -> Hive Dynamo Works -> 106 # S-Jrs -> 62CC4DC3 -> Sanctuary Fortress - Sanctuary Entrance -> Hive Entrance -> 117 _KEYBEARERS_HINTS = { LogbookAsset(0xDE525E1D): PickupIndex(53), LogbookAsset(0xA9909E66): PickupIndex(106), LogbookAsset(0x28E8C41A): PickupIndex(19), LogbookAsset(0x939AFF16): PickupIndex(91), LogbookAsset(0x65206511): PickupIndex(15), LogbookAsset(0x150E8DB8): PickupIndex(45), LogbookAsset(0xE3B417BF): PickupIndex(11), LogbookAsset(0x58C62CB3): PickupIndex(68), LogbookAsset(0x62CC4DC3): PickupIndex(117), } def add_elevator_connections_to_patches( layout_configuration: LayoutConfiguration, rng: Random, patches: GamePatches) -> GamePatches: """
def decode(game_modifications: dict, configuration: LayoutConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param game_modifications: :param configuration: :return: """ game = data_reader.decode_data(configuration.game_data) world_list = game.world_list # Starting Location starting_location = _area_name_to_area_location( world_list, 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 = {} for source_name, target_name in game_modifications["elevators"].items(): source_area = _area_name_to_area_location(world_list, source_name) target_area = _area_name_to_area_location(world_list, target_name) potential_source_nodes = [ node for node in world_list.area_by_area_location(source_area).nodes if isinstance(node, TeleporterNode) ] assert len(potential_source_nodes) == 1 source_node = potential_source_nodes[0] elevator_connection[source_node.teleporter_instance_id] = target_area # Translator Gates translator_gates = { _find_gate_with_name(gate_name): find_resource_info_with_long_name(game.resource_database.item, resource_name) for gate_name, resource_name in game_modifications["translators"].items() } # Pickups pickup_assignment = {} decoder = BitPackDecoder( base64.b64decode( game_modifications["_locations_internal"].encode("utf-8"), validate=True)) for world_name, world_data in game_modifications["locations"].items(): for area_node_name, pickup_name in world_data.items(): if pickup_name == "Nothing": continue node = world_list.node_from_name(f"{world_name}/{area_node_name}") assert isinstance(node, PickupNode) pickup = BitPackPickupEntry.bit_pack_unpack( decoder, pickup_name, game.resource_database) pickup_assignment[node.pickup_index] = pickup # Hints hints = {} for asset_id, hint in game_modifications["hints"].items(): hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint) return GamePatches( pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # Dict[int, AreaLocation] dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness={}, # Dict[Tuple[int, int], DockWeakness] translator_gates=translator_gates, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaLocation hints=hints, )
def resource(self) -> ResourceInfo: return LogbookAsset(self.string_asset_id)
def decode_single(player_index: int, num_players: int, game_modifications: dict, configuration: LayoutConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param game_modifications: :param player_index: :param num_players: :param configuration: :return: """ game = data_reader.decode_data(configuration.game_data) game_specific = dataclasses.replace( game.game_specific, energy_per_tank=configuration.energy_per_tank, beam_configurations=configuration.beam_configuration.create_game_specific(game.resource_database)) world_list = game.world_list # Starting Location starting_location = _area_name_to_area_location(world_list, 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 = {} for source_name, target_name in game_modifications["elevators"].items(): source_area = _area_name_to_area_location(world_list, source_name) target_area = _area_name_to_area_location(world_list, target_name) potential_source_nodes = [ node for node in world_list.area_by_area_location(source_area).nodes if isinstance(node, TeleporterNode) ] assert len(potential_source_nodes) == 1 source_node = potential_source_nodes[0] elevator_connection[source_node.teleporter_instance_id] = target_area # Translator Gates translator_gates = { _find_gate_with_name(gate_name): find_resource_info_with_long_name(game.resource_database.item, resource_name) for gate_name, resource_name in game_modifications["translators"].items() } # Pickups target_name_re = re.compile(r"(.*) for Player \d+") index_to_pickup_name = {} for world_name, world_data in game_modifications["locations"].items(): for area_node_name, target_name in 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) else: pickup_name = target_name node = world_list.node_from_name(f"{world_name}/{area_node_name}") assert isinstance(node, PickupNode) index_to_pickup_name[node.pickup_index] = pickup_name decoder = BitPackDecoder(base64.b64decode(game_modifications["_locations_internal"].encode("utf-8"), validate=True)) pickup_assignment = dict(BitPackPickupEntryList.bit_pack_unpack(decoder, { "index_mapping": index_to_pickup_name, "num_players": num_players, "database": game.resource_database, }).value) # Hints hints = {} for asset_id, hint in game_modifications["hints"].items(): hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint) return GamePatches( player_index=player_index, pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # Dict[int, AreaLocation] dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness={}, # Dict[Tuple[int, int], DockWeakness] translator_gates=translator_gates, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaLocation hints=hints, game_specific=game_specific, )
def test_add_default_hints_to_patches(echoes_game_description, empty_patches): # Setup rng = MagicMock() def _light_suit_location_hint(number: int): return Hint(HintType.LIGHT_SUIT_LOCATION, PrecisionPair.detailed(), PickupIndex(number)) def _guardian_hint(number: int): return Hint(HintType.GUARDIAN, PrecisionPair.detailed(), PickupIndex(number)) def _keybearer_hint(number: int): return Hint( HintType.KEYBEARER, PrecisionPair(HintLocationPrecision.DETAILED, HintItemPrecision.PRECISE_CATEGORY), PickupIndex(number)) expected = { # Keybearer LogbookAsset(0xE3B417BF): _keybearer_hint(11), LogbookAsset(0x65206511): _keybearer_hint(15), LogbookAsset(0x28E8C41A): _keybearer_hint(19), # Agon LogbookAsset(0x150E8DB8): _keybearer_hint(45), LogbookAsset(0xDE525E1D): _keybearer_hint(53), # Torvus LogbookAsset(0x58C62CB3): _keybearer_hint(68), LogbookAsset(0x939AFF16): _keybearer_hint(91), # Sanctuary LogbookAsset(0x62CC4DC3): _keybearer_hint(117), LogbookAsset(0xA9909E66): _keybearer_hint(106), # Locations with hints LogbookAsset(1041207119): _light_suit_location_hint(24), LogbookAsset(4115881194): _guardian_hint(43), LogbookAsset(1948976790): _guardian_hint(79), LogbookAsset(3212301619): _guardian_hint(115), } # Run result = base_patches_factory.add_default_hints_to_patches( rng, empty_patches, echoes_game_description.world_list) # Assert rng.shuffle.assert_has_calls([call(ANY), call(ANY)]) assert result.hints == expected
def test_add_default_hints_to_patches(echoes_game_description, empty_patches, is_multiworld): # Setup rng = MagicMock() def _light_suit_location_hint(number: int): return Hint(HintType.LOCATION, PrecisionPair(HintLocationPrecision.LIGHT_SUIT_LOCATION, HintItemPrecision.DETAILED), PickupIndex(number)) def _guardian_hint(number: int): return Hint(HintType.LOCATION, PrecisionPair(HintLocationPrecision.GUARDIAN, HintItemPrecision.DETAILED), PickupIndex(number)) def _keybearer_hint(number: int): return Hint(HintType.LOCATION, PrecisionPair(HintLocationPrecision.KEYBEARER, HintItemPrecision.OWNER if is_multiworld else HintItemPrecision.BROAD_CATEGORY), PickupIndex(number)) expected = { # Keybearer LogbookAsset(0xE3B417BF): _keybearer_hint(11), LogbookAsset(0x65206511): _keybearer_hint(15), LogbookAsset(0x28E8C41A): _keybearer_hint(19), # Agon LogbookAsset(0x150E8DB8): _keybearer_hint(45), LogbookAsset(0xDE525E1D): _keybearer_hint(53), # Torvus LogbookAsset(0x58C62CB3): _keybearer_hint(68), LogbookAsset(0x939AFF16): _keybearer_hint(91), # Sanctuary LogbookAsset(0x62CC4DC3): _keybearer_hint(117), LogbookAsset(0xA9909E66): _keybearer_hint(106), # Locations with hints LogbookAsset(1041207119): _light_suit_location_hint(24), LogbookAsset(4115881194): _guardian_hint(43), LogbookAsset(1948976790): _guardian_hint(79), LogbookAsset(3212301619): _guardian_hint(115), # Dark Temple hints LogbookAsset(67497535): Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=HintDarkTemple.AGON_WASTES), LogbookAsset(4072633400): Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=HintDarkTemple.TORVUS_BOG), LogbookAsset(0x82919C91): Hint(HintType.RED_TEMPLE_KEY_SET, None, dark_temple=HintDarkTemple.SANCTUARY_FORTRESS), # Jokes LogbookAsset(0x49CD4F34): Hint(HintType.JOKE, None), LogbookAsset(0x9F94AC29): Hint(HintType.JOKE, None), } # Run result = base_patches_factory.add_echoes_default_hints_to_patches( rng, empty_patches, echoes_game_description.world_list, num_joke=2, is_multiworld=is_multiworld) # Assert rng.shuffle.assert_has_calls([call(ANY), call(ANY)]) assert result.hints == expected
def _patches_with_data(request, echoes_game_data, echoes_item_database): game = data_reader.decode_data(echoes_game_data) data = { "starting_location": "Temple Grounds/Landing Site", "starting_items": {}, "elevators": { "Temple Grounds/Temple Transport C": "Great Temple/Temple Transport C", "Temple Grounds/Transport to Agon Wastes": "Agon Wastes/Transport to Temple Grounds", "Temple Grounds/Transport to Torvus Bog": "Torvus Bog/Transport to Temple Grounds", "Temple Grounds/Temple Transport B": "Great Temple/Temple Transport B", "Temple Grounds/Transport to Sanctuary Fortress": "Sanctuary Fortress/Transport to Temple Grounds", "Temple Grounds/Temple Transport A": "Great Temple/Temple Transport A", "Great Temple/Temple Transport A": "Temple Grounds/Temple Transport A", "Great Temple/Temple Transport C": "Temple Grounds/Temple Transport C", "Great Temple/Temple Transport B": "Temple Grounds/Temple Transport B", "Sky Temple Grounds/Sky Temple Gateway": "Sky Temple/Sky Temple Energy Controller", "Sky Temple/Sky Temple Energy Controller": "Sky Temple Grounds/Sky Temple Gateway", "Agon Wastes/Transport to Temple Grounds": "Temple Grounds/Transport to Agon Wastes", "Agon Wastes/Transport to Torvus Bog": "Torvus Bog/Transport to Agon Wastes", "Agon Wastes/Transport to Sanctuary Fortress": "Sanctuary Fortress/Transport to Agon Wastes", "Torvus Bog/Transport to Temple Grounds": "Temple Grounds/Transport to Torvus Bog", "Torvus Bog/Transport to Agon Wastes": "Agon Wastes/Transport to Torvus Bog", "Torvus Bog/Transport to Sanctuary Fortress": "Sanctuary Fortress/Transport to Torvus Bog", "Sanctuary Fortress/Transport to Temple Grounds": "Temple Grounds/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Agon Wastes": "Agon Wastes/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Torvus Bog": "Torvus Bog/Transport to Sanctuary Fortress", "Sanctuary Fortress/Aerie": "Sanctuary Fortress/Aerie Transport Station", "Sanctuary Fortress/Aerie Transport Station": "Sanctuary Fortress/Aerie", }, "translators": {}, "locations": {}, "hints": {}, "_locations_internal": "", } patches = game.create_game_patches() 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({ find_resource_info_with_long_name(game.resource_database.item, item_name): 1, }) data["starting_items"][item_name] = 1 if request.param.get("elevator"): elevator_id, elevator_source = request.param.get("elevator") elevator_connection = copy.copy(patches.elevator_connection) elevator_connection[elevator_id] = game.starting_location patches = dataclasses.replace(patches, elevator_connection=elevator_connection) data["elevators"][elevator_source] = "Temple Grounds/Landing Site" if request.param.get("translator"): gates = {} for index, gate_name, translator in request.param.get("translator"): gates[TranslatorGate(index)] = find_resource_info_with_long_name(game.resource_database.item, translator) data["translators"][gate_name] = translator patches = patches.assign_gate_assignment(gates) if request.param.get("pickup"): data["_locations_internal"], 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), pickup)]) data["locations"]["Temple Grounds"]['Transport to Agon Wastes/Pickup (Missile)'] = pickup_name if request.param.get("hint"): asset, hint = request.param.get("hint") patches = patches.assign_hint(LogbookAsset(asset), Hint.from_json(hint)) data["hints"][str(asset)] = hint return data, patches
def decode_single(player_index: int, all_pools: Dict[int, PoolResults], game: GameDescription, game_modifications: dict, configuration: LayoutConfiguration) -> 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: """ game_specific = base_patches_factory.create_game_specific( configuration, game) world_list = game.world_list initial_pickup_assignment = all_pools[player_index].assignment # Starting Location starting_location = _area_name_to_area_location( world_list, 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 = {} for source_name, target_name in game_modifications["elevators"].items(): source_area = _area_name_to_area_location(world_list, source_name) target_area = _area_name_to_area_location(world_list, target_name) potential_source_nodes = [ node for node in world_list.area_by_area_location(source_area).nodes if isinstance(node, TeleporterNode) ] assert len(potential_source_nodes) == 1 source_node = potential_source_nodes[0] elevator_connection[source_node.teleporter_instance_id] = target_area # Translator Gates translator_gates = { _find_gate_with_name(gate_name): find_resource_info_with_long_name(game.resource_database.item, resource_name) for gate_name, resource_name in game_modifications["translators"].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 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)) else: pickup_name = target_name target_player = 0 node = world_list.node_from_name(f"{world_name}/{area_node_name}") 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 asset_id, hint in game_modifications["hints"].items(): hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint) return GamePatches( player_index=player_index, pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # Dict[int, AreaLocation] dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness={}, # Dict[Tuple[int, int], DockWeakness] translator_gates=translator_gates, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaLocation hints=hints, game_specific=game_specific, )
def _patches_with_data(request, echoes_game_data, echoes_item_database): game = data_reader.decode_data(echoes_game_data) data = { "starting_location": "Temple Grounds/Landing Site", "starting_items": {}, "elevators": { "Temple Grounds/Temple Transport C": "Great Temple/Temple Transport C", "Temple Grounds/Transport to Agon Wastes": "Agon Wastes/Transport to Temple Grounds", "Temple Grounds/Transport to Torvus Bog": "Torvus Bog/Transport to Temple Grounds", "Temple Grounds/Temple Transport B": "Great Temple/Temple Transport B", "Temple Grounds/Sky Temple Gateway": "Great Temple/Sky Temple Energy Controller", "Temple Grounds/Transport to Sanctuary Fortress": "Sanctuary Fortress/Transport to Temple Grounds", "Temple Grounds/Temple Transport A": "Great Temple/Temple Transport A", "Great Temple/Temple Transport A": "Temple Grounds/Temple Transport A", "Great Temple/Temple Transport C": "Temple Grounds/Temple Transport C", "Great Temple/Temple Transport B": "Temple Grounds/Temple Transport B", "Great Temple/Sky Temple Energy Controller": "Temple Grounds/Sky Temple Gateway", "Agon Wastes/Transport to Temple Grounds": "Temple Grounds/Transport to Agon Wastes", "Agon Wastes/Transport to Torvus Bog": "Torvus Bog/Transport to Agon Wastes", "Agon Wastes/Transport to Sanctuary Fortress": "Sanctuary Fortress/Transport to Agon Wastes", "Torvus Bog/Transport to Temple Grounds": "Temple Grounds/Transport to Torvus Bog", "Torvus Bog/Transport to Agon Wastes": "Agon Wastes/Transport to Torvus Bog", "Torvus Bog/Transport to Sanctuary Fortress": "Sanctuary Fortress/Transport to Torvus Bog", "Sanctuary Fortress/Transport to Temple Grounds": "Temple Grounds/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Agon Wastes": "Agon Wastes/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Torvus Bog": "Torvus Bog/Transport to Sanctuary Fortress" }, "translators": {}, "locations": { world.name: { game.world_list.node_name(node): "Nothing" for node in world.all_nodes if node.is_resource_node and isinstance(node, PickupNode) } for world in sorted(game.world_list.worlds, key=lambda w: w.name) }, "hints": {}, "_locations_internal": "", } patches = GamePatches.with_game(game) if request.param.get("starting_item"): item_name = request.param.get("starting_item") patches = patches.assign_extra_initial_items({ find_resource_info_with_long_name(game.resource_database.item, item_name): 1, }) data["starting_items"][item_name] = 1 if request.param.get("elevator"): elevator_id, elevator_source = request.param.get("elevator") elevator_connection = copy.copy(patches.elevator_connection) elevator_connection[elevator_id] = game.starting_location patches = dataclasses.replace(patches, elevator_connection=elevator_connection) data["elevators"][elevator_source] = "Temple Grounds/Landing Site" if request.param.get("translator"): gates = {} for index, gate_name, translator in request.param.get("translator"): gates[TranslatorGate(index)] = find_resource_info_with_long_name( game.resource_database.item, translator) data["translators"][gate_name] = translator patches = patches.assign_gate_assignment(gates) if request.param.get("pickup"): data["_locations_internal"], 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), pickup)]) data["locations"]["Temple Grounds"][ 'Transport to Agon Wastes/Pickup (Missile)'] = pickup_name if request.param.get("hint"): asset, hint = request.param.get("hint") patches = patches.assign_hint(LogbookAsset(asset), Hint.from_json(hint)) data["hints"][str(asset)] = hint return data, patches