def collect_resource_node(self, node: ResourceNode, new_energy: int) -> "State": """ Creates a new State that has the given ResourceNode collected. :param node: :param new_energy: How much energy you should have when collecting this resource :return: """ if not node.can_collect(self.patches, self.resources): raise ValueError( "Trying to collect an uncollectable node'{}'".format(node)) new_resources = copy.copy(self.resources) add_resource_gain_to_current_resources( node.resource_gain_on_collect(self.patches, self.resources), new_resources) energy = new_energy if _energy_tank_difference(new_resources, self.resources, self.resource_database) > 0: energy = self._energy_for(new_resources) return State(new_resources, self.collected_resource_nodes + (node, ), energy, self.node, self.patches, self, self.resource_database)
def calculate_starting_state(game: GameDescription, patches: GamePatches) -> "State": starting_node = game.world_list.resolve_teleporter_connection( patches.starting_location) initial_resources = copy.copy(patches.starting_items) if isinstance(starting_node, PlayerShipNode): initial_resources[starting_node.resource()] = 1 initial_game_state = game.initial_states.get("Default") if initial_game_state is not None: add_resource_gain_to_current_resources(initial_game_state, initial_resources) starting_state = State( initial_resources, (), 99 + (100 * initial_resources.get(game.resource_database.energy_tank, 0)), starting_node, patches, None, game.resource_database, game.world_list, ) # Being present with value 0 is troublesome since this dict is used for a simplify_requirements later on keys_to_remove = [ resource for resource, quantity in initial_resources.items() if quantity == 0 ] for resource in keys_to_remove: del initial_resources[resource] return starting_state
def add_artifacts( resource_database: ResourceDatabase, mode: LayoutArtifactMode, ) -> PoolResults: """ :param resource_database: :param mode :return: """ item_pool: List[PickupEntry] = [] initial_resources: CurrentResources = {} artifacts_to_place = mode.value for i in range(artifacts_to_place): item_pool.append(pickup_creator.create_artifact(i, resource_database)) first_automatic_artifact = artifacts_to_place for automatic_artifact in range(first_automatic_artifact, 12): resource_info.add_resource_gain_to_current_resources( pickup_creator.create_artifact(automatic_artifact, resource_database).all_resources, initial_resources) return PoolResults(item_pool, {}, initial_resources)
def add_pickup_to_state(state: State, pickup: PickupEntry): """ Modifies inplace the given state, adding the resources of the given pickup :param state: :param pickup: :return: """ add_resource_gain_to_current_resources(pickup.resource_gain(state.resources, force_lock=True), state.resources)
def assign_pickup_resources(self, pickup: PickupEntry) -> "State": new_resources = copy.copy(self.resources) add_resource_gain_to_current_resources( pickup.resource_gain(self.resources), new_resources) energy = self.energy if _energy_tank_difference(new_resources, self.resources, self.resource_database) > 0: energy = self._energy_for(new_resources) return State(new_resources, self.collected_resource_nodes, energy, self.node, self.patches, self, self.resource_database)
def state_for_current_configuration(self) -> Optional[State]: state = self._initial_state.copy() state.node = self._actions[-1] 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.patches, state.resources), state.resources) return state
def assign_pickup_to_index(self, pickup: PickupEntry, index: PickupIndex) -> "State": new_patches = self.patches.assign_new_pickups([(index, pickup)]) new_resources = copy.copy(self.resources) if index in self.resources: add_resource_gain_to_current_resources( pickup.resource_gain(self.resources), new_resources) energy = self.energy if _energy_tank_difference(new_resources, self.resources, self.resource_database) > 0: energy = _energy_for(new_resources, self.resource_database) return State(new_resources, self.collected_resource_nodes, energy, self.node, new_patches, self, self.resource_database)
def add_major_items( resource_database: ResourceDatabase, major_items_configuration: MajorItemsConfiguration, ammo_configuration: AmmoConfiguration, ) -> PoolResults: """ :param resource_database: :param major_items_configuration: :param ammo_configuration: :return: """ item_pool: List[PickupEntry] = [] new_assignment: Dict[PickupIndex, PickupEntry] = {} initial_resources: CurrentResources = {} for item, state in major_items_configuration.items_state.items(): if len(item.ammo_index) != len(state.included_ammo): raise InvalidConfiguration( "Item {0.name} uses {0.ammo_index} as ammo, but there's only {1} values in included_ammo" .format(item, len(state.included_ammo))) ammo, locked_ammo = _find_ammo_for(item.ammo_index, ammo_configuration) if state.include_copy_in_original_location: if item.original_index is None: raise InvalidConfiguration( "Item {0.name} does not exist in the original game, cannot use state {1}" .format(item, state), ) new_assignment[item.original_index] = create_major_item( item, state, True, resource_database, ammo, locked_ammo) for _ in range(state.num_shuffled_pickups): item_pool.append( create_major_item(item, state, True, resource_database, ammo, locked_ammo)) for _ in range(state.num_included_in_starting_items): add_resource_gain_to_current_resources( create_major_item(item, state, False, resource_database, ammo, locked_ammo).all_resources, initial_resources) return PoolResults(item_pool, new_assignment, initial_resources)
def assign_pickups_resources(self, pickups: Iterator[PickupEntry]) -> "State": new_resources = copy.copy(self.resources) for pickup in pickups: add_resource_gain_to_current_resources(pickup.resource_gain(self.resources, force_lock=True), new_resources) energy = self.energy if _energy_tank_difference(new_resources, self.resources, self.resource_database) > 0: energy = self._energy_for(new_resources) return State( new_resources, self.collected_resource_nodes, energy, self.node, self.patches, self, self.game_data, )
def test_add_resource_gain_to_current_resources_convert(blank_pickup): # Setup resource_a = ItemResourceInfo("A", "A", 10, None) resource_b = ItemResourceInfo("B", "B", 10, None) pickup = dataclasses.replace( blank_pickup, progression=(), resource_lock=ResourceLock(resource_b, resource_b, resource_a), unlocks_resource=True, ) current_resources = {resource_a: 5} # Run add_resource_gain_to_current_resources( pickup.resource_gain(current_resources), current_resources) # Assert assert current_resources == {resource_a: 0, resource_b: 5}
def test_add_resource_gain_to_current_resources_convert(): # Setup resource_a = SimpleResourceInfo(1, "A", "A", ResourceType.ITEM) resource_b = SimpleResourceInfo(2, "B", "B", ResourceType.ITEM) pickup = PickupEntry(name="P1", model_index=1, item_category=ItemCategory.SUIT, resources=(ConditionalResources(None, None, ()), ), convert_resources=(ResourceConversion( resource_a, resource_b), )) current_resources = {resource_a: 5} # Run add_resource_gain_to_current_resources( pickup.resource_gain(current_resources), current_resources) # Assert assert current_resources == {resource_a: 0, resource_b: 5}
def add_sky_temple_key_distribution_logic( resource_database: ResourceDatabase, mode: LayoutSkyTempleKeyMode, ) -> PoolResults: """ Adds the given Sky Temple Keys to the item pool :param resource_database: :param mode: :return: """ item_pool: List[PickupEntry] = [] new_assignment: Dict[PickupIndex, PickupEntry] = {} initial_resources: CurrentResources = {} if mode == LayoutSkyTempleKeyMode.ALL_BOSSES or mode == LayoutSkyTempleKeyMode.ALL_GUARDIANS: locations_to_place = _GUARDIAN_INDICES[:] if mode == LayoutSkyTempleKeyMode.ALL_BOSSES: locations_to_place += _SUB_GUARDIAN_INDICES for key_number, location in enumerate(locations_to_place): new_assignment[location] = create_sky_temple_key( key_number, resource_database) first_automatic_key = len(locations_to_place) else: keys_to_place = mode.value if not isinstance(keys_to_place, int): raise InvalidConfiguration( "Unknown Sky Temple Key mode: {}".format(mode)) for key_number in range(keys_to_place): item_pool.append( create_sky_temple_key(key_number, resource_database)) first_automatic_key = keys_to_place for automatic_key_number in range(first_automatic_key, 9): add_resource_gain_to_current_resources( create_sky_temple_key(automatic_key_number, resource_database).all_resources, initial_resources) return PoolResults(item_pool, new_assignment, initial_resources)
def calculate_starting_state(self, game: GameDescription, patches: GamePatches, configuration: BaseConfiguration) -> "State": starting_node = game.world_list.resolve_teleporter_connection( patches.starting_location) initial_resources = copy.copy(patches.starting_items) starting_energy, energy_per_tank = self.energy_config(configuration) if starting_node.is_resource_node: assert isinstance(starting_node, ResourceNode) add_resource_gain_to_current_resources( starting_node.resource_gain_on_collect( NodeContext( patches, initial_resources, game.resource_database, game.world_list, )), initial_resources, ) initial_game_state = game.initial_states.get("Default") if initial_game_state is not None: add_resource_gain_to_current_resources(initial_game_state, initial_resources) starting_state = State( initial_resources, (), None, starting_node, patches, None, StateGameData(game.resource_database, game.world_list, energy_per_tank, starting_energy)) # Being present with value 0 is troublesome since this dict is used for a simplify_requirements later on keys_to_remove = [ resource for resource, quantity in initial_resources.items() if quantity == 0 ] for resource in keys_to_remove: del initial_resources[resource] return starting_state
async def _patches_for_pickup(self, provider_name: str, pickup: PickupEntry): inventory_resources: CurrentResources = { item: inv_item.capacity for item, inv_item in self._inventory.items() } conditional = pickup.conditional_for_resources(inventory_resources) if conditional.name is not None: item_name = conditional.name else: item_name = pickup.name resources_to_give = {} add_resource_gain_to_current_resources(conditional.resources, inventory_resources) add_resource_gain_to_current_resources(conditional.resources, resources_to_give) add_resource_gain_to_current_resources(pickup.conversion_resource_gain(inventory_resources), resources_to_give) # Ignore item% for received items resources_to_give.pop(self.game.resource_database.item_percentage, None) patches = [ all_prime_dol_patches.adjust_item_amount_and_capacity_patch(self.patches.powerup_functions, item.index, delta) for item, delta in resources_to_give.items() ] return patches, f"Received {item_name} from {provider_name}."
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(): assert combo.currentData() is not None state.patches.elevator_connection[teleporter] = combo.currentData() for gate, item in self._translator_gate_to_combo.items(): state.patches.translator_gates[gate] = item.currentData() 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.patches, state.resources), state.resources) return state
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 _resources_to_give_for_pickup( self, pickup: PickupEntry, inventory: Inventory) -> Tuple[str, CurrentResources]: inventory_resources: CurrentResources = { item: inv_item.capacity for item, inv_item in inventory.items() } conditional = pickup.conditional_for_resources(inventory_resources) if conditional.name is not None: item_name = conditional.name else: item_name = pickup.name resources_to_give = {} if pickup.respects_lock and not pickup.unlocks_resource and ( pickup.resource_lock is not None and inventory_resources.get( pickup.resource_lock.locked_by, 0) == 0): pickup_resources = list( pickup.resource_lock.convert_gain(conditional.resources)) item_name = f"Locked {item_name}" else: pickup_resources = conditional.resources add_resource_gain_to_current_resources(pickup_resources, inventory_resources) add_resource_gain_to_current_resources(pickup_resources, resources_to_give) add_resource_gain_to_current_resources( pickup.conversion_resource_gain(inventory_resources), resources_to_give) # Ignore item% for received items if self.game.resource_database.item_percentage is not None: resources_to_give.pop(self.game.resource_database.item_percentage, None) return item_name, resources_to_give
def test_missile_expansion_before_launcher(include_before, echoes_item_database, echoes_resource_database): # Setup ammo = echoes_item_database.ammo["Missile Expansion"] major_item = echoes_item_database.major_items["Missile Launcher"] missile = echoes_resource_database.get_item(ammo.items[0]) missile_launcher = echoes_resource_database.get_item(major_item.progression[0]) temporary = echoes_resource_database.get_item(ammo.temporary) percent = echoes_resource_database.item_percentage # Run expansion = pickup_creator.create_ammo_expansion(ammo, [5], True, echoes_resource_database) launcher = pickup_creator.create_major_item( major_item, MajorItemState(included_ammo=(5,)), True, echoes_resource_database, ammo, True ) resources = {} if include_before: # Ammo Expansion add_resource_gain_to_current_resources(expansion.resource_gain(resources, force_lock=True), resources) assert resources == {percent: 1, temporary: 5} # Add Launcher add_resource_gain_to_current_resources(launcher.resource_gain(resources, force_lock=True), resources) if include_before: assert resources == {percent: 2, temporary: 0, missile_launcher: 1, missile: 10} else: assert resources == {percent: 1, temporary: 0, missile_launcher: 1, missile: 5} # Ammo Expansion add_resource_gain_to_current_resources(expansion.resource_gain(resources, force_lock=True), resources) if include_before: assert resources == {percent: 3, temporary: 0, missile_launcher: 1, missile: 15} else: assert resources == {percent: 2, temporary: 0, missile_launcher: 1, missile: 10}
def _add_pickup_to_resources(pickup: PickupEntry, inventory: CurrentResources) -> CurrentResources: return add_resource_gain_to_current_resources( pickup.resource_gain(inventory), copy.copy(inventory))