def test_gate_assignment_for_configuration_all_emerald(echoes_game_description, default_echoes_configuration): # Setup patches_factory = echoes_game_description.game.generator.base_patches_factory scan_visor = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Scan Visor") emerald = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Emerald Translator") translator_configuration = default_echoes_configuration.translator_configuration configuration = dataclasses.replace( default_echoes_configuration, translator_configuration=dataclasses.replace( translator_configuration, translator_requirement={ key: LayoutTranslatorRequirement.EMERALD for key in translator_configuration.translator_requirement.keys() } ) ) rng = MagicMock() # Run results = patches_factory.configurable_node_assignment( configuration, echoes_game_description, rng) # Assert assert list(results.values()) == [ RequirementAnd([ ResourceRequirement(scan_visor, 1, False), ResourceRequirement(emerald, 1, False), ]) ] * len(translator_configuration.translator_requirement)
def test_gate_assignment_for_configuration_all_random( echoes_resource_database): # Setup violet = find_resource_info_with_long_name(echoes_resource_database.item, "Violet Translator") emerald = find_resource_info_with_long_name(echoes_resource_database.item, "Emerald Translator") configuration = MagicMock() configuration.translator_configuration.translator_requirement = { TranslatorGate(index): LayoutTranslatorRequirement.RANDOM for index in [1, 15, 23] } rng = MagicMock() rng.choice.side_effect = [ LayoutTranslatorRequirement.EMERALD, LayoutTranslatorRequirement.VIOLET, LayoutTranslatorRequirement.EMERALD, ] # Run results = base_patches_factory.gate_assignment_for_configuration( configuration, echoes_resource_database, rng) # Assert assert results == { TranslatorGate(1): emerald, TranslatorGate(15): violet, TranslatorGate(23): emerald, }
def test_gate_assignment_for_configuration_all_random(echoes_game_description, default_echoes_configuration): # Setup patches_factory = echoes_game_description.game.generator.base_patches_factory scan_visor = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Scan Visor") violet = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Violet Translator") emerald = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Emerald Translator") translator_configuration = default_echoes_configuration.translator_configuration configuration = dataclasses.replace( default_echoes_configuration, translator_configuration=translator_configuration.with_full_random(), ) requirements = [ RequirementAnd([ ResourceRequirement(scan_visor, 1, False), ResourceRequirement(emerald, 1, False), ]), RequirementAnd([ ResourceRequirement(scan_visor, 1, False), ResourceRequirement(violet, 1, False), ]) ] requirements = requirements * len(translator_configuration.translator_requirement) choices = [LayoutTranslatorRequirement.EMERALD, LayoutTranslatorRequirement.VIOLET] rng = MagicMock() rng.choice.side_effect = choices * len(translator_configuration.translator_requirement) # Run results = patches_factory.configurable_node_assignment( configuration, echoes_game_description, rng) # Assert assert list(results.values()) == requirements[:len(translator_configuration.translator_requirement)]
def _get_command_visor(self): try: return find_resource_info_with_long_name( self.game.resource_database.item, "Command Visor" ) except MissingResource: return None
def create_tracker(self, game_enum: RandovaniaGame): effective_theme = self.theme if not path_for(game_enum, effective_theme).is_file(): effective_theme = TrackerTheme.CLASSIC if (game_enum, effective_theme) == self._last_tracker_config: return self.delete_tracker() resource_database = default_database.resource_database_for(game_enum) with path_for(game_enum, effective_theme).open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) for element in tracker_details["elements"]: text_template = "" if "image_path" in element: image_path = get_data_path().joinpath(element["image_path"]) pixmap = QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) elif "label" in element: label = QLabel(self.inventory_group) label.setAlignment(Qt.AlignCenter) text_template = element["label"] else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] self._tracker_elements.append( Element(label, resources, text_template)) self.inventory_layout.addWidget(label, element["row"], element["column"]) self.inventory_spacer = QSpacerItem(5, 5, QSizePolicy.Expanding, QSizePolicy.Expanding) self.inventory_layout.addItem(self.inventory_spacer, self.inventory_layout.rowCount(), self.inventory_layout.columnCount()) self._update_tracker_from_hook({}) self._last_tracker_config = (game_enum, effective_theme)
def configurable_node_assignment( self, configuration: EchoesConfiguration, game: GameDescription, rng: Random) -> Iterator[NodeConfigurationAssociation]: """ :param configuration: :param game: :param rng: :return: """ all_choices = list(LayoutTranslatorRequirement) all_choices.remove(LayoutTranslatorRequirement.RANDOM) all_choices.remove(LayoutTranslatorRequirement.RANDOM_WITH_REMOVED) without_removed = copy.copy(all_choices) without_removed.remove(LayoutTranslatorRequirement.REMOVED) random_requirements = { LayoutTranslatorRequirement.RANDOM, LayoutTranslatorRequirement.RANDOM_WITH_REMOVED } result = [] scan_visor = search.find_resource_info_with_long_name( game.resource_database.item, "Scan Visor") scan_visor_req = ResourceRequirement.simple(scan_visor) for node in game.world_list.iterate_nodes(): if not isinstance(node, ConfigurableNode): continue identifier = game.world_list.identifier_for_node(node) requirement = configuration.translator_configuration.translator_requirement[ identifier] if requirement in random_requirements: if rng is None: raise MissingRng("Translator") requirement = rng.choice( all_choices if requirement == LayoutTranslatorRequirement. RANDOM_WITH_REMOVED else without_removed) translator = game.resource_database.get_by_type_and_index( ResourceType.ITEM, requirement.item_name) result.append((identifier, RequirementAnd([ scan_visor_req, ResourceRequirement.simple(translator), ]))) return result
def list_paths_with_resource_logic(args): gd = load_game_description(args) resource = None for resource_type in gd.resource_database: try: resource = find_resource_info_with_long_name( resource_type, args.resource) break except MissingResource: continue if resource is None: print(f"A resource named {args.resource} was not found.") raise SystemExit(1) _list_paths_with_resource(gd, args.print_only_area, resource, None)
def test_gate_assignment_for_configuration_all_emerald( echoes_resource_database): # Setup emerald = find_resource_info_with_long_name(echoes_resource_database.item, "Emerald Translator") indices = [1, 15, 23] configuration = MagicMock() configuration.translator_configuration.translator_requirement = { TranslatorGate(index): LayoutTranslatorRequirement.EMERALD for index in indices } rng = MagicMock() # Run results = base_patches_factory.gate_assignment_for_configuration( configuration, echoes_resource_database, rng) # Assert assert results == {TranslatorGate(index): emerald for index in indices}
def test_bit_pack_pickup_entry(has_convert: bool, echoes_resource_database): # Setup name = "Some Random Name" if has_convert: convert_resources = (ResourceConversion( find_resource_info_with_long_name(echoes_resource_database.item, "Morph Ball"), find_resource_info_with_long_name(echoes_resource_database.item, "Item Percentage")), ) else: convert_resources = () pickup = PickupEntry( name=name, model_index=26, item_category=ItemCategory.TEMPLE_KEY, broad_category=ItemCategory.KEY, resources=(ConditionalResources( "Morph Ball", None, ( (find_resource_info_with_long_name( echoes_resource_database.item, "Morph Ball"), 2), (find_resource_info_with_long_name( echoes_resource_database.item, "Item Percentage"), 5), ), ), ConditionalResources( "Grapple Beam", find_resource_info_with_long_name( echoes_resource_database.item, "Morph Ball"), ((find_resource_info_with_long_name( echoes_resource_database.item, "Grapple Beam"), 3), ), )), convert_resources=convert_resources) # Run encoded = bitpacking.pack_value( BitPackPickupEntry(pickup, echoes_resource_database)) decoder = BitPackDecoder(encoded) decoded = BitPackPickupEntry.bit_pack_unpack(decoder, echoes_resource_database) # Assert assert pickup == decoded
def test_bit_pack_pickup_entry(has_convert: bool, echoes_resource_database, generic_item_category): # Setup name = "Some Random Name" if has_convert: resource_lock = ResourceLock( find_resource_info_with_long_name(echoes_resource_database.item, "Morph Ball"), find_resource_info_with_long_name(echoes_resource_database.item, "Item Percentage"), find_resource_info_with_long_name(echoes_resource_database.item, "Space Jump Boots"), ) else: resource_lock = None pickup = PickupEntry( name=name, model=PickupModel( game=RandovaniaGame.METROID_PRIME_CORRUPTION, name="HyperMissile", ), item_category=generic_item_category, broad_category=generic_item_category, progression=( (find_resource_info_with_long_name(echoes_resource_database.item, "Morph Ball"), 1), (find_resource_info_with_long_name(echoes_resource_database.item, "Grapple Beam"), 1), ), extra_resources=((find_resource_info_with_long_name( echoes_resource_database.item, "Item Percentage"), 5), ), resource_lock=resource_lock) # Run encoded = bitpacking.pack_value( BitPackPickupEntry(pickup, echoes_resource_database)) decoder = BitPackDecoder(encoded) decoded = BitPackPickupEntry.bit_pack_unpack(decoder, echoes_resource_database) # Assert assert pickup == decoded
def item(name: str): return find_resource_info_with_long_name(game.resource_database.item, name)
def _get_command_visor(self): return find_resource_info_with_long_name( self.game.resource_database.item, "Command Visor")
def _get_scan_visor(self): return find_resource_info_with_long_name( self.game.resource_database.item, "Scan Visor")
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 item(name): return search.find_resource_info_with_long_name( echoes_game_description.resource_database.item, name)
def decode_single(player_index: int, all_pools: Dict[int, PoolResults], game: GameDescription, game_modifications: dict, configuration: EchoesConfiguration) -> 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 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: ElevatorConnection = {} 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] = 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)) - 1 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, # ElevatorConnection 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 get_item_by_name(self, name: str) -> ItemResourceInfo: return search.find_resource_info_with_long_name(self.item, name)
def _patches_with_data(request, echoes_game_description, echoes_item_database): game = echoes_game_description 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": {} } patches = dataclasses.replace(game.create_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({ find_resource_info_with_long_name(game.resource_database.item, item_name): 1, }) data["starting_items"][item_name] = 1 if request.param.get("elevator"): teleporter, elevator_source = request.param.get("elevator") elevator_connection = copy.copy(patches.elevator_connection) elevator_connection[teleporter] = 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"): 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"): 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 _get_scan_visor(self) -> ItemResourceInfo: try: return find_resource_info_with_long_name( self.resource_database.item, "Scan Visor") except MissingResource: return None
def create_tracker(self): tracker_name = self.selected_tracker if tracker_name == self._current_tracker_name or tracker_name is None: return self.delete_tracker() with path_for( self.trackers[tracker_name]).open("r") as tracker_details_file: tracker_details = json.load(tracker_details_file) game_enum = RandovaniaGame(tracker_details["game"]) resource_database = default_database.resource_database_for(game_enum) for element in tracker_details["elements"]: text_template = "" minimum_to_check = element.get("minimum_to_check", 1) field_to_check = FieldToCheck( element.get("field_to_check", FieldToCheck.CAPACITY.value)) labels = [] if "image_path" in element: paths = element["image_path"] if not isinstance(paths, list): paths = [paths] visible = True for path in paths: image_path = get_data_path().joinpath(path) if not image_path.exists(): logging.error("Tracker asset not found: %s", image_path) pixmap = QtGui.QPixmap(str(image_path)) label = ClickableLabel(self.inventory_group, paint_with_opacity(pixmap, 0.3), paint_with_opacity(pixmap, 1.0)) label.set_checked(False) label.set_ignore_mouse_events(True) label.setVisible(visible) visible = False labels.append(label) elif "label" in element: label = QtWidgets.QLabel(self.inventory_group) label.setAlignment(QtCore.Qt.AlignCenter) text_template = element["label"] labels.append(label) else: raise ValueError(f"Invalid element: {element}") resources = [ find_resource_info_with_long_name(resource_database.item, resource_name) for resource_name in element["resources"] ] for resource, label in zip(resources, labels): label.setToolTip(resource.long_name) self._tracker_elements.append( Element(labels, resources, text_template, minimum_to_check, field_to_check)) for label in labels: self.inventory_layout.addWidget(label, element["row"], element["column"]) self.inventory_spacer = QtWidgets.QSpacerItem( 5, 5, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.inventory_layout.addItem(self.inventory_spacer, self.inventory_layout.rowCount(), self.inventory_layout.columnCount()) self._current_tracker_game = game_enum self._update_tracker_from_hook({}) self._current_tracker_name = tracker_name