def configurable_node_assignment( self, configuration: DreadConfiguration, game: GameDescription, rng: Random) -> Iterator[NodeConfigurationAssociation]: 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.simple(rsb.get_item("Screw")), "WEIGHT": Requirement.impossible(), "SPEEDBOOST": ResourceRequirement.simple(rsb.get_item("Speed")), } for node in game.world_list.iterate_nodes(): if not isinstance(node, ConfigurableNode): continue result.append((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 requirement_to_leave(self, context: NodeContext) -> Requirement: items = [] if self.scan_visor is not None: items.append(ResourceRequirement.simple(self.scan_visor)) if self.required_translator is not None: items.append(ResourceRequirement.simple(self.required_translator)) return RequirementAnd(items)
def test_connections_from_dock_blast_shield(empty_patches): # Setup trivial = Requirement.trivial() req_1 = ResourceRequirement.simple( SimpleResourceInfo(0, "Ev1", "Ev1", ResourceType.EVENT)) req_2 = ResourceRequirement.simple( SimpleResourceInfo(1, "Ev2", "Ev2", ResourceType.EVENT)) dock_type = DockType("Type", "Type", frozendict()) weak_1 = DockWeakness(0, "Weak 1", frozendict(), req_1, None) weak_2 = DockWeakness(1, "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, 0, False, None, "", ("default", ), {}, dock_type, node_2_identifier, weak_1, None, None) node_1_lock = DockLockNode.create_from_dock(node_1, 1) node_2 = DockNode(node_2_identifier, 2, False, None, "", ("default", ), {}, dock_type, node_1_identifier, weak_2, None, None) node_2_lock = DockLockNode.create_from_dock(node_2, 3) 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]) world_list.ensure_has_node_cache() game_mock = MagicMock() game_mock.world_list = world_list patches = dataclasses.replace(empty_patches, game=game_mock) context = NodeContext( patches=patches, current_resources=ResourceCollection(), database=patches.game.resource_database, node_provider=world_list, ) # Run result_1 = list(node_1.connections_from(context)) result_2 = list(node_2.connections_from(context)) # Assert simple = ResourceRequirement.simple assert result_1 == [ (node_2, RequirementAnd( [req_1, simple(NodeResourceInfo.from_node(node_2, context))])), (node_1_lock, RequirementAnd([trivial, req_2])), ] assert result_2 == [ (node_1, simple(NodeResourceInfo.from_node(node_2, context))), (node_2_lock, req_2), ]
def make_req(item_id: int): return RequirementAnd([ ResourceRequirement.simple( ItemResourceInfo(0, "Scan Visor", "Scan", 1, frozendict({"item_id": 9})), ), ResourceRequirement.simple( ItemResourceInfo(1, "Other", "Other", 1, frozendict({"item_id": item_id})), ), ])
def test_set_as_str_things(echoes_resource_database): item = echoes_resource_database.get_item_by_name req_set = RequirementSet([ RequirementList([ ResourceRequirement.simple(item("Screw Attack")), ResourceRequirement.simple(item("Space Jump Boots")), ]), RequirementList([ ResourceRequirement.simple(item("Power Bomb")), ]), ]) 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) -> 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 _requirement_from_back(context: NodeContext, target_node: Node) -> typing.Optional[ResourceRequirement]: if isinstance(target_node, DockNode): weak = context.patches.get_dock_weakness_for(target_node) if weak.lock is not None: return ResourceRequirement.simple(NodeResourceInfo.from_node(target_node, context)) return None
def read_resource_requirement( data: Dict, resource_database: ResourceDatabase) -> ResourceRequirement: data = data["data"] return ResourceRequirement.with_data(resource_database, ResourceType.from_str(data["type"]), data["name"], data["amount"], data["negate"])
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.simple(item("Dark Visor")), ResourceRequirement.create(item("Missile"), 5, False), ResourceRequirement.simple(item("Seeker Launcher")), ]) extract = [(req.resource.long_name, req.amount) for req in req_list.values()] assert sorted(extract) == [ ("Dark Visor", 1), ("Missile", 5), ("Seeker Launcher", 1), ]
def test_node_resource_info_as_requirement(blank_game_description): db = blank_game_description.resource_database node = blank_game_description.world_list.all_nodes[0] context = NodeContext(None, None, db, blank_game_description.world_list) nri = NodeResourceInfo.from_node req = ResourceRequirement.simple(nri(node, context)) assert not req.satisfied(_empty_col(), 0, db) assert req.satisfied(_col_for(db, nri(node, context)), 0, db)
def test_set_hash(echoes_resource_database): req_set_a = RequirementSet([ RequirementList([ ResourceRequirement.simple( echoes_resource_database.get_item_by_name("Power Bomb")), ]), ]) req_set_b = RequirementSet([ RequirementList([ ResourceRequirement.simple( echoes_resource_database.get_item_by_name("Power Bomb")), ]), ]) 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 test_simple_echoes_damage(echoes_resource_database): db = echoes_resource_database req = ResourceRequirement.create( 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(_empty_col(), db) == 50 assert req.damage(_col_for(db, d_suit), db) == 11 assert req.damage(_col_for(db, l_suit), db) == 0
def _open_dock_connection(self, context: NodeContext, target_node: Node, ) -> tuple[Node, Requirement]: forward_weakness = self.get_front_weakness(context) reqs: list[Requirement] = [self._get_open_requirement(context, forward_weakness)] # This dock has a lock, so require it if forward_weakness.lock is not None: reqs.append(ResourceRequirement.simple(NodeResourceInfo.from_node(self, context))) # The other dock has a lock, so require it if (other_lock_req := _requirement_from_back(context, target_node)) is not None: reqs.append(other_lock_req)
def state_for_current_configuration(self) -> Optional[State]: state = self._initial_state.copy() if self._actions: state.node = self._actions[-1] state.patches = state.patches.assign_elevators( (state.world_list.get_teleporter_node(teleporter), combo.currentData()) for teleporter, combo in self._elevator_id_to_combo.items()) 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.simple(translator) state.patches.configurable_nodes[gate] = RequirementAnd([ ResourceRequirement.simple(scan_visor), 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: state.resources.add_resource_gain( node.resource_gain_on_collect(state.node_context())) return state
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) dangerous_extra = [ ResourceRequirement.simple(resource) for resource, quantity in node.resource_gain_on_collect(context) if resource in game.dangerous_resources ] if dangerous_extra: extra_requirement = RequirementAnd(dangerous_extra) return extra_requirement
def test_pretty_print_requirement_array_combinable( mock_print_requirement: MagicMock, echoes_resource_database): mock_print_requirement.return_value = ["a", "b"] array = RequirementAnd([ ResourceRequirement.simple(echoes_resource_database.item[0]), 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_list_dangerous_resources(database, input_data, output_data): # setup req_list = RequirementList( (ResourceRequirement.create(database.resource_by_index[item[0]], 1, item[1]) for item in input_data)) expected_result = { database.resource_by_index[item] for item in output_data } # run result = set(req_list.dangerous_resources) # assert assert result == expected_result
def _unsatisfied_item_requirements_in_list( alternative: RequirementList, state: State, uncollected_resources: set[ResourceInfo]): possible = True items = [] damage = [] for individual in alternative.values(): 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.create(state.resource_database.energy_tank, tank_count + 1, False) ] # FIXME: get the required items for reductions (aka suits) else: yield items
def current_requirement(self) -> ResourceRequirement: resource_type = self.resource_type # Quantity if resource_type == ResourceType.TRICK: quantity: int = self.amount_combo.currentData() elif resource_type == ResourceType.EVENT: quantity = 1 else: quantity = int(self.amount_edit.text()) # Negate flag if resource_type == ResourceType.ITEM: negate: bool = self.negate_combo.currentData() elif resource_type == ResourceType.EVENT: negate = self.negate_check.isChecked() else: negate = False return ResourceRequirement.create( self.resource_name_combo.currentData(), quantity, negate)
def test_sort_resource_requirement(blank_game_description): db = blank_game_description.resource_database node = blank_game_description.world_list.all_nodes[0] assert node is not None resources = [ NodeResourceInfo.from_node( node, NodeContext(None, None, db, blank_game_description.world_list)), SimpleResourceInfo(1, "Resource", "Resource", ResourceType.MISC), TrickResourceInfo(2, "Trick", "Trick", "Long Description"), ItemResourceInfo(3, "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 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.simple(db.get_item_by_name("Varia Suit")) 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 test_requirement_set_constructor(echoes_resource_database): item = echoes_resource_database.get_item_by_name req_set = RequirementSet([ RequirementList([ ResourceRequirement.simple(item("Dark Visor")), ResourceRequirement.create(item("Missile"), 5, False), ResourceRequirement.simple(item("Seeker Launcher")), ]), RequirementList([ ResourceRequirement.simple(item("Screw Attack")), ResourceRequirement.simple(item("Space Jump Boots")), ]), RequirementList([ ResourceRequirement.simple(item("Power Bomb")), ResourceRequirement.simple(item("Boost Ball")), ]), ]) extract = [ sorted((req.resource.long_name, req.amount) for req in req_list.values()) for req_list in req_set.alternatives ] assert sorted(extract) == [ [ ("Boost Ball", 1), ("Power Bomb", 1), ], [ ("Dark Visor", 1), ("Missile", 5), ("Seeker Launcher", 1), ], [ ("Screw Attack", 1), ("Space Jump Boots", 1), ], ]
def _req(name: str): id_req = ResourceRequirement.simple(database.get_item(name)) return id_req
def make_req_c(db: ResourceDatabase): res = db.item[2] return res, ResourceRequirement.simple(res)
def mk_req(name: str): return ResourceRequirement.with_data(echoes_resource_database, ResourceType.ITEM, name, 1, False)
def _patches_with_data(request, echoes_game_description, echoes_game_patches, echoes_item_database): game = echoes_game_description db = game.resource_database data = { "game": echoes_game_description.game.value, "starting_location": "Temple Grounds/Landing Site", "starting_items": {}, "teleporters": { "Temple Grounds/Temple Transport C/Elevator to Great Temple - Temple Transport C": "Great Temple/Temple Transport C", "Temple Grounds/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Temple Grounds": "Agon Wastes/Transport to Temple Grounds", "Temple Grounds/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Temple Grounds": "Torvus Bog/Transport to Temple Grounds", "Temple Grounds/Temple Transport B/Elevator to Great Temple - Temple Transport B": "Great Temple/Temple Transport B", "Temple Grounds/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Temple Grounds": "Sanctuary Fortress/Transport to Temple Grounds", "Temple Grounds/Temple Transport A/Elevator to Great Temple - Temple Transport A": "Great Temple/Temple Transport A", "Great Temple/Temple Transport A/Elevator to Temple Grounds - Temple Transport A": "Temple Grounds/Temple Transport A", "Great Temple/Temple Transport C/Elevator to Temple Grounds - Temple Transport C": "Temple Grounds/Temple Transport C", "Great Temple/Temple Transport B/Elevator to Temple Grounds - Temple Transport B": "Temple Grounds/Temple Transport B", "Temple Grounds/Sky Temple Gateway/Teleport to Great Temple - Sky Temple Energy Controller": "Great Temple/Sky Temple Energy Controller", "Great Temple/Sky Temple Energy Controller/Teleport to Temple Grounds - Sky Temple Gateway": "Temple Grounds/Sky Temple Gateway", "Agon Wastes/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Agon Wastes": "Temple Grounds/Transport to Agon Wastes", "Agon Wastes/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Agon Wastes": "Torvus Bog/Transport to Agon Wastes", "Agon Wastes/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Agon Wastes": "Sanctuary Fortress/Transport to Agon Wastes", "Torvus Bog/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Torvus Bog": "Temple Grounds/Transport to Torvus Bog", "Torvus Bog/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Torvus Bog": "Agon Wastes/Transport to Torvus Bog", "Torvus Bog/Transport to Sanctuary Fortress/Elevator to Sanctuary Fortress - Transport to Torvus Bog": "Sanctuary Fortress/Transport to Torvus Bog", "Sanctuary Fortress/Transport to Temple Grounds/Elevator to Temple Grounds - Transport to Sanctuary Fortress": "Temple Grounds/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Agon Wastes/Elevator to Agon Wastes - Transport to Sanctuary Fortress": "Agon Wastes/Transport to Sanctuary Fortress", "Sanctuary Fortress/Transport to Torvus Bog/Elevator to Torvus Bog - Transport to Sanctuary Fortress": "Torvus Bog/Transport to Sanctuary Fortress", "Sanctuary Fortress/Aerie/Elevator to Sanctuary Fortress - Aerie Transport Station": "Sanctuary Fortress/Aerie Transport Station", "Sanctuary Fortress/Aerie Transport Station/Elevator to Sanctuary Fortress - Aerie": "Sanctuary Fortress/Aerie", }, "dock_weakness": {}, "configurable_nodes": {}, "locations": {}, "hints": {} } patches = dataclasses.replace(echoes_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([ (db.get_item_by_name(item_name), 1), ]) data["starting_items"][item_name] = 1 if request.param.get("elevator"): teleporter: NodeIdentifier = request.param.get("elevator") patches = patches.assign_elevators([ (game.world_list.get_teleporter_node(teleporter), game.starting_location), ]) data["teleporters"][ teleporter.as_string] = "Temple Grounds/Landing Site" if request.param.get("configurable_nodes"): gates = [] for identifier, translator in request.param.get("configurable_nodes"): requirement = ResourceRequirement.simple(db.get_item(translator)) gates.append((NodeIdentifier.from_string(identifier), requirement)) data["configurable_nodes"][ identifier] = data_writer.write_requirement(requirement) patches = patches.assign_node_configuration(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"): identifier, hint = request.param.get("hint") patches = patches.assign_hint(NodeIdentifier.from_string(identifier), Hint.from_json(hint)) data["hints"][identifier] = hint return data, patches
def requirement_to_leave(self, context: NodeContext) -> Requirement: if context.current_resources.add_self_as_requirement_to_resources: return ResourceRequirement.simple(self.event) else: return Requirement.trivial()
def _create_default_resource_requirement( resource_database: ResourceDatabase) -> ResourceRequirement: return ResourceRequirement.simple( resource_database.get_by_type(ResourceType.ITEM)[0], )
def _make_req(name: str): req = database.get_item(name) id_req = ResourceRequirement.simple(req) return req, id_req
def current_individual(self) -> ResourceRequirement: return ResourceRequirement.create( self.resource_name_combo.currentData(), int(self.amount_edit.text()), self.negate_combo.currentData())