def test_create_spawn_point_field(echoes_resource_database, empty_patches): # Setup patches = empty_patches.assign_starting_location(AreaLocation( 100, 5000)).assign_extra_initial_items({ echoes_resource_database.get_by_type_and_index( ResourceType.ITEM, 15): 3, }) capacities = [{ 'amount': 3 if item.index == 15 else 0, 'index': item.index } for item in echoes_resource_database.item] # Run result = claris_patcher_file._create_spawn_point_field( patches, echoes_resource_database) # Assert assert result == { "location": { "world_asset_id": 100, "area_asset_id": 5000, }, "amount": capacities, "capacity": capacities, }
def test_invalid_constructor_location_should_be_missing(): with pytest.raises(ValueError) as err: StartingLocation(StartingLocationConfiguration.SHIP, AreaLocation(0, 0)) assert str(err.value) == "custom_location set to world 0/area 0, " \ "but configuration is StartingLocationConfiguration.SHIP instead of CUSTOM"
def decode_data_with_world_reader( data: Dict) -> Tuple[WorldReader, GameDescription]: game = data["game"] game_name = data["game_name"] resource_database = read_resource_database(data["resource_database"]) dock_weakness_database = read_dock_weakness_database( data["dock_weakness_database"], resource_database) if game == 2: game_specific = read_game_specific(data["game_specific"], resource_database) else: game_specific = None world_reader = WorldReader(resource_database, dock_weakness_database) world_list = world_reader.read_world_list(data["worlds"]) victory_condition = read_requirement(data["victory_condition"], resource_database) starting_location = AreaLocation.from_json(data["starting_location"]) initial_states = read_initial_states(data["initial_states"], resource_database) return world_reader, GameDescription( game=game, game_name=game_name, resource_database=resource_database, game_specific=game_specific, dock_weakness_database=dock_weakness_database, world_list=world_list, victory_condition=victory_condition, starting_location=starting_location, initial_states=initial_states, )
def test_create_message_for_hint_relative_area(echoes_game_description, pickup, players_config, distance_precise, distance_text): world_list = echoes_game_description.world_list patches = echoes_game_description.create_game_patches( ).assign_pickup_assignment({ PickupIndex(5): PickupTarget(pickup, 0), }) hint_name_creator = LocationHintCreator(world_list, None, None) location_formatters = { HintLocationPrecision.RELATIVE_TO_AREA: RelativeAreaFormatter(world_list, patches) } hint = Hint( HintType.LOCATION, PrecisionPair( HintLocationPrecision.RELATIVE_TO_AREA, HintItemPrecision.DETAILED, RelativeDataArea(distance_precise, AreaLocation(1039999561, 3822429534), HintRelativeAreaName.NAME)), PickupIndex(5)) # Run result = item_hints.create_message_for_hint(hint, {0: patches}, players_config, hint_name_creator, location_formatters, world_list) # Assert assert result == ( f'The &push;&main-color=#FF6705B3;Pickup&pop; can be found ' f'&push;&main-color=#FF3333;{distance_text} 10 rooms&pop; away from Torvus Bog - Great Bridge.' )
def one_way_elevator_connections( rng: Random, elevator_database: Tuple[Elevator, ...], world_list: WorldList, elevator_target: bool, replacement: bool, ) -> Dict[int, AreaLocation]: if elevator_target: target_locations = [ elevator.area_location for elevator in elevator_database ] else: target_locations = [ AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas ] rng.shuffle(target_locations) def _create_target(): if replacement: return rng.choice(target_locations) else: return target_locations.pop() return { elevator.instance_id: _create_target() for elevator in elevator_database }
def test_create_spawn_point_field(echoes_resource_database, empty_patches): # Setup patches = empty_patches.assign_starting_location(AreaLocation(100, 5000)) capacities = [{ 'amount': starting_resources._vanilla_item_loss_enabled_items.get(item.index, 0), 'index': item.index } for item in echoes_resource_database.item] # Run result = patcher_file._create_spawn_point_field( echoes_resource_database, StartingResources.from_non_custom_configuration( StartingResourcesConfiguration.VANILLA_ITEM_LOSS_ENABLED), patches) # Assert assert result == { "location": { "world_asset_id": 100, "area_asset_id": 5000, }, "amount": capacities, "capacity": capacities, }
def static_teleporters(self) -> Dict[Teleporter, AreaLocation]: static = {} if self.skip_final_bosses and self.game == RandovaniaGame.PRIME2: static[Teleporter(1006255871, 2278776548, 136970379)] = AreaLocation( 1006255871, 1393588666) return static
def decode_data( data: Dict, add_self_as_requirement_to_resources: bool = True) -> GameDescription: game = data["game"] game_name = data["game_name"] resource_database = read_resource_database(data["resource_database"]) pickup_database = read_pickup_database(data["pickup_database"], resource_database) dock_weakness_database = read_dock_weakness_database( data["dock_weakness_database"], resource_database) world_reader = WorldReader(resource_database, dock_weakness_database, add_self_as_requirement_to_resources) world_list = world_reader.read_world_list(data["worlds"]) victory_condition = read_requirement_set(data["victory_condition"], resource_database) starting_location = AreaLocation.from_json(data["starting_location"]) initial_states = read_initial_states(data["initial_states"], resource_database) return GameDescription( game=game, game_name=game_name, resource_database=resource_database, pickup_database=pickup_database, dock_weakness_database=dock_weakness_database, world_list=world_list, victory_condition=victory_condition, starting_location=starting_location, initial_states=initial_states, add_self_as_requirement_to_resources= add_self_as_requirement_to_resources, )
def setup_starting_area_elements(self): game_description = default_prime2_game_description() world_to_group = {} self._starting_location_for_area = {} for row, world in enumerate(game_description.world_list.worlds): for column, is_dark_world in enumerate([False, True]): group_box = QGroupBox(self.starting_locations_contents) group_box.setTitle(world.correct_name(is_dark_world)) vertical_layout = QVBoxLayout(group_box) vertical_layout.setContentsMargins(8, 4, 8, 4) vertical_layout.setSpacing(2) vertical_layout.setAlignment(QtCore.Qt.AlignTop) group_box.vertical_layout = vertical_layout world_to_group[world.correct_name(is_dark_world)] = group_box self.starting_locations_layout.addWidget(group_box, row, column) for world in game_description.world_list.worlds: for area in sorted(world.areas, key=lambda a: a.name): group_box = world_to_group[world.correct_name(area.in_dark_aether)] check = QtWidgets.QCheckBox(group_box) check.setText(area.name) check.area_location = AreaLocation(world.world_asset_id, area.area_asset_id) check.stateChanged.connect(functools.partial(self._on_check_starting_area, check)) group_box.vertical_layout.addWidget(check) self._starting_location_for_area[area.area_asset_id] = check self.starting_area_quick_fill_ship.clicked.connect(self._starting_location_on_select_ship) self.starting_area_quick_fill_save_station.clicked.connect(self._starting_location_on_select_save_station)
def on_preset_changed_starting_area(self, preset: Preset): starting_locations = preset.configuration.starting_location.locations self._during_batch_check_update = True for world in self.game_description.world_list.worlds: for is_dark_world in dark_world_flags(world): all_areas = True no_areas = True areas = [area for area in world.areas if area.in_dark_aether == is_dark_world] correct_name = world.correct_name(is_dark_world) if correct_name not in self._starting_location_for_world: continue for area in areas: if area.area_asset_id in self._starting_location_for_area: is_checked = AreaLocation(world.world_asset_id, area.area_asset_id) in starting_locations if is_checked: no_areas = False else: all_areas = False self._starting_location_for_area[area.area_asset_id].setChecked(is_checked) if all_areas: self._starting_location_for_world[correct_name].setCheckState(Qt.Checked) elif no_areas: self._starting_location_for_world[correct_name].setCheckState(Qt.Unchecked) else: self._starting_location_for_world[correct_name].setCheckState(Qt.PartiallyChecked) self._during_batch_check_update = False
def _area_name_to_area_location(world_list: WorldList, area_name: str) -> AreaLocation: world_name, area_name = re.match("([^/]+)/([^/]+)", area_name).group(1, 2) starting_world = world_list.world_with_name(world_name) starting_area = starting_world.area_by_name(area_name) return AreaLocation(starting_world.world_asset_id, starting_area.area_asset_id)
def _update_area_list(self, areas_to_check: FrozenSet[AreaLocation], invert_check: bool, location_for_world: Dict[str, QtWidgets.QCheckBox], location_for_area: Dict[int, QtWidgets.QCheckBox], ): self._during_batch_check_update = True for world in self.game_description.world_list.worlds: for is_dark_world in dark_world_flags(world): all_areas = True no_areas = True areas = [area for area in world.areas if area.in_dark_aether == is_dark_world] correct_name = world.correct_name(is_dark_world) if correct_name not in location_for_world: continue for area in areas: if area.area_asset_id in location_for_area: is_checked = AreaLocation(world.world_asset_id, area.area_asset_id) in areas_to_check if invert_check: is_checked = not is_checked if is_checked: no_areas = False else: all_areas = False location_for_area[area.area_asset_id].setChecked(is_checked) if all_areas: location_for_world[correct_name].setCheckState(Qt.Checked) elif no_areas: location_for_world[correct_name].setCheckState(Qt.Unchecked) else: location_for_world[correct_name].setCheckState(Qt.PartiallyChecked) self._during_batch_check_update = False
def read_node(self, data: Dict) -> Node: name: str = data["name"] heal: bool = data["heal"] node_type: int = data["node_type"] if node_type == 0: self.generic_index += 1 return GenericNode(name, heal, self.generic_index) elif node_type == 1: return DockNode(name, heal, data["dock_index"], DockConnection(data["connected_area_asset_id"], data["connected_dock_index"]), self.dock_weakness_database.get_by_type_and_index(DockType(data["dock_type"]), data["dock_weakness_index"])) elif node_type == 2: return PickupNode(name, heal, PickupIndex(data["pickup_index"])) elif node_type == 3: instance_id = data["teleporter_instance_id"] destination_world_asset_id = data["destination_world_asset_id"] destination_area_asset_id = data["destination_area_asset_id"] return TeleporterNode(name, heal, instance_id, AreaLocation(destination_world_asset_id, destination_area_asset_id)) elif node_type == 4: return EventNode(name, heal, self.resource_database.get_by_type_and_index(ResourceType.EVENT, data["event_index"])) else: raise Exception("Unknown node type: {}".format(node_type))
def _create_area_list_selection(self, parent: QtWidgets.QWidget, layout: QtWidgets.QGridLayout, all_area_locations: List[AreaLocation], on_check: Callable[[List[AreaLocation], bool], None], ): world_to_group = {} checks_for_world = {} checks_for_area = {} worlds, areas_by_world = self._areas_by_world_from_locations(all_area_locations) worlds.sort(key=lambda it: it.name) def _on_check_area(c, _): if not self._during_batch_check_update: on_check([c.area_location], c.isChecked()) def _on_check_world(c, _): if not self._during_batch_check_update: world_list = self.game_description.world_list w = world_list.world_by_asset_id(c.world_asset_id) world_areas = [world_list.area_to_area_location(a) for a in w.areas if c.is_dark_world == a.in_dark_aether if a.area_asset_id in checks_for_area] on_check(world_areas, c.isChecked()) for row, world in enumerate(worlds): for column, is_dark_world in enumerate(dark_world_flags(world)): group_box = QGroupBox(parent) world_check = QtWidgets.QCheckBox(group_box) world_check.setText(world.correct_name(is_dark_world)) world_check.world_asset_id = world.world_asset_id world_check.is_dark_world = is_dark_world world_check.stateChanged.connect(functools.partial(_on_check_world, world_check)) world_check.setTristate(True) vertical_layout = QVBoxLayout(group_box) vertical_layout.setContentsMargins(8, 4, 8, 4) vertical_layout.setSpacing(2) vertical_layout.setAlignment(QtCore.Qt.AlignTop) separator = QtWidgets.QFrame() separator.setFrameShape(QtWidgets.QFrame.HLine) separator.setFrameShadow(QtWidgets.QFrame.Sunken) group_box.vertical_layout = vertical_layout group_box.vertical_layout.addWidget(world_check) group_box.vertical_layout.addWidget(separator) world_to_group[world.correct_name(is_dark_world)] = group_box layout.addWidget(group_box, row, column) checks_for_world[world.correct_name(is_dark_world)] = world_check for world in worlds: for area in sorted(areas_by_world[world.world_asset_id], key=lambda a: a.name): group_box = world_to_group[world.correct_name(area.in_dark_aether)] check = QtWidgets.QCheckBox(group_box) check.setText(area.name) check.area_location = AreaLocation(world.world_asset_id, area.area_asset_id) check.stateChanged.connect(functools.partial(_on_check_area, check)) group_box.vertical_layout.addWidget(check) checks_for_area[area.area_asset_id] = check return checks_for_world, checks_for_area
def areas_list(cls, game: RandovaniaGame): world_list = default_database.game_description_for(game).world_list areas = [ AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas if area.valid_starting_location ] return list(sorted(areas))
def test_one_way_elevator_connections(echoes_game_description, replacement, expected): # Setup rng = random.Random(5000) target_locations = [AreaLocation(i, i) for i in range(6)] elevators = [ ElevatorHelper(Teleporter(i, i, i), AreaLocation(i, i)) for i in range(6) ] database = tuple(elevators) # Run result = elevator_distributor.one_way_elevator_connections( rng, database, target_locations, replacement) # Assert assert result == expected
def current_starting_area_location(self) -> Optional[AreaLocation]: if self.specific_starting_world_combo.isEnabled(): return AreaLocation( self.specific_starting_world_combo.currentData().world_asset_id, self.specific_starting_area_combo.currentData().area_asset_id, ) else: return None
def _areas_list(): world_list = default_database.default_prime2_game_description().world_list areas = [ AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas ] return list(sorted(areas))
def elevator_connections_for_seed_number( rng: Random, ) -> Dict[int, AreaLocation]: elevator_connection = {} for elevator in try_randomize_elevators(rng): elevator_connection[elevator.instance_id] = AreaLocation( elevator.connected_elevator.world_asset_id, elevator.connected_elevator.area_asset_id) return elevator_connection
def from_json(cls, value: List[dict], game: RandovaniaGame) -> "LocationList": if not isinstance(value, list): raise ValueError( "StartingLocation from_json must receive a list, got {}". format(type(value))) elements = [AreaLocation.from_json(location) for location in value] return cls.with_elements(elements, game)
def area_locations_with_filter( game: RandovaniaGame, condition: Callable[[Area], bool]) -> List[AreaLocation]: world_list = default_database.game_description_for(game).world_list areas = [ AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas if condition(area) ] return list(sorted(areas))
def _location_with_data(request, mocker, echoes_game_description): world_list = echoes_game_description.world_list areas = list(itertools.islice( (AreaLocation(world.world_asset_id, area.area_asset_id) for world in world_list.worlds for area in world.areas if area.valid_starting_location), 15)) mocker.patch("randovania.layout.location_list.LocationList.areas_list", return_value=list(sorted(areas))) return request.param["encoded"], LocationList.from_json(request.param["json"], RandovaniaGame.PRIME2)
def add_elevator_connections_to_patches( layout_configuration: EchoesConfiguration, rng: Random, patches: GamePatches) -> GamePatches: """ :param layout_configuration: :param rng: :param patches: :return: """ elevator_connection = copy.copy(patches.elevator_connection) if layout_configuration.elevators != LayoutElevators.VANILLA: if rng is None: raise MissingRng("Elevator") world_list = data_reader.decode_data( layout_configuration.game_data).world_list areas_to_not_change = { 2278776548, # Sky Temple Gateway 2068511343, # Sky Temple Energy Controller 3136899603, # Aerie Transport Station 1564082177, # Aerie } elevator_db = elevator_distributor.create_elevator_database( world_list, areas_to_not_change) if layout_configuration.elevators in { LayoutElevators.TWO_WAY_RANDOMIZED, LayoutElevators.TWO_WAY_UNCHECKED }: connections = elevator_distributor.two_way_elevator_connections( rng=rng, elevator_database=elevator_db, between_areas=layout_configuration.elevators == LayoutElevators.TWO_WAY_RANDOMIZED) else: connections = elevator_distributor.one_way_elevator_connections( rng=rng, elevator_database=elevator_db, world_list=world_list, elevator_target=layout_configuration.elevators != LayoutElevators.ONE_WAY_ANYTHING, replacement=layout_configuration.elevators == LayoutElevators.ONE_WAY_ELEVATOR_REPLACEMENT, ) elevator_connection.update(connections) if layout_configuration.skip_final_bosses: elevator_connection[136970379] = AreaLocation(1006255871, 1393588666) return dataclasses.replace(patches, elevator_connection=elevator_connection)
def setup_starting_area_elements(self): game_description = self.game_description world_to_group = {} self._starting_location_for_world = {} self._starting_location_for_area = {} for row, world in enumerate(game_description.world_list.worlds): for column, is_dark_world in enumerate([False, True]): group_box = QGroupBox(self.starting_locations_contents) world_check = QtWidgets.QCheckBox(group_box) world_check.setText(world.correct_name(is_dark_world)) world_check.world_asset_id = world.world_asset_id world_check.is_dark_world = is_dark_world world_check.stateChanged.connect( functools.partial(self._on_check_starting_world, world_check)) world_check.setTristate(True) vertical_layout = QVBoxLayout(group_box) vertical_layout.setContentsMargins(8, 4, 8, 4) vertical_layout.setSpacing(2) vertical_layout.setAlignment(QtCore.Qt.AlignTop) separator = QtWidgets.QFrame() separator.setFrameShape(QtWidgets.QFrame.HLine) separator.setFrameShadow(QtWidgets.QFrame.Sunken) group_box.vertical_layout = vertical_layout group_box.vertical_layout.addWidget(world_check) group_box.vertical_layout.addWidget(separator) world_to_group[world.correct_name(is_dark_world)] = group_box self.starting_locations_layout.addWidget( group_box, row, column) self._starting_location_for_world[world.correct_name( is_dark_world)] = world_check for world in game_description.world_list.worlds: for area in sorted(world.areas, key=lambda a: a.name): if not area.valid_starting_location: continue group_box = world_to_group[world.correct_name( area.in_dark_aether)] check = QtWidgets.QCheckBox(group_box) check.setText(area.name) check.area_location = AreaLocation(world.world_asset_id, area.area_asset_id) check.stateChanged.connect( functools.partial(self._on_check_starting_area, check)) group_box.vertical_layout.addWidget(check) self._starting_location_for_area[area.area_asset_id] = check self.starting_area_quick_fill_ship.clicked.connect( self._starting_location_on_select_ship) self.starting_area_quick_fill_save_station.clicked.connect( self._starting_location_on_select_save_station)
def test_create_elevators_field_elevators_for_a_seed(echoes_resource_database, empty_patches): # Setup game = data_reader.decode_data(default_data.decode_default_prime2(), False) patches = dataclasses.replace(empty_patches, elevator_connection={ 589851: AreaLocation(464164546, 900285955), 1572998: AreaLocation(1039999561, 3479543630), }) # Run result = patcher_file._create_elevators_field(game.world_list, patches) # Assert assert result == [ { "origin_location": { "world_asset_id": 1006255871, "area_asset_id": 2918020398 }, "target_location": { "world_asset_id": 464164546, "area_asset_id": 900285955 }, "room_name": "Transport to Sanctuary Fortress", }, { "origin_location": { "world_asset_id": 1006255871, "area_asset_id": 1660916974 }, "target_location": { "world_asset_id": 1039999561, "area_asset_id": 3479543630 }, "room_name": "Transport to Torvus Bog", }, ]
def from_json(cls, value: list) -> "StartingLocation": if not isinstance(value, list): raise ValueError("StartingLocation from_json must receive a list, got {}".format(type(value))) world_list = default_database.default_prime2_game_description().world_list elements = [] for location in value: world_name, area_name = location.split("/") world = world_list.world_with_name(world_name) area = world.area_by_name(area_name) elements.append(AreaLocation(world.world_asset_id, area.area_asset_id)) return cls.with_elements(elements)
def bit_pack_unpack(cls, decoder: BitPackDecoder) -> "StartingLocation": configuration = StartingLocationConfiguration.bit_pack_unpack(decoder) location = None if configuration == StartingLocationConfiguration.CUSTOM: world_list, areas = _areas_list() area_index = decoder.decode(len(areas))[0] area = areas[area_index] location = AreaLocation( world_list.world_with_area(area).world_asset_id, area.area_asset_id) return cls(configuration, location)
def _node_mapping_to_elevator_connection( world_list: WorldList, elevators: Dict[str, str], ) -> Dict[int, AreaLocation]: result = {} for source_name, target_node in elevators.items(): source_node = world_list.node_from_name(source_name) assert isinstance(source_node, TeleporterNode) target_node = world_list.node_from_name(target_node) result[source_node.teleporter_instance_id] = AreaLocation( world_list.nodes_to_world(target_node).world_asset_id, world_list.nodes_to_area(target_node).area_asset_id) return result
def _calculate_nodes_to_area_world(worlds: Iterable[World]): nodes_to_area = {} nodes_to_world = {} ids_to_area = {} for world in worlds: for area in world.areas: ids_to_area[AreaLocation(world.world_asset_id, area.area_asset_id)] = area for node in area.nodes: if node in nodes_to_area: raise ValueError( "Trying to map {} to {}, but already mapped to {}".format( node, area, nodes_to_area[node])) nodes_to_area[node] = area nodes_to_world[node] = world return nodes_to_area, nodes_to_world, ids_to_area
def test_add_elevator_connections_to_patches_vanilla(echoes_game_data, skip_final_bosses: bool, default_layout_configuration): # Setup game = data_reader.decode_data(echoes_game_data) expected = dataclasses.replace(game.create_game_patches()) if skip_final_bosses: expected.elevator_connection[136970379] = AreaLocation(1006255871, 1393588666) # Run result = base_patches_factory.add_elevator_connections_to_patches( dataclasses.replace(default_layout_configuration, skip_final_bosses=skip_final_bosses), Random(0), game.create_game_patches()) # Assert assert result == expected