def requirement_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: return RequirementAnd([ ResourceRequirement(patches.translator_gates[self.gate], 1, False), ResourceRequirement(self.scan_visor, 1, False), ])
def configurable_node_assignment(self, configuration: DreadConfiguration, game: GameDescription, rng: Random) -> NodeConfigurationAssignment: result = {} rsb = game.resource_database requirement_for_type = { "POWERBEAM": rsb.requirement_template["Shoot Beam"], "BOMB": rsb.requirement_template["Lay Bomb"], "MISSILE": rsb.requirement_template["Shoot Missile"], "SUPERMISSILE": rsb.requirement_template["Shoot Super Missile"], "POWERBOMB": rsb.requirement_template["Lay Power Bomb"], "SCREWATTACK": ResourceRequirement(rsb.get_item("Screw"), 1, False), "WEIGHT": Requirement.impossible(), "SPEEDBOOST": ResourceRequirement(rsb.get_item("Speed"), 1, False), } for node in game.world_list.all_nodes: if not isinstance(node, ConfigurableNode): continue result[game.world_list.identifier_for_node(node)] = RequirementAnd([ requirement_for_type[block_type] for block_type in node.extra["tile_types"] ]).simplify() return result
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_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 requirement_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: items = [ResourceRequirement(self.scan_visor, 1, False)] if self.required_translator is not None: items.append( ResourceRequirement(self.required_translator, 1, False)) return RequirementAnd(items)
def make_req(item_id: int): return RequirementAnd([ ResourceRequirement( ItemResourceInfo("Scan Visor", "Scan", 1, frozendict({"item_id": 9})), 1, False, ), ResourceRequirement( ItemResourceInfo("Other", "Other", 1, frozendict({"item_id": item_id})), 1, False, ), ])
def requirement_to_leave(self, context: NodeContext) -> Requirement: items = [] if self.scan_visor is not None: items.append(ResourceRequirement(self.scan_visor, 1, False)) if self.required_translator is not None: items.append( ResourceRequirement(self.required_translator, 1, False)) return RequirementAnd(items)
def test_simple_echoes_damage(echoes_resource_database): db = echoes_resource_database req = ResourceRequirement( db.get_by_type_and_index(ResourceType.DAMAGE, "DarkWorld1"), 50, False, ) d_suit = db.get_item_by_name("Dark Suit") l_suit = db.get_item_by_name("Light Suit") assert req.damage({}, db) == 50 assert req.damage({d_suit: 1}, db) == 11 assert req.damage({l_suit: 1}, db) == 0
def test_connections_from_dock_blast_shield(empty_patches): # Setup trivial = Requirement.trivial() req_1 = ResourceRequirement( SimpleResourceInfo("Ev1", "Ev1", ResourceType.EVENT), 1, False) req_2 = ResourceRequirement( SimpleResourceInfo("Ev2", "Ev2", ResourceType.EVENT), 1, False) dock_type = DockType("Type", "Type", frozendict()) weak_1 = DockWeakness("Weak 1", frozendict(), req_1, None) weak_2 = DockWeakness("Weak 2", frozendict(), trivial, DockLock(DockLockType.FRONT_BLAST_BACK_BLAST, req_2)) node_1_identifier = NodeIdentifier.create("W", "Area 1", "Node 1") node_2_identifier = NodeIdentifier.create("W", "Area 2", "Node 2") node_1 = DockNode(node_1_identifier, False, None, "", ("default", ), {}, dock_type, node_2_identifier, weak_1, None, None) node_1_lock = DockLockNode.create_from_dock(node_1) node_2 = DockNode(node_2_identifier, False, None, "", ("default", ), {}, dock_type, node_1_identifier, weak_2, None, None) node_2_lock = DockLockNode.create_from_dock(node_2) area_1 = Area("Area 1", None, True, [node_1, node_1_lock], {}, {}) area_2 = Area("Area 2", None, True, [node_2, node_2_lock], {}, {}) world = World("W", [area_1, area_2], {}) world_list = WorldList([world]) context = NodeContext( patches=empty_patches, current_resources={}, database=None, node_provider=world_list, ) # Run result_1 = list(node_1.connections_from(context)) result_2 = list(node_2.connections_from(context)) # Assert assert result_1 == [ (node_2, RequirementAnd([req_1, ResourceRequirement.simple(node_2_identifier)])), (node_1_lock, RequirementAnd([trivial, req_2])), ] assert result_2 == [ (node_1, ResourceRequirement.simple(node_2_identifier)), (node_2_lock, req_2), ]
def test_set_as_str_things(echoes_resource_database): item = echoes_resource_database.get_item_by_name req_set = RequirementSet([ RequirementList([ ResourceRequirement(item("Screw Attack"), 1, False), ResourceRequirement(item("Space Jump Boots"), 1, False), ]), RequirementList([ ResourceRequirement(item("Power Bomb"), 1, False), ]), ]) assert req_set.as_str == "(Power Bomb ≥ 1) or (Screw Attack ≥ 1, Space Jump Boots ≥ 1)"
def configurable_node_assignment( self, configuration: EchoesConfiguration, game: GameDescription, rng: Random) -> NodeConfigurationAssignment: """ :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(scan_visor, 1, False) for node in game.world_list.all_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[identifier] = RequirementAnd([ scan_visor_req, ResourceRequirement(translator, 1, False), ]) return result
def requirement_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: if current_resources.get("add_self_as_requirement_to_resources") == 1: return ResourceRequirement(self.event, 1, False) else: return Requirement.trivial()
def patch_resource_database( self, db: ResourceDatabase, configuration: BaseConfiguration) -> ResourceDatabase: base_damage_reduction = db.base_damage_reduction damage_reductions = copy.copy(db.damage_reductions) requirement_template = copy.copy(db.requirement_template) suits = [db.get_item_by_name("Varia Suit")] if configuration.heat_protection_only_varia: requirement_template["Heat-Resisting Suit"] = ResourceRequirement( db.get_item_by_name("Varia Suit"), 1, False) else: suits.extend([ db.get_item_by_name("Gravity Suit"), db.get_item_by_name("Phazon Suit") ]) reductions = [DamageReduction(None, configuration.heat_damage / 10.0)] reductions.extend([DamageReduction(suit, 0) for suit in suits]) damage_reductions[db.get_by_type_and_index(ResourceType.DAMAGE, "HeatDamage1")] = reductions if configuration.progressive_damage_reduction: base_damage_reduction = self.prime1_progressive_damage_reduction else: base_damage_reduction = self.prime1_absolute_damage_reduction return dataclasses.replace(db, damage_reductions=damage_reductions, base_damage_reduction=base_damage_reduction, requirement_template=requirement_template)
def read_resource_requirement(data: Dict, resource_database: ResourceDatabase ) -> ResourceRequirement: data = data["data"] return ResourceRequirement.with_data( resource_database, ResourceType(data["type"]), data["index"], data["amount"], data["negate"])
def requirement_to_leave(self, context: NodeContext) -> Requirement: # FIXME: using non-resource as key in CurrentResources if context.current_resources.get( "add_self_as_requirement_to_resources") == 1: return ResourceRequirement(self.pickup_index, 1, False) else: return Requirement.trivial()
def test_node_identifier_as_requirement(): nic = NodeIdentifier.create req = ResourceRequirement.simple(nic("W", "A", "N")) db = typing.cast(ResourceDatabase, None) assert not req.satisfied({}, 0, db) assert req.satisfied({nic("W", "A", "N"): 1}, 0, db)
def test_requirement_list_constructor(echoes_resource_database): def item(name): return search.find_resource_info_with_long_name( echoes_resource_database.item, name) req_list = RequirementList([ ResourceRequirement(item("Dark Visor"), 1, False), ResourceRequirement(item("Missile"), 5, False), ResourceRequirement(item("Seeker Launcher"), 1, False), ]) extract = [(req.resource.long_name, req.amount) for req in req_list.items] assert sorted(extract) == [ ("Dark Visor", 1), ("Missile", 5), ("Seeker Launcher", 1), ]
def requirement_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> Requirement: # FIXME: using non-resource as key in CurrentResources if current_resources.get("add_self_as_requirement_to_resources") == 1: return ResourceRequirement(self.pickup_index, 1, False) else: return Requirement.trivial()
def _requirement_from_back( context: NodeContext, identifier: NodeIdentifier) -> typing.Optional[ResourceRequirement]: target_node = context.node_provider.node_by_identifier(identifier) if isinstance(target_node, DockNode): weak = context.patches.dock_weakness.get( identifier, target_node.default_dock_weakness) if weak.lock is not None: return ResourceRequirement.simple(identifier) return None
def _extra_requirement_for_node(game: GameDescription, node: Node) -> Optional[Requirement]: extra_requirement = None if node.is_resource_node: resource_node: ResourceNode = node node_resource = resource_node.resource() if node_resource in game.dangerous_resources: extra_requirement = ResourceRequirement(node_resource, 1, False) return extra_requirement
def _extra_requirement_for_node(game: GameDescription, context: NodeContext, node: Node) -> Optional[Requirement]: extra_requirement = None if node.is_resource_node: assert isinstance(node, ResourceNode) node_resource = node.resource(context) if node_resource in game.dangerous_resources: extra_requirement = ResourceRequirement(node_resource, 1, False) return extra_requirement
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 test_list_dangerous_resources(input_data, output_data): # setup req_list = RequirementList( (ResourceRequirement(_make_resource(str(item[0])), 1, item[1]) for item in input_data)) expected_result = {_make_resource(str(item)) for item in output_data} # run result = set(req_list.dangerous_resources) # assert assert result == expected_result
def test_set_hash(echoes_resource_database): req_set_a = RequirementSet([ RequirementList([ ResourceRequirement( echoes_resource_database.get_item_by_name("Power Bomb"), 1, False), ]), ]) req_set_b = RequirementSet([ RequirementList([ ResourceRequirement( echoes_resource_database.get_item_by_name("Power Bomb"), 1, False), ]), ]) assert req_set_a == req_set_b assert req_set_a is not req_set_b hash_a = hash(req_set_a) hash_b = hash(req_set_b) assert hash_a == hash_b assert hash_a == req_set_a._cached_hash
def state_for_current_configuration(self) -> Optional[State]: state = self._initial_state.copy() if self._actions: state.node = self._actions[-1] for teleporter, combo in self._elevator_id_to_combo.items(): state.patches.elevator_connection[teleporter] = combo.currentData() for gate, item in self._translator_gate_to_combo.items(): scan_visor = self.game_description.resource_database.get_item( "Scan") requirement: Optional[ LayoutTranslatorRequirement] = item.currentData() if requirement is None: translator_req = Requirement.impossible() else: translator = self.game_description.resource_database.get_item( requirement.item_name) translator_req = ResourceRequirement(translator, 1, False) state.patches.configurable_nodes[gate] = RequirementAnd([ ResourceRequirement(scan_visor, 1, False), translator_req, ]) for pickup, quantity in self._collected_pickups.items(): for _ in range(quantity): add_pickup_to_state(state, pickup) for node in self._collected_nodes: add_resource_gain_to_current_resources( node.resource_gain_on_collect(state.node_context()), state.resources) return state
def test_list_dangerous_resources(input_data, output_data): # setup req_list = RequirementList( (ResourceRequirement(SimpleResourceInfo(item[0], str(item[0]), str(item[0]), ""), 1, item[1]) for item in input_data)) expected_result = { SimpleResourceInfo(item, str(item), str(item), "") for item in output_data } # run result = set(req_list.dangerous_resources) # assert assert result == expected_result
def test_pretty_print_requirement_array_combinable( mock_print_requirement: MagicMock, echoes_resource_database): mock_print_requirement.return_value = ["a", "b"] array = RequirementAnd([ ResourceRequirement(echoes_resource_database.item[0], 1, False), RequirementTemplate("Shoot Sunburst"), ]) # Run result = list(pretty_print.pretty_print_requirement_array(array, 3)) # Assert assert result == [(3, "Power Beam and Shoot Sunburst")] mock_print_requirement.assert_not_called()
def test_sort_resource_requirement(): resources = [ NodeIdentifier.create("World", "Area", "Node"), PickupIndex(10), _make_resource("Resource"), TrickResourceInfo("Trick", "Trick", "Long Description"), ItemResourceInfo("Item", "Item", 1), ] # Assert resources has an entry for every type of ResourceInfo assert {type(it) for it in resources} == set(ResourceInfo.__args__) assert len(resources) == len(ResourceInfo.__args__) requirements = [ResourceRequirement.simple(it) for it in resources] result = sorted(requirements) assert result == list(reversed(requirements))
def test_get_pickups_that_solves_unreachable(echoes_game_description, mocker): def item(name): return search.find_resource_info_with_long_name( echoes_game_description.resource_database.item, name) mock_req_lists: MagicMock = mocker.patch( "randovania.generator.filler.pickup_list._requirement_lists_without_satisfied_resources" ) pickups_left = [] reach = MagicMock() reach.state.resources = {} reach.state.energy = 100 possible_set = MagicMock() reach.unreachable_nodes_with_requirements.return_value = { "foo": possible_set } uncollected_resource_nodes = [MagicMock()] mock_req_lists.return_value = { RequirementList([ ResourceRequirement(item("Dark Visor"), 1, False), ResourceRequirement(item("Missile"), 5, False), ]), RequirementList([ ResourceRequirement(item("Screw Attack"), 1, False), ]), RequirementList([ ResourceRequirement(item("Power Bomb"), 1, False), ResourceRequirement(item("Boost Ball"), 1, False), ]), RequirementList([ ResourceRequirement(item("Spider Ball"), 1, False), ResourceRequirement(item("Boost Ball"), 1, False), ]), } # Run result = pickup_list.get_pickups_that_solves_unreachable( pickups_left, reach, uncollected_resource_nodes) # Assert mock_req_lists.assert_called_once_with( reach.state, [possible_set], [uncollected_resource_nodes[0].resource.return_value]) assert result == tuple()
def _unsatisfied_item_requirements_in_list( alternative: RequirementList, state: State, uncollected_resources: List[ResourceInfo]): possible = True items = [] damage = [] for individual in alternative.items: if individual.negate: possible = False break if individual.resource.resource_type == ResourceType.DAMAGE: damage.append(individual) continue if individual.satisfied(state.resources, state.energy, state.resource_database): continue if individual.resource.resource_type != ResourceType.ITEM: if individual.resource not in uncollected_resources: possible = False break items.append(individual) if not possible: return sum_damage = sum( req.damage(state.resources, state.resource_database) for req in damage) if state.energy < sum_damage: tank_count = (sum_damage - state.energy) // state.game_data.energy_per_tank yield items + [ ResourceRequirement(state.resource_database.energy_tank, tank_count + 1, False) ] # FIXME: get the required items for reductions (aka suits) else: yield items