mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	Pokemon Emerald: Fix pre-fill problems (#4686)
Co-authored-by: Mysteryem <Mysteryem@users.noreply.github.com>
This commit is contained in:
		| @@ -14,6 +14,9 @@ _not_ used for logical access (the seed will never require you to catch somethin | ||||
|  | ||||
| - Now excludes the location "Navel Rock Top - Hidden Item Sacred Ash" if your goal is Champion and you didn't randomize | ||||
| event tickets. | ||||
| - Fixed handling of shuffle option for badges/HMs in the case that the player sets those items to nonlocal or uses | ||||
| plando to put an item in one of those locations, or in the case that fill gets itself stuck on these items and has to | ||||
| retry. | ||||
|  | ||||
| # 2.3.0 | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import os | ||||
| import pkgutil | ||||
| from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union | ||||
|  | ||||
| from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType | ||||
| from BaseClasses import CollectionState, ItemClassification, MultiWorld, Tutorial, LocationProgressType | ||||
| from Fill import FillError, fill_restrictive | ||||
| from Options import OptionError, Toggle | ||||
| import settings | ||||
| @@ -100,6 +100,7 @@ class PokemonEmeraldWorld(World): | ||||
|  | ||||
|     required_client_version = (0, 4, 6) | ||||
|  | ||||
|     item_pool: List[PokemonEmeraldItem] | ||||
|     badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] | ||||
|     hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] | ||||
|     free_fly_location_id: int | ||||
| @@ -185,7 +186,7 @@ class PokemonEmeraldWorld(World): | ||||
|  | ||||
|         # In race mode we don't patch any item location information into the ROM | ||||
|         if self.multiworld.is_race and not self.options.remote_items: | ||||
|             logging.warning("Pokemon Emerald: Forcing Player %s (%s) to use remote items due to race mode.", | ||||
|             logging.warning("Pokemon Emerald: Forcing player %s (%s) to use remote items due to race mode.", | ||||
|                             self.player, self.player_name) | ||||
|             self.options.remote_items.value = Toggle.option_true | ||||
|  | ||||
| @@ -197,7 +198,7 @@ class PokemonEmeraldWorld(World): | ||||
|  | ||||
|             # Prevent setting the number of required legendaries higher than the number of enabled legendaries | ||||
|             if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value): | ||||
|                 logging.warning("Pokemon Emerald: Legendary hunt count for Player %s (%s) higher than number of allowed " | ||||
|                 logging.warning("Pokemon Emerald: Legendary hunt count for player %s (%s) higher than number of allowed " | ||||
|                                 "legendary encounters. Reducing to number of allowed encounters.", self.player, | ||||
|                                 self.player_name) | ||||
|                 self.options.legendary_hunt_count.value = len(self.options.allowed_legendary_hunt_encounters.value) | ||||
| @@ -234,10 +235,17 @@ class PokemonEmeraldWorld(World): | ||||
|                 max_norman_count = 4 | ||||
|  | ||||
|         if self.options.norman_count.value > max_norman_count: | ||||
|             logging.warning("Pokemon Emerald: Norman requirements for Player %s (%s) are unsafe in combination with " | ||||
|             logging.warning("Pokemon Emerald: Norman requirements for player %s (%s) are unsafe in combination with " | ||||
|                             "other settings. Reducing to 4.", self.player, self.player_name) | ||||
|             self.options.norman_count.value = max_norman_count | ||||
|  | ||||
|         # Shuffled badges/hms will always be placed locally, so add them to local_items | ||||
|         if self.options.badges == RandomizeBadges.option_shuffle: | ||||
|             self.options.local_items.value.update(self.item_name_groups["Badge"]) | ||||
|  | ||||
|         if self.options.hms == RandomizeHms.option_shuffle: | ||||
|             self.options.local_items.value.update(self.item_name_groups["HM"]) | ||||
|  | ||||
|     def create_regions(self) -> None: | ||||
|         from .regions import create_regions | ||||
|         all_regions = create_regions(self) | ||||
| @@ -377,12 +385,11 @@ class PokemonEmeraldWorld(World): | ||||
|         item_locations = [location for location in item_locations if emerald_data.locations[location.key].category not in filter_categories] | ||||
|         default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations] | ||||
|  | ||||
|         # Take the itempool as-is | ||||
|         if self.options.item_pool_type == ItemPoolType.option_shuffled: | ||||
|             self.multiworld.itempool += default_itempool | ||||
|  | ||||
|         # Recreate the itempool from random items | ||||
|             # Take the itempool as-is | ||||
|             self.item_pool = default_itempool | ||||
|         elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced): | ||||
|             # Recreate the itempool from random items | ||||
|             item_categories = ["Ball", "Healing", "Rare Candy", "Vitamin", "Evolution Stone", | ||||
|                                "Money", "TM", "Held", "Misc", "Berry"] | ||||
|  | ||||
| @@ -392,6 +399,7 @@ class PokemonEmeraldWorld(World): | ||||
|                 if not item.advancement: | ||||
|                     item_category_counter.update([tag for tag in item.tags if tag in item_categories]) | ||||
|  | ||||
|             self.item_pool = [] | ||||
|             item_category_weights = [item_category_counter.get(category) for category in item_categories] | ||||
|             item_category_weights = [weight if weight is not None else 0 for weight in item_category_weights] | ||||
|  | ||||
| @@ -436,19 +444,10 @@ class PokemonEmeraldWorld(World): | ||||
|                         item_code = self.random.choice(fill_item_candidates_by_category[category]) | ||||
|                     item = self.create_item_by_code(item_code) | ||||
|  | ||||
|                 self.multiworld.itempool.append(item) | ||||
|                 self.item_pool.append(item) | ||||
|  | ||||
|     def set_rules(self) -> None: | ||||
|         from .rules import set_rules | ||||
|         set_rules(self) | ||||
|         self.multiworld.itempool += self.item_pool | ||||
|  | ||||
|     def generate_basic(self) -> None: | ||||
|         # Create auth | ||||
|         # self.auth = self.random.randbytes(16)  # Requires >=3.9 | ||||
|         self.auth = self.random.getrandbits(16 * 8).to_bytes(16, "little") | ||||
|  | ||||
|         randomize_types(self) | ||||
|         randomize_wild_encounters(self) | ||||
|         set_free_fly(self) | ||||
|         set_legendary_cave_entrances(self) | ||||
|  | ||||
| @@ -475,9 +474,20 @@ class PokemonEmeraldWorld(World): | ||||
|         if not self.options.key_items: | ||||
|             convert_unrandomized_items_to_events(LocationCategory.KEY) | ||||
|  | ||||
|     def pre_fill(self) -> None: | ||||
|         # Badges and HMs that are set to shuffle need to be placed at | ||||
|         # their own subset of locations | ||||
|     def set_rules(self): | ||||
|         from .rules import set_rules | ||||
|         set_rules(self) | ||||
|  | ||||
|     def connect_entrances(self): | ||||
|         randomize_wild_encounters(self) | ||||
|         self.shuffle_badges_hms() | ||||
|         # For entrance randomization, disconnect entrances here, randomize map, then | ||||
|         # undo badge/HM placement and re-shuffle them in the new map. | ||||
|  | ||||
|     def shuffle_badges_hms(self) -> None: | ||||
|         my_progression_items = [item for item in self.item_pool if item.advancement] | ||||
|         my_locations = list(self.get_locations()) | ||||
|  | ||||
|         if self.options.badges == RandomizeBadges.option_shuffle: | ||||
|             badge_locations: List[PokemonEmeraldLocation] | ||||
|             badge_items: List[PokemonEmeraldItem] | ||||
| @@ -502,41 +512,20 @@ class PokemonEmeraldWorld(World): | ||||
|                 badge_priority["Knuckle Badge"] = 0 | ||||
|             badge_items.sort(key=lambda item: badge_priority.get(item.name, 0)) | ||||
|  | ||||
|             # Un-exclude badge locations, since we need to put progression items on them | ||||
|             for location in badge_locations: | ||||
|                 location.progress_type = LocationProgressType.DEFAULT \ | ||||
|                     if location.progress_type == LocationProgressType.EXCLUDED \ | ||||
|                     else location.progress_type | ||||
|  | ||||
|             collection_state = self.multiworld.get_all_state(False) | ||||
|  | ||||
|             # If HM shuffle is on, HMs are not placed and not in the pool, so | ||||
|             # `get_all_state` did not contain them. Collect them manually for | ||||
|             # this fill. We know that they will be included in all state after | ||||
|             # this stage. | ||||
|             # Build state | ||||
|             state = CollectionState(self.multiworld) | ||||
|             for item in my_progression_items: | ||||
|                 state.collect(item, True) | ||||
|             # If HM shuffle is on, HMs are neither placed in locations nor in | ||||
|             # the item pool, so we also need to collect them. | ||||
|             if self.hm_shuffle_info is not None: | ||||
|                 for _, item in self.hm_shuffle_info: | ||||
|                     collection_state.collect(item) | ||||
|                     state.collect(item, True) | ||||
|             state.sweep_for_advancements(my_locations) | ||||
|  | ||||
|             # In specific very constrained conditions, fill_restrictive may run | ||||
|             # out of swaps before it finds a valid solution if it gets unlucky. | ||||
|             # This is a band-aid until fill/swap can reliably find those solutions. | ||||
|             attempts_remaining = 2 | ||||
|             while attempts_remaining > 0: | ||||
|                 attempts_remaining -= 1 | ||||
|                 self.random.shuffle(badge_locations) | ||||
|                 try: | ||||
|                     fill_restrictive(self.multiworld, collection_state, badge_locations, badge_items, | ||||
|                                      single_player_placement=True, lock=True, allow_excluded=True) | ||||
|                     break | ||||
|                 except FillError as exc: | ||||
|                     if attempts_remaining == 0: | ||||
|                         raise exc | ||||
|             # Shuffle badges | ||||
|             self.fill_subset_with_retries(badge_items, badge_locations, state) | ||||
|  | ||||
|                     logging.debug(f"Failed to shuffle badges for player {self.player}. Retrying.") | ||||
|                     continue | ||||
|  | ||||
|         # Badges are guaranteed to be either placed or in the multiworld's itempool now | ||||
|         if self.options.hms == RandomizeHms.option_shuffle: | ||||
|             hm_locations: List[PokemonEmeraldLocation] | ||||
|             hm_items: List[PokemonEmeraldItem] | ||||
| @@ -559,33 +548,56 @@ class PokemonEmeraldWorld(World): | ||||
|             if self.options.badges == RandomizeBadges.option_vanilla and \ | ||||
|                     self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): | ||||
|                 hm_priority["HM05 Flash"] = 0 | ||||
|             hm_items.sort(key=lambda item: hm_priority.get(item.name, 0)) | ||||
|             hm_items.sort(key=lambda item: hm_priority.get(item.name, 0), reverse=True) | ||||
|  | ||||
|             # Un-exclude HM locations, since we need to put progression items on them | ||||
|             for location in hm_locations: | ||||
|                 location.progress_type = LocationProgressType.DEFAULT \ | ||||
|                     if location.progress_type == LocationProgressType.EXCLUDED \ | ||||
|                     else location.progress_type | ||||
|             # Build state | ||||
|             # Badges are either in the item pool, or already placed and collected during sweep | ||||
|             state = CollectionState(self.multiworld) | ||||
|             for item in my_progression_items: | ||||
|                 state.collect(item, True) | ||||
|             state.sweep_for_advancements(my_locations) | ||||
|  | ||||
|             collection_state = self.multiworld.get_all_state(False) | ||||
|             # Shuffle HMs | ||||
|             self.fill_subset_with_retries(hm_items, hm_locations, state) | ||||
|  | ||||
|             # In specific very constrained conditions, fill_restrictive may run | ||||
|             # out of swaps before it finds a valid solution if it gets unlucky. | ||||
|             # This is a band-aid until fill/swap can reliably find those solutions. | ||||
|             attempts_remaining = 2 | ||||
|             while attempts_remaining > 0: | ||||
|                 attempts_remaining -= 1 | ||||
|                 self.random.shuffle(hm_locations) | ||||
|                 try: | ||||
|                     fill_restrictive(self.multiworld, collection_state, hm_locations, hm_items, | ||||
|                                      single_player_placement=True, lock=True, allow_excluded=True) | ||||
|                     break | ||||
|                 except FillError as exc: | ||||
|                     if attempts_remaining == 0: | ||||
|                         raise exc | ||||
|     def fill_subset_with_retries(self, items: list[PokemonEmeraldItem], locations: list[PokemonEmeraldLocation], state: CollectionState): | ||||
|         # Un-exclude locations, since we need to put progression items on them | ||||
|         for location in locations: | ||||
|             location.progress_type = LocationProgressType.DEFAULT \ | ||||
|                 if location.progress_type == LocationProgressType.EXCLUDED \ | ||||
|                 else location.progress_type | ||||
|  | ||||
|                     logging.debug(f"Failed to shuffle HMs for player {self.player}. Retrying.") | ||||
|                     continue | ||||
|         # In specific very constrained conditions, `fill_restrictive` may run | ||||
|         # out of swaps before it finds a valid solution if it gets unlucky. | ||||
|         attempts_remaining = 2 | ||||
|         while attempts_remaining > 0: | ||||
|             attempts_remaining -= 1 | ||||
|             locations_copy = locations.copy() | ||||
|             items_copy = items.copy() | ||||
|             self.random.shuffle(locations_copy) | ||||
|             try: | ||||
|                 fill_restrictive(self.multiworld, state, locations_copy, items_copy, single_player_placement=True, | ||||
|                                  lock=True) | ||||
|                 break | ||||
|             except FillError as exc: | ||||
|                 if attempts_remaining <= 0: | ||||
|                     raise exc | ||||
|  | ||||
|                 # Undo partial item placement | ||||
|                 for location in locations: | ||||
|                     location.locked = False | ||||
|                     if location.item is not None: | ||||
|                         location.item.location = None | ||||
|                         location.item = None | ||||
|  | ||||
|                 logging.debug(f"Failed to shuffle items for player {self.player} ({self.player_name}). Retrying.") | ||||
|                 continue | ||||
|  | ||||
|     def generate_basic(self) -> None: | ||||
|         # Create auth | ||||
|         self.auth = self.random.randbytes(16) | ||||
|  | ||||
|         randomize_types(self) | ||||
|  | ||||
|     def generate_output(self, output_directory: str) -> None: | ||||
|         self.modified_trainers = copy.deepcopy(emerald_data.trainers) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Bryce Wilson
					Bryce Wilson