def generate_list(permalink: Permalink, status_update: Optional[Callable[[str], None]], timeout: Optional[int] = 120) -> LayoutDescription: if status_update is None: status_update = id data = permalink.layout_configuration.game_data create_patches_params = { "permalink": permalink, "game": data_reader.decode_data(data, False), "status_update": status_update } resolver_game = data_reader.decode_data(data) def create_failure(message: str): return GenerationFailure(message, permalink=permalink) new_patches = None final_state_by_resolve = None with multiprocessing.dummy.Pool(1) as dummy_pool: patches_async = dummy_pool.apply_async(func=_create_patches, kwds=create_patches_params) try: new_patches = patches_async.get(timeout) except multiprocessing.TimeoutError: raise create_failure("Timeout reached when generating patches.") resolve_params = { "configuration": permalink.layout_configuration, "game": resolver_game, "patches": new_patches, "status_update": status_update, } final_state_async = dummy_pool.apply_async(func=resolver.resolve, kwds=resolve_params) try: final_state_by_resolve = final_state_async.get(60) except multiprocessing.TimeoutError: raise create_failure("Timeout reached when validating possibility") if final_state_by_resolve is None: # Why is final_state_by_distribution not OK? raise create_failure( "Generated seed was considered impossible by the solver") else: solver_path = _state_to_solver_path(final_state_by_resolve, resolver_game) return LayoutDescription(permalink=permalink, version=VERSION, patches=new_patches, solver_path=solver_path)
def test_round_trip_full(): original_data = default_data.decode_default_prime2() game = data_reader.decode_data(original_data) encoded_data = data_writer.write_game_description(game) assert original_data == encoded_data
def _create_test_layout_description( configuration: LayoutConfiguration, pickup_mapping: Iterable[int], ) -> LayoutDescription: """ Creates a LayoutDescription for the given configuration, with the patches being for the given pickup_mapping :param configuration: :param pickup_mapping: :return: """ game = data_reader.decode_data(configuration.game_data) pickup_database = game.pickup_database return LayoutDescription( version=VERSION, permalink=Permalink( seed_number=0, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=configuration, ), patches=GamePatches.with_game(game).assign_new_pickups([ (PickupIndex(i), pickup_database.original_pickup_mapping[PickupIndex(new_index)]) for i, new_index in enumerate(pickup_mapping) ]), solver_path=())
def __init__(self, data: dict, edit_mode: bool): super().__init__() self.setupUi(self) set_default_window_icon(self) self.edit_mode = edit_mode self.world_selector_box.currentIndexChanged.connect( self.on_select_world) self.area_selector_box.currentIndexChanged.connect(self.on_select_area) self.other_node_connection_edit_button.clicked.connect( self._open_edit_connection) self.save_database_button.clicked.connect(self._save_database) self.verticalLayout.setAlignment(Qt.AlignTop) self.alternatives_grid_layout = QGridLayout( self.other_node_alternatives_contents) self.game_description = data_reader.decode_data(data, False) self.resource_database = self.game_description.resource_database self.world_list = self.game_description.world_list for world in sorted(self.world_list.worlds, key=lambda x: x.name): self.world_selector_box.addItem(world.name, userData=world) self.update_edit_mode()
def as_json(self) -> dict: result = { "info": { "version": self.version, "permalink": self.permalink.as_json, } } if self.permalink.spoiler: world_list = data_reader.decode_data(self.permalink.layout_configuration.game_data).world_list result["locations"] = { key: value for key, value in sorted(_pickup_assignment_to_item_locations(world_list, self.patches.pickup_assignment).items()) } result["elevators"] = { world_list.node_name(_find_node_with_teleporter(world_list, teleporter_id), with_world=True): world_list.node_name(world_list.resolve_teleporter_connection(connection), with_world=True) for teleporter_id, connection in self.patches.elevator_connection.items() } result["playthrough"] = [ { "path_from_previous": path.previous_nodes, "node": path.node_name, } for path in self.solver_path ] return result
def from_json_dict(cls, json_dict: dict) -> "LayoutDescription": version = json_dict["info"]["version"] version_as_obj = StrictVersion(version) if version_as_obj < StrictVersion("0.21.0"): raise RuntimeError("Unsupported log file version '{}'.".format(version)) # TODO: add try/catch to throw convert potential errors in "seed from future version broke" permalink = Permalink.from_json_dict(json_dict["info"]["permalink"]) if not permalink.spoiler: raise ValueError("Unable to read details of seed log with spoiler disabled") game = data_reader.decode_data(permalink.layout_configuration.game_data) patches = GamePatches( _item_locations_to_pickup_assignment(game, json_dict["locations"]), _node_mapping_to_elevator_connection(game.world_list, json_dict["elevators"]), {}, {}, (), game.starting_location ) return LayoutDescription( version=version, permalink=permalink, patches=patches, solver_path=_playthrough_list_to_solver_path(json_dict["playthrough"]), )
def __init__(self, game_connection: GameConnection, options: Options): super().__init__() self.setupUi(self) self.game_connection = game_connection common_qt_lib.set_default_window_icon(self) self.game_data = data_reader.decode_data( default_data.decode_default_prime2()) self._energy_tank_item = find_resource_info_with_long_name( self.game_data.resource_database.item, "Energy Tank") self._item_to_label: Dict[ItemResourceInfo, ClickableLabel] = {} self._labels_for_keys = [] self.create_tracker() self.game_connection_setup = GameConnectionSetup( self, self.game_connection_tool, self.connection_status_label, self.game_connection, options) self.force_update_button.setEnabled(not options.tracking_inventory) self.force_update_button.clicked.connect(self.on_force_update_button) self._update_timer = QTimer(self) self._update_timer.setInterval(100) self._update_timer.timeout.connect(self._on_timer_update) self._update_timer.setSingleShot(True)
def test_add_elevator_connections_to_patches_random(echoes_game_data): # Setup game = data_reader.decode_data(echoes_game_data) permalink = dataclasses.replace(Permalink.default(), layout_configuration=dataclasses.replace( LayoutConfiguration.default(), elevators=LayoutElevators.RANDOMIZED)) expected = dataclasses.replace(GamePatches.with_game(game), elevator_connection={ 589851: AreaLocation(1039999561, 1868895730), 1572998: AreaLocation(1039999561, 3479543630), 1966093: AreaLocation(2252328306, 408633584), 2097251: AreaLocation(1119434212, 3331021649), 136970379: AreaLocation(2252328306, 2068511343), 3342446: AreaLocation(1039999561, 3205424168), 3538975: AreaLocation(1119434212, 2806956034), 152: AreaLocation(1006255871, 2889020216), 393260: AreaLocation(464164546, 3145160350), 524321: AreaLocation(464164546, 900285955), 589949: AreaLocation(1006255871, 2278776548), 122: AreaLocation(464164546, 3528156989), 1245307: AreaLocation(1006255871, 1345979968), 2949235: AreaLocation(1006255871, 1287880522), 129: AreaLocation(1006255871, 2918020398), 2162826: AreaLocation(1006255871, 1660916974), 4522032: AreaLocation(1006255871, 3455543403), 38: AreaLocation(1119434212, 1473133138), 1245332: AreaLocation(2252328306, 2399252740), 1638535: AreaLocation(2252328306, 2556480432), }) # Run result = base_patches_factory.add_elevator_connections_to_patches( permalink.layout_configuration, Random(permalink.seed_number), GamePatches.with_game(game), ) # Assert assert result == expected
def __init__(self, layout_configuration: LayoutConfiguration): super().__init__() self.setupUi(self) set_default_window_icon(self) self.layout_configuration = layout_configuration self.game_description = data_reader.decode_data( layout_configuration.game_data, True) self.logic, self._initial_state = logic_bootstrap( layout_configuration, self.game_description, GamePatches.with_game(self.game_description)) self.resource_filter_check.stateChanged.connect( self.update_locations_tree_for_reachable_nodes) self.hide_collected_resources_check.stateChanged.connect( self.update_locations_tree_for_reachable_nodes) self.undo_last_action_button.clicked.connect(self._undo_last_action) self.configuration_label.setText( "Trick Level: {}; Elevators: Vanilla; Item Loss: {}".format( layout_configuration.trick_level.value, layout_configuration.starting_resources.configuration.value, )) self.setup_pickups_box() self.setup_possible_locations_tree() self._starting_nodes = { node for node in self.game_description.world_list.all_nodes if node.is_resource_node and node.resource() in self._initial_state.resources } self._add_new_action(self._initial_state.node)
def test_round_trip_generated_patches(echoes_game_data, preset_manager): # Setup preset = dataclasses.replace( preset_manager.default_preset, layout_configuration=dataclasses.replace( preset_manager.default_preset.layout_configuration, trick_level_configuration=TrickLevelConfiguration( global_level=LayoutTrickLevel.MINIMAL_RESTRICTIONS, specific_levels={}, ) ) ) patches = generator._create_randomized_patches( permalink=Permalink( seed_number=1000, spoiler=True, preset=preset, ), game=data_reader.decode_data(echoes_game_data), status_update=lambda x: None, ) # Run encoded = game_patches_serializer.serialize(patches, echoes_game_data) decoded = game_patches_serializer.decode(encoded, preset.layout_configuration) # Assert assert patches == decoded
def test_round_trip_generated_patches(echoes_game_data): # Setup configuration = LayoutConfiguration.from_params( trick_level_configuration=TrickLevelConfiguration( global_level=LayoutTrickLevel.MINIMAL_RESTRICTIONS, specific_levels={}, )) patches = generator._create_randomized_patches( permalink=Permalink( seed_number=1000, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=configuration, ), game=data_reader.decode_data(echoes_game_data), status_update=lambda x: None, ) # Run encoded = game_patches_serializer.serialize(patches, echoes_game_data) decoded = game_patches_serializer.decode(encoded, configuration) # Assert assert patches == decoded
def test_create_node_and_save(tmp_path, echoes_game_data, skip_qtbot): # Setup tmp_path.joinpath("test-game", "game").mkdir(parents=True) tmp_path.joinpath("human-readable").mkdir() db_path = Path(tmp_path.joinpath("test-game", "game")) window = DataEditorWindow(echoes_game_data, db_path, True, True) window.set_warning_dialogs_disabled(True) skip_qtbot.addWidget(window) # Run window._do_create_node("Some Node", None) window._save_as_internal_database() # Assert exported_data = data_reader.read_split_file(db_path) exported_game = data_reader.decode_data(exported_data) pretty_print.write_human_readable_game(exported_game, tmp_path.joinpath("human-readable")) new_files = { f.name: f.read_text("utf-8") for f in tmp_path.joinpath("human-readable").glob("*.txt") } existing_files = { f.name: f.read_text("utf-8") for f in tmp_path.joinpath("test-game", "game").glob("*.txt") } assert list(new_files.keys()) == list(existing_files.keys()) assert new_files == existing_files
def create_patcher_file(description: LayoutDescription, cosmetic_patches: CosmeticPatches) -> dict: result = {} patcher_config = description.permalink.patcher_configuration layout = description.permalink.layout_configuration patches = description.patches game = data_reader.decode_data(layout.game_data, add_self_as_requirement_to_resources=False) result["spawn_point"] = _create_spawn_point_field( game.resource_database, layout.starting_resources, patches) result["pickups"] = _create_pickup_list(patches, game.pickup_database) result["elevators"] = _create_elevators_field(game.world_list, patches) result["specific_patches"] = { "hive_chamber_b_post_state": not is_vanilla_starting_location(layout), "warp_to_start": patcher_config.warp_to_start, "speed_up_credits": cosmetic_patches.speed_up_credits, "disable_hud_popup": cosmetic_patches.disable_hud_popup, } return result
def test_create_node_and_save(mock_prime2_human_readable_path, mock_prime2_json_path, tmpdir, echoes_game_data, skip_qtbot): # Setup mock_prime2_human_readable_path.return_value = Path(tmpdir).joinpath( "human") mock_prime2_json_path.return_value = Path(tmpdir).joinpath("database") window = DataEditorWindow(echoes_game_data, True) skip_qtbot.addWidget(window) # Run window._do_create_node("Some Node") window._save_as_internal_database() # Assert with mock_prime2_json_path.return_value.open() as data_file: exported_data = json.load(data_file) exported_game = data_reader.decode_data(exported_data) output = io.StringIO() data_writer.write_human_readable_world_list(exported_game, output) assert mock_prime2_human_readable_path.return_value.read_text( "utf-8") == output.getvalue()
def test_round_trip_full(game_enum: RandovaniaGame): original_data = default_data.read_json_then_binary(game_enum)[1] game = data_reader.decode_data(original_data) encoded_data = data_writer.write_game_description(game) assert list(encoded_data.keys()) == list(original_data.keys()) assert encoded_data == original_data
def test_round_trip_small(test_files_dir): # Setup with test_files_dir.joinpath("prime2_small_v1.json").open("r") as data_file: original_data = game_migration.migrate_to_current(json.load(data_file)) game = data_reader.decode_data(original_data) encoded_data = data_writer.write_game_description(game) assert encoded_data == original_data
def test_round_trip_small(test_files_dir): # Setup with test_files_dir.joinpath("prime_data_as_json.json").open("r") as data_file: original_data = json.load(data_file) game = data_reader.decode_data(original_data) encoded_data = data_writer.write_game_description(game) assert original_data == encoded_data
def update_human_readable_logic(args): from randovania.game_description import pretty_print from randovania.game_description import data_reader game = RandovaniaGame(args.game) path, data = default_data.read_json_then_binary(game) gd = data_reader.decode_data(data) path.with_suffix("").mkdir(parents=True, exist_ok=True) pretty_print.write_human_readable_game(gd, path.with_suffix(""))
def test_create_elevators_field_no_elevator(empty_patches): # Setup game = data_reader.decode_data(default_data.decode_default_prime2()) # Run with pytest.raises(ValueError) as exp: patcher_file._create_elevators_field(empty_patches, game) # Assert assert str(exp.value) == "Invalid elevator count. Expected 22, got 0."
def test_create_elevators_field_no_elevator(empty_patches): # Setup game = data_reader.decode_data(default_data.decode_default_prime2(), False) # Run result = patcher_file._create_elevators_field(game.world_list, empty_patches) # Assert assert result == []
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 test_decode(patches_with_data, default_layout_configuration): encoded, expected = patches_with_data game = data_reader.decode_data(default_layout_configuration.game_data) pool = pool_creator.calculate_pool_results(default_layout_configuration, game.resource_database) # Run decoded = game_patches_serializer.decode_single(0, {0: pool}, game, encoded, default_layout_configuration) # Assert assert decoded == expected
def test_add_elevator_connections_to_patches_vanilla( echoes_game_data, default_layout_configuration): # Setup game = data_reader.decode_data(echoes_game_data) # Run result = base_patches_factory.add_elevator_connections_to_patches( default_layout_configuration, Random(0), game.create_game_patches()) # Assert assert result == game.create_game_patches()
def decode(game_modifications: List[dict], layout_configurations: Dict[int, EchoesConfiguration], ) -> Dict[int, GamePatches]: all_games = {index: data_reader.decode_data(configuration.game_data) for index, configuration in layout_configurations.items()} all_pools = {index: pool_creator.calculate_pool_results(configuration, all_games[index].resource_database) for index, configuration in layout_configurations.items()} return { index: decode_single(index, all_pools, all_games[index], modifications, layout_configurations[index]) for index, modifications in enumerate(game_modifications) }
def test_add_elevator_connections_to_patches_vanilla(echoes_game_data): # Setup game = data_reader.decode_data(echoes_game_data) permalink = Permalink.default() # Run result = base_patches_factory.add_elevator_connections_to_patches( permalink.layout_configuration, Random(permalink.seed_number), GamePatches.with_game(game)) # Assert assert result == GamePatches.with_game(game)
def test_round_trip_small(test_files_dir, small_name): # Setup with test_files_dir.joinpath(small_name).open("r") as data_file: original_data = json.load(data_file) game = data_reader.decode_data(original_data) encoded_data = data_writer.write_game_description(game) # # Uncomment the following to update the file # with test_files_dir.joinpath(small_name).open("w", encoding="utf-8") as meta: # json.dump(encoded_data, meta, indent=4); assert False assert encoded_data == original_data
def run_bootstrap(preset: Preset): game = data_reader.decode_data(preset.configuration.game_data) permalink = Permalink( seed_number=15000, spoiler=True, presets={0: preset}, ) patches = base_patches_factory.create_base_patches(preset.configuration, Random(15000), game, False) _, state = logic_bootstrap(preset.configuration, game, patches) return game, state, permalink
def validate_command_logic(args): debug.set_level(args.debug) data = prime_database.decode_data_file(args) game = data_reader.decode_data(data) description = LayoutDescription.from_file(args.layout_file) configuration = description.permalink.layout_configuration patches = description.patches final_state_by_resolve = resolver.resolve(configuration=configuration, game=game, patches=patches) print(final_state_by_resolve)
def serialize(patches: GamePatches, game_data: dict) -> dict: """ Encodes a given GamePatches into a JSON-serializable dict. :param patches: :param game_data: :return: """ game = data_reader.decode_data(game_data) world_list = game.world_list ordered_pickups = [] result = { "starting_location": world_list.area_name( world_list.area_by_area_location(patches.starting_location)), "starting_items": { resource_info.long_name: quantity for resource_info, quantity in patches.starting_items.items() }, "elevators": { world_list.area_name( world_list.nodes_to_area( _find_node_with_teleporter(world_list, teleporter_id))): world_list.area_name( world_list.nodes_to_area( world_list.resolve_teleporter_connection(connection))) for teleporter_id, connection in patches.elevator_connection.items() }, "translators": { _name_for_gate(gate): requirement.long_name for gate, requirement in patches.translator_gates.items() }, "locations": { key: value for key, value in _pickup_assignment_to_item_locations( world_list, patches.pickup_assignment, ordered_pickups).items() }, "hints": { str(asset.asset_id): hint.as_json for asset, hint in patches.hints.items() } } b = bitpacking.pack_value( BitPackPickupEntryList(ordered_pickups, game.resource_database)) result["_locations_internal"] = base64.b64encode(b).decode("utf-8") return result
def rename_docks_logic(args): from randovania.game_description import data_reader from randovania.game_description import data_writer from randovania.game_description import pretty_print from randovania.game_description.editor import Editor from randovania.game_description.world.dock_node import DockNode from randovania.game_description import integrity_check game = RandovaniaGame(args.game) path, data = default_data.read_json_then_binary(game) gd = data_reader.decode_data(data) # Make the changes editor = Editor(gd) for world in gd.world_list.worlds: for area in world.areas: for i in range(len(area.nodes)): node = area.nodes[i] if not isinstance(node, DockNode): continue valid_name, suffix = integrity_check.dock_has_correct_name( area, node) if not valid_name: expected_name = integrity_check.base_dock_name(node) docks_to_same_target = integrity_check.docks_with_same_base_name( area, expected_name) if suffix is None: suffix = f" ({docks_to_same_target.index(node) + 1})" print( f"In {area.name}, renaming '{node.name}' to '{expected_name}{suffix}'" ) editor.replace_node( area, node, dataclasses.replace(node, name=f"{expected_name}{suffix}")) # Write it back logging.info("Writing database files") new_data = data_writer.write_game_description(gd) data_writer.write_as_split_files(new_data, path) logging.info("Writing human readable") path.with_suffix("").mkdir(parents=True, exist_ok=True) pretty_print.write_human_readable_game(gd, path.with_suffix(""))