def logic_bootstrap( configuration: LayoutConfiguration, game: GameDescription, patches: GamePatches, ) -> Tuple[Logic, State]: """ Core code for starting a new Logic/State. :param configuration: :param game: :param patches: :return: """ # global state for easy printing functions debug._gd = game game = copy.deepcopy(game) logic = Logic(game, configuration) starting_state = calculate_starting_state(logic, patches) if configuration.trick_level == LayoutTrickLevel.MINIMAL_RESTRICTIONS: _add_minimal_restrictions_initial_resources(starting_state.resources, game.resource_database) difficulty_level, static_resources = static_resources_for_layout_logic( configuration.trick_level, game.resource_database) starting_state.resources = merge_resources(static_resources, starting_state.resources) starting_state.resources[ game.resource_database.difficulty_resource] = difficulty_level game.simplify_connections(starting_state.resources) return logic, starting_state
def logic_bootstrap( configuration: EchoesConfiguration, game: GameDescription, patches: GamePatches, ) -> Tuple[GameDescription, State]: """ Core code for starting a new Logic/State. :param configuration: :param game: :param patches: :return: """ game = copy.deepcopy(game) starting_state = calculate_starting_state(game, patches) if configuration.trick_level.minimal_logic: _add_minimal_logic_initial_resources( starting_state.resources, game.resource_database, configuration.major_items_configuration) static_resources = trick_resources_for_configuration( configuration.trick_level, game.resource_database) static_resources.update(version_resources_for_game(game.resource_database)) static_resources.update( misc_resources_for_configuration(configuration, game.resource_database)) for resource, quantity in static_resources.items(): starting_state.resources[resource] = quantity game.patch_requirements(starting_state.resources, configuration.damage_strictness.value) return game, starting_state
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database): # Setup scan_visor = echoes_resource_database.get_item(10) node_a = GenericNode("Node A", True, None, 0) node_b = GenericNode("Node B", True, None, 1) node_c = GenericNode("Node C", True, None, 2) translator_node = TranslatorGateNode("Translator Gate", True, None, 3, TranslatorGate(1), scan_visor) world_list = WorldList([ World("Test World", "Test Dark World", 1, [ Area( "Test Area A", False, 10, 0, True, [node_a, node_b, node_c, translator_node], { node_a: { node_b: Requirement.trivial(), translator_node: Requirement.trivial(), }, node_b: { node_a: Requirement.trivial(), }, node_c: { translator_node: Requirement.trivial(), }, translator_node: { node_a: Requirement.trivial(), node_c: Requirement.trivial(), }, }) ]) ]) game_specific = EchoesGameSpecific(energy_per_tank=100, safe_zone_heal_per_second=1, beam_configurations=(), dangerous_energy_tank=False) game = GameDescription(RandovaniaGame.PRIME2, DockWeaknessDatabase([], [], [], []), echoes_resource_database, game_specific, Requirement.impossible(), None, {}, world_list) patches = game.create_game_patches() patches = patches.assign_gate_assignment({TranslatorGate(1): scan_visor}) initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99, node_a, patches, None, echoes_resource_database, game.world_list) # Run reach = reach_with_all_safe_resources(game, initial_state) # Assert if has_translator: assert set( reach.safe_nodes) == {node_a, node_b, translator_node, node_c} else: assert set(reach.safe_nodes) == {node_a, node_b}
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database): # Setup scan_visor = echoes_resource_database.get_by_type_and_index( ResourceType.ITEM, 10) node_a = GenericNode("Node A", True, 0) node_b = GenericNode("Node B", True, 1) node_c = GenericNode("Node C", True, 2) translator_node = TranslatorGateNode("Translator Gate", True, 3, TranslatorGate(1), scan_visor) world_list = WorldList([ World("Test World", "Test Dark World", 1, [ Area( "Test Area A", False, 10, 0, [node_a, node_b, node_c, translator_node], { node_a: { node_b: RequirementSet.trivial(), translator_node: RequirementSet.trivial(), }, node_b: { node_a: RequirementSet.trivial(), }, node_c: { translator_node: RequirementSet.trivial(), }, translator_node: { node_a: RequirementSet.trivial(), node_c: RequirementSet.trivial(), }, }) ]) ]) game = GameDescription(0, "", DockWeaknessDatabase([], [], [], []), echoes_resource_database, RequirementSet.impossible(), None, {}, world_list) patches = game.create_game_patches() patches = patches.assign_gate_assignment({TranslatorGate(1): scan_visor}) initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99, node_a, patches, None, echoes_resource_database) # Run reach = reach_with_all_safe_resources(game, initial_state) # Assert if has_translator: assert set( reach.safe_nodes) == {node_a, node_b, translator_node, node_c} else: assert set(reach.safe_nodes) == {node_a, node_b}
def _create_elevators_field(patches: GamePatches, game: GameDescription) -> list: """ Creates the elevator entries in the patcher file :param patches: :param game: :return: """ world_list = game.world_list nodes_by_teleporter_id = { node.teleporter_instance_id: node for node in game.all_editable_teleporter_nodes() } if len(patches.elevator_connection) != len(nodes_by_teleporter_id): raise ValueError("Invalid elevator count. Expected {}, got {}.".format( len(nodes_by_teleporter_id), len(patches.elevator_connection) )) elevators = [ { "instance_id": instance_id, "origin_location": world_list.node_to_area_location(nodes_by_teleporter_id[instance_id]).as_json, "target_location": connection.as_json, "room_name": _pretty_name_for_elevator(world_list, nodes_by_teleporter_id[instance_id], connection) } for instance_id, connection in patches.elevator_connection.items() ] return elevators
def decode_data_with_world_reader( data: Dict) -> Tuple[WorldReader, GameDescription]: data = game_migration.migrate_to_current(copy.deepcopy(data)) game = RandovaniaGame(data["game"]) resource_database = read_resource_database(game, data["resource_database"]) dock_weakness_database = read_dock_weakness_database( data["dock_weakness_database"], resource_database) layers = frozen_lib.wrap(data["layers"]) 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 = AreaIdentifier.from_json(data["starting_location"]) initial_states = read_initial_states(data["initial_states"], resource_database) minimal_logic = read_minimal_logic_db(data["minimal_logic"]) return world_reader, GameDescription( game=game, resource_database=resource_database, layers=layers, dock_weakness_database=dock_weakness_database, world_list=world_list, victory_condition=victory_condition, starting_location=starting_location, initial_states=initial_states, minimal_logic=minimal_logic, )
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 create_base_patches( configuration: LayoutConfiguration, rng: Random, game: GameDescription, ) -> GamePatches: """ :param configuration: :param rng: :param game: :return: """ # TODO: we shouldn't need the seed_number! patches = game.create_game_patches() patches = add_elevator_connections_to_patches(configuration, rng, patches) # Gates patches = patches.assign_gate_assignment( gate_assignment_for_configuration(configuration, game.resource_database, rng)) # Starting Location patches = patches.assign_starting_location( starting_location_for_configuration(configuration, game, rng)) # Hints if rng is not None: patches = add_default_hints_to_patches(rng, patches, game.world_list) return patches
def create_base_patches( configuration: EchoesConfiguration, rng: Random, game: GameDescription, is_multiworld: bool, ) -> GamePatches: """ """ patches = dataclasses.replace(game.create_game_patches(), game_specific=create_game_specific( configuration, game)) patches = add_elevator_connections_to_patches(configuration, rng, patches) # Gates if configuration.game == RandovaniaGame.PRIME2: patches = patches.assign_gate_assignment( gate_assignment_for_configuration(configuration, game.resource_database, rng)) # Starting Location patches = patches.assign_starting_location( starting_location_for_configuration(configuration, game, rng)) # Hints if rng is not None and configuration.game == RandovaniaGame.PRIME2: patches = add_echoes_default_hints_to_patches( rng, patches, game.world_list, num_joke=2, is_multiworld=is_multiworld) return patches
def create_base_patches( configuration: LayoutConfiguration, rng: Random, game: GameDescription, ) -> GamePatches: """ :param configuration: :param rng: :param game: :return: """ patches = dataclasses.replace(game.create_game_patches(), game_specific=create_game_specific( configuration, game)) patches = add_elevator_connections_to_patches(configuration, rng, patches) # Gates patches = patches.assign_gate_assignment( gate_assignment_for_configuration(configuration, game.resource_database, rng)) # Starting Location patches = patches.assign_starting_location( starting_location_for_configuration(configuration, game, rng)) # Hints if rng is not None: patches = add_default_hints_to_patches(rng, patches, game.world_list, num_joke=2) return patches
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 logic_bootstrap( self, configuration: BaseConfiguration, game: GameDescription, patches: GamePatches, ) -> Tuple[GameDescription, State]: """ Core code for starting a new Logic/State. :param configuration: :param game: :param patches: :return: """ if not game.mutable: raise ValueError("Running logic_bootstrap with non-mutable game") game.world_list.ensure_has_node_cache() starting_state = self.calculate_starting_state(game, patches, configuration) if configuration.trick_level.minimal_logic: self._add_minimal_logic_initial_resources( starting_state.resources, game, configuration.major_items_configuration) static_resources = ResourceCollection.with_database( game.resource_database) static_resources.add_resource_gain( self.trick_resources_for_configuration(configuration.trick_level, game.resource_database)) static_resources.add_resource_gain( self.event_resources_for_configuration(configuration, game.resource_database)) static_resources.add_resource_gain( self.version_resources_for_game(configuration, game.resource_database)) static_resources.add_resource_gain( self.misc_resources_for_configuration(configuration, game.resource_database)) for resource, quantity in static_resources.as_resource_gain(): starting_state.resources.set_resource(resource, quantity) game.patch_requirements(starting_state.resources, configuration.damage_strictness.value) return game, starting_state
def logic_bootstrap( configuration: LayoutConfiguration, game: GameDescription, patches: GamePatches, ) -> Tuple[GameDescription, State]: """ Core code for starting a new Logic/State. :param configuration: :param game: :param patches: :return: """ # global state for easy printing functions debug._gd = game game = copy.deepcopy(game) starting_state = calculate_starting_state(game, patches) if configuration.trick_level_configuration.global_level == LayoutTrickLevel.MINIMAL_RESTRICTIONS: major_items_config = configuration.major_items_configuration _add_minimal_restrictions_initial_resources( starting_state.resources, game.resource_database, major_items_config.progressive_grapple, major_items_config.progressive_suit, ) difficulty_level, static_resources = static_resources_for_layout_logic( configuration.trick_level_configuration, game.resource_database) add_resources_into_another(starting_state.resources, static_resources) add_resources_into_another( starting_state.resources, _create_vanilla_translator_resources(game.resource_database, configuration.elevators)) starting_state.resources[ game.resource_database.difficulty_resource] = difficulty_level # All version differences are patched out from the game starting_state.resources[find_resource_info_with_long_name( game.resource_database.version, "NTSC")] = 1 game.patch_requirements(starting_state.resources, configuration.damage_strictness.value) return game, starting_state
def logic_bootstrap( configuration: LayoutConfiguration, game: GameDescription, patches: GamePatches, ) -> Tuple[GameDescription, State]: """ Core code for starting a new Logic/State. :param configuration: :param game: :param patches: :return: """ # global state for easy printing functions debug._gd = game game = copy.deepcopy(game) starting_state = calculate_starting_state(game, patches) if configuration.trick_level_configuration.global_level == LayoutTrickLevel.MINIMAL_RESTRICTIONS: major_items_config = configuration.major_items_configuration _add_minimal_restrictions_initial_resources( starting_state.resources, game.resource_database, major_items_config.progressive_grapple, major_items_config.progressive_suit, ) difficulty_level, static_resources = static_resources_for_layout_logic( configuration.trick_level_configuration, game.resource_database) add_resources_into_another(starting_state.resources, static_resources) add_resources_into_another( starting_state.resources, _create_vanilla_translator_resources( game.resource_database, configuration.translator_configuration)) starting_state.resources[ game.resource_database.difficulty_resource] = difficulty_level game.simplify_connections(starting_state.resources) return game, starting_state
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database, echoes_game_patches): # Setup scan_visor = echoes_resource_database.get_item("DarkVisor") nc = functools.partial(NodeIdentifier.create, "Test World", "Test Area A") node_a = GenericNode(nc("Node A"), True, None, "", ("default",), {}) node_b = GenericNode(nc("Node B"), True, None, "", ("default",), {}) node_c = GenericNode(nc("Node C"), True, None, "", ("default",), {}) translator_node = ConfigurableNode(translator_identif := nc("Translator Gate"), True, None, "", ("default",), {}) world_list = WorldList([ World("Test World", [ Area("Test Area A", None, True, [node_a, node_b, node_c, translator_node], { node_a: { node_b: Requirement.trivial(), translator_node: Requirement.trivial(), }, node_b: { node_a: Requirement.trivial(), }, node_c: { translator_node: Requirement.trivial(), }, translator_node: { node_a: Requirement.trivial(), node_c: Requirement.trivial(), }, }, {} ) ], {}) ]) game = GameDescription(RandovaniaGame.METROID_PRIME_ECHOES, DockWeaknessDatabase([], {}, (None, None)), echoes_resource_database, ("default",), Requirement.impossible(), None, {}, None, world_list) patches = echoes_game_patches.assign_node_configuration({ translator_identif: ResourceRequirement(scan_visor, 1, False) }) initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99, node_a, patches, None, StateGameData(echoes_resource_database, game.world_list, 100, 99)) # Run reach = reach_lib.reach_with_all_safe_resources(game, initial_state) # Assert if has_translator: assert set(reach.safe_nodes) == {node_a, node_b, translator_node, node_c} else: assert set(reach.safe_nodes) == {node_a, node_b}
def logic_bootstrap(configuration: LayoutConfiguration, game: GameDescription, patches: GamePatches, ) -> Tuple[GameDescription, State]: """ Core code for starting a new Logic/State. :param configuration: :param game: :param patches: :return: """ # global state for easy printing functions debug._gd = game game = copy.deepcopy(game) starting_state = calculate_starting_state(game, patches) if configuration.trick_level_configuration.minimal_logic: major_items_config = configuration.major_items_configuration _add_minimal_logic_initial_resources(starting_state.resources, game.resource_database, major_items_config.progressive_grapple, major_items_config.progressive_suit, ) static_resources = trick_resources_for_configuration(configuration.trick_level_configuration, game.resource_database) static_resources.update(version_resources_for_game(game.resource_database)) static_resources.update(misc_resources_for_configuration(configuration, game.resource_database)) for resource, quantity in static_resources.items(): starting_state.resources[resource] = quantity game.patch_requirements(starting_state.resources, configuration.damage_strictness.value) return game, starting_state
def find_database_errors(game: GameDescription) -> list[str]: copy = game.get_mutable() derived_nodes.create_derived_nodes(copy) result = [] for layer in copy.layers: if layer_name_re.match(layer) is None: result.append( f"Layer '{layer}' doesn't match {layer_name_re.pattern}") for world in copy.world_list.worlds: result.extend(find_world_errors(copy, world)) result.extend(find_invalid_strongly_connected_components(copy)) return result
def pretty_print_area(game: GameDescription, area: Area, print_function=print): print_function(area.name) print_function("Asset id: {}".format(area.area_asset_id)) for node in area.nodes: print_function(f"> {node.name}; Heals? {node.heal}") for target_node, requirement in game.world_list.potential_nodes_from( node, game.create_game_patches()): if target_node is None: print_function(" > None?") else: print_function(" > {}".format( game.world_list.node_name(target_node))) for level, text in pretty_print_requirement( requirement.simplify()): print_function(" {}{}".format(" " * level, text)) print_function()
def create_from_game( cls, game: GameDescription, player_index: int, configuration: BaseConfiguration, ) -> GamePatches: game.world_list.ensure_has_node_cache() return GamePatches( game, player_index, configuration, pickup_assignment={}, elevator_connection=game.get_default_elevator_connection(), dock_connection=[None] * len(game.world_list.all_nodes), dock_weakness=[None] * len(game.world_list.all_nodes), configurable_nodes={}, starting_items=ResourceCollection.with_database( game.resource_database), starting_location=game.starting_location, hints={}, )
def create_base_patches(self, configuration: BaseConfiguration, rng: Random, game: GameDescription, is_multiworld: bool, player_index: int, rng_required: bool = True ) -> GamePatches: """ """ patches = GamePatches(player_index, configuration, {}, game.get_default_elevator_connection(), {}, {}, {}, {}, game.starting_location, {}) # Elevators try: patches = self.add_elevator_connections_to_patches(configuration, rng, patches) except MissingRng as e: if rng_required: raise e # Configurable Nodes try: patches = patches.assign_node_configuration( self.configurable_node_assignment(configuration, game, rng) ) except MissingRng as e: if rng_required: raise e # Starting Location try: patches = patches.assign_starting_location( self.starting_location_for_configuration(configuration, game, rng)) except MissingRng as e: if rng_required: raise e return patches
for connection in connections.values(): connection.pop(node, None) if area.default_node == node.name: has_default_node = False areas.append( Area( name=area.name, default_node=area.default_node if has_default_node else None, valid_starting_location=area.valid_starting_location, nodes=nodes, connections=connections, extra=area.extra, )) worlds.append(dataclasses.replace(world, areas=areas)) return GameDescription( game=game.game, resource_database=game.resource_database, layers=game.layers, dock_weakness_database=game.dock_weakness_database, world_list=WorldList(worlds), victory_condition=game.victory_condition, starting_location=game.starting_location, initial_states=game.initial_states, minimal_logic=game.minimal_logic, )