def assign_pickup_to_starting_items(self, pickup: PickupEntry) -> "State": pickup_resources = ResourceCollection.from_resource_gain( self.resource_database, pickup.resource_gain(self.resources, force_lock=True)) # Make sure there's no item percentage on starting items if self.resource_database.item_percentage is not None: pickup_resources.remove_resource( self.resource_database.item_percentage) new_resources = self.resources.duplicate() new_resources.add_resource_gain(pickup_resources.as_resource_gain()) new_patches = self.patches.assign_extra_initial_items( pickup_resources.as_resource_gain()) tank_delta = _energy_tank_difference(new_resources, self.resources, self.resource_database) return State( new_resources, self.collected_resource_nodes, self.energy + tank_delta * self.game_data.energy_per_tank, self.node, new_patches, self, self.game_data, )
def find_area_errors(game: GameDescription, area: Area) -> Iterator[str]: nodes_with_paths_in = set() for node in area.nodes: nodes_with_paths_in.update(area.connections[node].keys()) for error in find_node_errors(game, node): yield f"{area.name} - {error}" if node in area.connections.get(node, {}): yield f"{area.name} - Node '{node.name}' has a connection to itself" if area.default_node is not None and area.node_with_name(area.default_node) is None: yield f"{area.name} has default node {area.default_node}, but no node with that name exists" # elif area.default_node is not None: # nodes_with_paths_in.add(area.node_with_name(area.default_node)) for node in area.nodes: if isinstance(node, (TeleporterNode, DockNode)) or area.connections[node]: continue # FIXME: cannot implement this for PickupNodes because their resource gain depends on GamePatches if isinstance(node, EventNode): # if this node would satisfy the victory condition, it does not need outgoing connections current = ResourceCollection.from_resource_gain(game.resource_database, node.resource_gain_on_collect(None)) if game.victory_condition.satisfied(current, 0, game.resource_database): continue if node in nodes_with_paths_in: yield f"{area.name} - '{node.name}': Node has paths in, but no connections out."
def find_locations_that_gives_items( target_items: List[ItemResourceInfo], all_patches: Dict[int, GamePatches], player: int, ) -> dict[ItemResourceInfo, list[tuple[int, PickupLocation]]]: result: dict[ItemResourceInfo, list[tuple[int, PickupLocation]]] = { item: [] for item in target_items } for other_player, patches in all_patches.items(): for pickup_index, target in patches.pickup_assignment.items(): if target.player != player: continue # TODO: iterate over all tiers of progression db = patches.game.resource_database resources = ResourceCollection.from_resource_gain( db, target.pickup.resource_gain(ResourceCollection())) for resource, quantity in resources.as_resource_gain(): if quantity > 0 and resource in result: result[resource].append( (other_player, PickupLocation(patches.configuration.game, pickup_index))) return result
def test_assign_extra_initial_items_to_empty(empty_patches, items): db = empty_patches.game.resource_database items = wrap(db, items) # Run new_patches = empty_patches.assign_extra_initial_items(items) # Assert assert new_patches.starting_items == ResourceCollection.from_resource_gain(db, items)
def test_logbook_node_requirements_to_leave(logbook_node, empty_patches): # Setup has_translator, translator, node = logbook_node db = empty_patches.game.resource_database def ctx(resources): return NodeContext(empty_patches, resources, db, empty_patches.game.world_list) # Run to_leave = node.requirement_to_leave(ctx({})) # Assert rc2 = ResourceCollection.from_resource_gain(db, []) rc3 = ResourceCollection.from_resource_gain(db, [(translator, 1)]) assert to_leave.satisfied(rc2, 99, None) != has_translator assert to_leave.satisfied(rc3, 99, None)
def test_convert_resource_gain_to_current_resources(blank_resource_db, resource_gain, expected): # Setup db = blank_resource_db resource_gain = wrap(db, resource_gain) expected = wrap(db, expected) # Run result = ResourceCollection.from_resource_gain(db, resource_gain) # Assert assert dict(result.as_resource_gain()) == expected
def pickups_to_solve_list(pickup_pool: list[PickupEntry], requirement_list: RequirementList, state: State): pickups = [] db = state.resource_database resources = state.resources.duplicate() pickups_for_this = list(pickup_pool) # Check pickups that give less items in total first # This means we test for expansions before the major items, in case both give the same resource # Useful to get Dark Beam Ammo Expansion instead of Dark Beam. pickups_for_this.sort(key=lambda p: sum( 1 for _ in p.resource_gain(resources, force_lock=True))) for individual in sorted(requirement_list.values()): if individual.satisfied(resources, state.energy, state.resource_database): continue # Create another copy of the list so we can remove elements while iterating for pickup in list(pickups_for_this): new_resources = ResourceCollection.from_resource_gain( db, pickup.resource_gain(resources, force_lock=True)) pickup_progression = ResourceCollection.from_resource_gain( db, pickup.progression) if new_resources[individual.resource] + pickup_progression[ individual.resource] > 0: pickups.append(pickup) pickups_for_this.remove(pickup) resources.add_resource_gain(new_resources.as_resource_gain()) if individual.satisfied(resources, state.energy, state.resource_database): break if not individual.satisfied(resources, state.energy, state.resource_database): return None return pickups
def _on_filters_changed(self): if self.edit_mode: game = self.original_game_description else: game = derived_nodes.remove_inactive_layers( self.original_game_description, self.layers_editor.selected_layers(), ) resources = self.layers_editor.selected_tricks() if resources: self._collection_for_filtering = ResourceCollection.from_resource_gain( game.resource_database, resources.items()) else: self._collection_for_filtering = None self.update_game(game)
def __init__( self, parent: QWidget, window_manager: WindowManager, preset: Preset, ): super().__init__(parent) self.setupUi(self) set_default_window_icon(self) self._window_manager = window_manager self._game_description = filtered_database.game_description_for_layout( preset.configuration) database = self._game_description.resource_database trick_level = preset.configuration.trick_level if trick_level.minimal_logic: trick_usage_description = "Minimal Logic" else: trick_usage_description = ", ".join( sorted( f"{trick.long_name} ({trick_level.level_for_trick(trick).long_name})" for trick in database.trick if trick_level.has_specific_level_for_trick(trick))) # setup self.area_list_label.linkActivated.connect( self._on_click_link_to_data_editor) self.setWindowTitle("{} for preset {}".format(self.windowTitle(), preset.name)) self.title_label.setText(self.title_label.text().format( trick_levels=trick_usage_description)) # connect self.button_box.accepted.connect(self.button_box_close) self.button_box.rejected.connect(self.button_box_close) if trick_level.minimal_logic: return # Update bootstrap = self._game_description.game.generator.bootstrap trick_resources = ResourceCollection.from_resource_gain( database, bootstrap.trick_resources_for_configuration(trick_level, database)) lines = [] for world in sorted(self._game_description.world_list.worlds, key=lambda it: it.name): for area in sorted(world.areas, key=lambda it: it.name): used_tricks = _check_used_tricks(area, trick_resources, database) if used_tricks: lines.append( f'<p><a href="data-editor://{world.correct_name(area.in_dark_aether)}/{area.name}">' f'{world.correct_name(area.in_dark_aether)} - {area.name}</a>' f'<br />{"<br />".join(used_tricks)}</p>') self.area_list_label.setText("".join(lines))