 21a3c74783
			
		
	
	21a3c74783
	
	
	
		
			
			Changelog:
Features:
- New goal
  - Grand Prix
    - Complete all of the Kart Races to win!
- New optional Location Checks
  - Omosanity (Activating Omochao)
  - Kart Race Mode
- Ring Loss option
  - `Classic` - lose all rings on hit
  - `Modern` - lose 20 rings on hit
  - `OHKO` - instantly die on hit, regardless of ring count (shields still protect you)
- New Trap
  - Pong Trap
Quality of Life:
- SA2B is now distributed as an `.apworld`
- Maximum possible number of Emblems in item pool is increased from 180 to 250
- An indicator now shows on the Stage Select screen when `Cannon's Core` is available
- Certain traps (`Exposition` and `Pong`) are now possible to receive on `Route 101` and `Route 280`
- Certain traps (`Confusion`, `Chaos Control`, `Exposition` and `Pong`) are now possible to receive on `FinalHazard`
Bug Fixes:
- Actually swap Intermediate and Expert Chao Races correctly
- Don't always grant double score for killing Gold Beetles anymore
- Ensure upgrades are applied properly, even when received while dying
- Fix the Message Queue getting disordered when receiving many messages in quick succession
- Fix Logic errors
  - `City Escape - 3` (Hard Logic) now requires no upgrades
  - `Mission Street - Pipe 2` (Hard Logic) now requires no upgrades
  - `Crazy Gadget - Pipe 3` (Hard Logic) now requires no upgrades
  - `Egg Quarters - 3` (Hard Logic) now requires only `Rouge - Mystic Melody`
  - `Mad Space - 5` (Hard Logic) now requires no upgrades
Co-authored-by: RaspberrySpaceJam <tyler.summers@gmail.com>
		
	
		
			
				
	
	
		
			420 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			420 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import typing
 | |
| import math
 | |
| 
 | |
| from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
 | |
| from .Items import SA2BItem, ItemData, item_table, upgrades_table, emeralds_table, junk_table, trap_table, item_groups
 | |
| from .Locations import SA2BLocation, all_locations, setup_locations
 | |
| from .Options import sa2b_options
 | |
| from .Regions import create_regions, shuffleable_regions, connect_regions, LevelGate, gate_0_whitelist_regions, \
 | |
|     gate_0_blacklist_regions
 | |
| from .Rules import set_rules
 | |
| from .Names import ItemName, LocationName
 | |
| from worlds.AutoWorld import WebWorld, World
 | |
| from .GateBosses import get_gate_bosses, get_boss_name
 | |
| from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions
 | |
| import Patch
 | |
| 
 | |
| 
 | |
| class SA2BWeb(WebWorld):
 | |
|     theme = "partyTime"
 | |
| 
 | |
|     setup_en = Tutorial(
 | |
|         "Multiworld Setup Guide",
 | |
|         "A guide to setting up the Sonic Adventure 2: Battle randomizer connected to an Archipelago Multiworld.",
 | |
|         "English",
 | |
|         "setup_en.md",
 | |
|         "setup/en",
 | |
|         ["RaspberrySpaceJam", "PoryGone", "Entiss"]
 | |
|     )
 | |
|     
 | |
|     tutorials = [setup_en]
 | |
| 
 | |
| 
 | |
| def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range: int, multiworld: MultiWorld):
 | |
|     blacklist_level_count = 0
 | |
| 
 | |
|     for i in range(gate_0_range):
 | |
|         if shuffled_levels[i] in gate_0_blacklist_regions:
 | |
|             blacklist_level_count += 1
 | |
| 
 | |
|     if blacklist_level_count == gate_0_range:
 | |
|         index_to_swap = multiworld.random.randint(0, gate_0_range)
 | |
|         for i in range(len(shuffled_levels)):
 | |
|             if shuffled_levels[i] in gate_0_whitelist_regions:
 | |
|                 shuffled_levels[i], shuffled_levels[index_to_swap] = shuffled_levels[index_to_swap], shuffled_levels[i]
 | |
|                 break
 | |
| 
 | |
| 
 | |
| class SA2BWorld(World):
 | |
|     """
 | |
|     Sonic Adventure 2 Battle is an action platforming game. Play as Sonic, Tails, Knuckles, Shadow, Rouge, and Eggman across 31 stages and prevent the destruction of the earth.
 | |
|     """
 | |
|     game: str = "Sonic Adventure 2 Battle"
 | |
|     option_definitions = sa2b_options
 | |
|     topology_present = False
 | |
|     data_version = 5
 | |
| 
 | |
|     item_name_groups = item_groups
 | |
|     item_name_to_id = {name: data.code for name, data in item_table.items()}
 | |
|     location_name_to_id = all_locations
 | |
| 
 | |
|     location_table: typing.Dict[str, int]
 | |
| 
 | |
|     music_map: typing.Dict[int, int]
 | |
|     mission_map: typing.Dict[int, int]
 | |
|     mission_count_map: typing.Dict[int, int]
 | |
|     emblems_for_cannons_core: int
 | |
|     region_emblem_map: typing.Dict[int, int]
 | |
|     gate_costs: typing.Dict[int, int]
 | |
|     gate_bosses: typing.Dict[int, int]
 | |
|     web = SA2BWeb()
 | |
| 
 | |
|     def _get_slot_data(self):
 | |
|         return {
 | |
|             "ModVersion": 201,
 | |
|             "Goal": self.multiworld.goal[self.player].value,
 | |
|             "MusicMap": self.music_map,
 | |
|             "MissionMap": self.mission_map,
 | |
|             "MissionCountMap": self.mission_count_map,
 | |
|             "MusicShuffle": self.multiworld.music_shuffle[self.player].value,
 | |
|             "Narrator": self.multiworld.narrator[self.player].value,
 | |
|             "MinigameTrapDifficulty": self.multiworld.minigame_trap_difficulty[self.player].value,
 | |
|             "RingLoss": self.multiworld.ring_loss[self.player].value,
 | |
|             "RequiredRank": self.multiworld.required_rank[self.player].value,
 | |
|             "ChaoKeys": self.multiworld.keysanity[self.player].value,
 | |
|             "Whistlesanity": self.multiworld.whistlesanity[self.player].value,
 | |
|             "GoldBeetles": self.multiworld.beetlesanity[self.player].value,
 | |
|             "OmochaoChecks": self.multiworld.omosanity[self.player].value,
 | |
|             "KartRaceChecks": self.multiworld.kart_race_checks[self.player].value,
 | |
|             "ChaoRaceChecks": self.multiworld.chao_race_checks[self.player].value,
 | |
|             "ChaoGardenDifficulty": self.multiworld.chao_garden_difficulty[self.player].value,
 | |
|             "DeathLink": self.multiworld.death_link[self.player].value,
 | |
|             "EmblemPercentageForCannonsCore": self.multiworld.emblem_percentage_for_cannons_core[self.player].value,
 | |
|             "RequiredCannonsCoreMissions": self.multiworld.required_cannons_core_missions[self.player].value,
 | |
|             "NumberOfLevelGates": self.multiworld.number_of_level_gates[self.player].value,
 | |
|             "LevelGateDistribution": self.multiworld.level_gate_distribution[self.player].value,
 | |
|             "EmblemsForCannonsCore": self.emblems_for_cannons_core,
 | |
|             "RegionEmblemMap": self.region_emblem_map,
 | |
|             "GateCosts": self.gate_costs,
 | |
|             "GateBosses": self.gate_bosses,
 | |
|         }
 | |
| 
 | |
|     def _create_items(self, name: str):
 | |
|         data = item_table[name]
 | |
|         return [self.create_item(name) for _ in range(data.quantity)]
 | |
| 
 | |
|     def fill_slot_data(self) -> dict:
 | |
|         slot_data = self._get_slot_data()
 | |
|         slot_data["MusicMap"] = self.music_map
 | |
|         for option_name in sa2b_options:
 | |
|             option = getattr(self.multiworld, option_name)[self.player]
 | |
|             slot_data[option_name] = option.value
 | |
| 
 | |
|         return slot_data
 | |
| 
 | |
|     def get_levels_per_gate(self) -> list:
 | |
|         levels_per_gate = list()
 | |
|         max_gate_index = self.multiworld.number_of_level_gates[self.player]
 | |
|         average_level_count = 30 / (max_gate_index + 1)
 | |
|         levels_added = 0
 | |
| 
 | |
|         for i in range(max_gate_index + 1):
 | |
|             levels_per_gate.append(average_level_count)
 | |
|             levels_added += average_level_count
 | |
|         additional_count_iterator = 0
 | |
|         while levels_added < 30:
 | |
|             levels_per_gate[additional_count_iterator] += 1
 | |
|             levels_added += 1
 | |
|             additional_count_iterator += 1 if additional_count_iterator < max_gate_index else -max_gate_index
 | |
| 
 | |
|         if self.multiworld.level_gate_distribution[self.player] == 0 or self.multiworld.level_gate_distribution[self.player] == 2:
 | |
|             early_distribution = self.multiworld.level_gate_distribution[self.player] == 0
 | |
|             levels_to_distribute = 5
 | |
|             gate_index_offset = 0
 | |
|             while levels_to_distribute > 0:
 | |
|                 if levels_per_gate[0 + gate_index_offset] == 1 or \
 | |
|                         levels_per_gate[max_gate_index - gate_index_offset] == 1:
 | |
|                     break
 | |
|                 if early_distribution:
 | |
|                     levels_per_gate[0 + gate_index_offset] += 1
 | |
|                     levels_per_gate[max_gate_index - gate_index_offset] -= 1
 | |
|                 else:
 | |
|                     levels_per_gate[0 + gate_index_offset] -= 1
 | |
|                     levels_per_gate[max_gate_index - gate_index_offset] += 1
 | |
|                 gate_index_offset += 1
 | |
|                 if gate_index_offset > math.floor(max_gate_index / 2):
 | |
|                     gate_index_offset = 0
 | |
|                 levels_to_distribute -= 1
 | |
| 
 | |
|         return levels_per_gate
 | |
| 
 | |
|     def generate_early(self):
 | |
|         if self.multiworld.goal[self.player].value == 3:
 | |
|             # Turn off everything else for Grand Prix goal
 | |
|             self.multiworld.number_of_level_gates[self.player].value = 0
 | |
|             self.multiworld.emblem_percentage_for_cannons_core[self.player].value = 0
 | |
|             self.multiworld.junk_fill_percentage[self.player].value = 100
 | |
|             self.multiworld.trap_fill_percentage[self.player].value = 100
 | |
|             self.multiworld.omochao_trap_weight[self.player].value = 0
 | |
|             self.multiworld.timestop_trap_weight[self.player].value = 0
 | |
|             self.multiworld.confusion_trap_weight[self.player].value = 0
 | |
|             self.multiworld.tiny_trap_weight[self.player].value = 0
 | |
|             self.multiworld.gravity_trap_weight[self.player].value = 0
 | |
| 
 | |
|             valid_trap_weights = self.multiworld.exposition_trap_weight[self.player].value + self.multiworld.pong_trap_weight[self.player].value
 | |
| 
 | |
|             if valid_trap_weights == 0:
 | |
|                 self.multiworld.exposition_trap_weight[self.player].value = 4
 | |
|                 self.multiworld.pong_trap_weight[self.player].value = 4
 | |
| 
 | |
|             if self.multiworld.kart_race_checks[self.player].value == 0:
 | |
|                 self.multiworld.kart_race_checks[self.player].value = 2
 | |
| 
 | |
|             self.gate_bosses = {}
 | |
|         else:
 | |
|             self.gate_bosses = get_gate_bosses(self.multiworld, self.player)
 | |
| 
 | |
|     def create_regions(self):
 | |
|         self.mission_map       = get_mission_table(self.multiworld, self.player)
 | |
|         self.mission_count_map = get_mission_count_table(self.multiworld, self.player)
 | |
| 
 | |
|         self.location_table = setup_locations(self.multiworld, self.player, self.mission_map, self.mission_count_map)
 | |
|         create_regions(self.multiworld, self.player, self.location_table)
 | |
| 
 | |
|         # Not Generate Basic
 | |
|         if self.multiworld.goal[self.player].value == 0 or self.multiworld.goal[self.player].value == 2:
 | |
|             self.multiworld.get_location(LocationName.finalhazard, self.player).place_locked_item(self.create_item(ItemName.maria))
 | |
|         elif self.multiworld.goal[self.player].value == 1:
 | |
|             self.multiworld.get_location(LocationName.green_hill, self.player).place_locked_item(self.create_item(ItemName.maria))
 | |
|         elif self.multiworld.goal[self.player].value == 3:
 | |
|             self.multiworld.get_location(LocationName.grand_prix, self.player).place_locked_item(self.create_item(ItemName.maria))
 | |
| 
 | |
|         itempool: typing.List[SA2BItem] = []
 | |
| 
 | |
|         # First Missions
 | |
|         total_required_locations = len(self.location_table)
 | |
|         total_required_locations -= 1; # Locked Victory Location
 | |
| 
 | |
|         if self.multiworld.goal[self.player].value != 3:
 | |
|             # Fill item pool with all required items
 | |
|             for item in {**upgrades_table}:
 | |
|                 itempool += self._create_items(item)
 | |
| 
 | |
|             if self.multiworld.goal[self.player].value == 1 or self.multiworld.goal[self.player].value == 2:
 | |
|                 # Some flavor of Chaos Emerald Hunt
 | |
|                 for item in {**emeralds_table}:
 | |
|                     itempool += self._create_items(item)
 | |
| 
 | |
|         # Cap at 250 Emblems
 | |
|         raw_emblem_count = total_required_locations - len(itempool)
 | |
|         total_emblem_count = min(raw_emblem_count, 250)
 | |
|         extra_junk_count = raw_emblem_count - total_emblem_count
 | |
| 
 | |
|         self.emblems_for_cannons_core = math.floor(
 | |
|             total_emblem_count * (self.multiworld.emblem_percentage_for_cannons_core[self.player].value / 100.0))
 | |
| 
 | |
|         gate_cost_mult = 1.0
 | |
|         if self.multiworld.level_gate_costs[self.player].value == 0:
 | |
|             gate_cost_mult = 0.6
 | |
|         elif self.multiworld.level_gate_costs[self.player].value == 1:
 | |
|             gate_cost_mult = 0.8
 | |
| 
 | |
|         shuffled_region_list = list(range(30))
 | |
|         emblem_requirement_list = list()
 | |
|         self.multiworld.random.shuffle(shuffled_region_list)
 | |
|         levels_per_gate = self.get_levels_per_gate()
 | |
| 
 | |
|         check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.multiworld)
 | |
|         levels_added_to_gate = 0
 | |
|         total_levels_added = 0
 | |
|         current_gate = 0
 | |
|         current_gate_emblems = 0
 | |
|         self.gate_costs = dict()
 | |
|         self.gate_costs[0] = 0
 | |
|         gates = list()
 | |
|         gates.append(LevelGate(0))
 | |
|         for i in range(30):
 | |
|             gates[current_gate].gate_levels.append(shuffled_region_list[i])
 | |
|             emblem_requirement_list.append(current_gate_emblems)
 | |
|             levels_added_to_gate += 1
 | |
|             total_levels_added += 1
 | |
|             if levels_added_to_gate >= levels_per_gate[current_gate]:
 | |
|                 current_gate += 1
 | |
|                 if current_gate > self.multiworld.number_of_level_gates[self.player].value:
 | |
|                     current_gate = self.multiworld.number_of_level_gates[self.player].value
 | |
|                 else:
 | |
|                     current_gate_emblems = max(
 | |
|                         math.floor(total_emblem_count * math.pow(total_levels_added / 30.0, 2.0) * gate_cost_mult), current_gate)
 | |
|                     gates.append(LevelGate(current_gate_emblems))
 | |
|                     self.gate_costs[current_gate] = current_gate_emblems
 | |
|                 levels_added_to_gate = 0
 | |
| 
 | |
|         self.region_emblem_map = dict(zip(shuffled_region_list, emblem_requirement_list))
 | |
| 
 | |
|         first_cannons_core_mission, final_cannons_core_mission = get_first_and_last_cannons_core_missions(self.mission_map, self.mission_count_map)
 | |
| 
 | |
|         connect_regions(self.multiworld, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses, first_cannons_core_mission, final_cannons_core_mission)
 | |
| 
 | |
|         max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core)
 | |
|         itempool += [self.create_item(ItemName.emblem) for _ in range(max_required_emblems)]
 | |
| 
 | |
|         non_required_emblems = (total_emblem_count - max_required_emblems)
 | |
|         junk_count = math.floor(non_required_emblems * (self.multiworld.junk_fill_percentage[self.player].value / 100.0))
 | |
|         itempool += [self.create_item(ItemName.emblem, True) for _ in range(non_required_emblems - junk_count)]
 | |
| 
 | |
|         # Carve Traps out of junk_count
 | |
|         trap_weights = []
 | |
|         trap_weights += ([ItemName.omochao_trap] * self.multiworld.omochao_trap_weight[self.player].value)
 | |
|         trap_weights += ([ItemName.timestop_trap] * self.multiworld.timestop_trap_weight[self.player].value)
 | |
|         trap_weights += ([ItemName.confuse_trap] * self.multiworld.confusion_trap_weight[self.player].value)
 | |
|         trap_weights += ([ItemName.tiny_trap] * self.multiworld.tiny_trap_weight[self.player].value)
 | |
|         trap_weights += ([ItemName.gravity_trap] * self.multiworld.gravity_trap_weight[self.player].value)
 | |
|         trap_weights += ([ItemName.exposition_trap] * self.multiworld.exposition_trap_weight[self.player].value)
 | |
|         #trap_weights += ([ItemName.darkness_trap] * self.multiworld.darkness_trap_weight[self.player].value)
 | |
|         trap_weights += ([ItemName.pong_trap] * self.multiworld.pong_trap_weight[self.player].value)
 | |
| 
 | |
|         junk_count += extra_junk_count
 | |
|         trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0))
 | |
|         junk_count -= trap_count
 | |
| 
 | |
|         junk_pool = []
 | |
|         junk_keys = list(junk_table.keys())
 | |
|         for i in range(junk_count):
 | |
|             junk_item = self.multiworld.random.choice(junk_keys)
 | |
|             junk_pool.append(self.create_item(junk_item))
 | |
| 
 | |
|         itempool += junk_pool
 | |
| 
 | |
|         trap_pool = []
 | |
|         for i in range(trap_count):
 | |
|             trap_item = self.multiworld.random.choice(trap_weights)
 | |
|             trap_pool.append(self.create_item(trap_item))
 | |
| 
 | |
|         itempool += trap_pool
 | |
| 
 | |
|         self.multiworld.itempool += itempool
 | |
| 
 | |
|         # Music Shuffle
 | |
|         if self.multiworld.music_shuffle[self.player] == "levels":
 | |
|             musiclist_o = list(range(0, 47))
 | |
|             musiclist_s = musiclist_o.copy()
 | |
|             self.multiworld.random.shuffle(musiclist_s)
 | |
|             musiclist_o.extend(range(47, 78))
 | |
|             musiclist_s.extend(range(47, 78))
 | |
| 
 | |
|             if self.multiworld.sadx_music[self.player].value == 1:
 | |
|                 musiclist_s = [x+100 for x in musiclist_s]
 | |
|             elif self.multiworld.sadx_music[self.player].value == 2:
 | |
|                 for i in range(len(musiclist_s)):
 | |
|                     if self.multiworld.random.randint(0,1):
 | |
|                         musiclist_s[i] += 100
 | |
| 
 | |
|             self.music_map = dict(zip(musiclist_o, musiclist_s))
 | |
|         elif self.multiworld.music_shuffle[self.player] == "full":
 | |
|             musiclist_o = list(range(0, 78))
 | |
|             musiclist_s = musiclist_o.copy()
 | |
|             self.multiworld.random.shuffle(musiclist_s)
 | |
| 
 | |
|             if self.multiworld.sadx_music[self.player].value == 1:
 | |
|                 musiclist_s = [x+100 for x in musiclist_s]
 | |
|             elif self.multiworld.sadx_music[self.player].value == 2:
 | |
|                 for i in range(len(musiclist_s)):
 | |
|                     if self.multiworld.random.randint(0,1):
 | |
|                         musiclist_s[i] += 100
 | |
| 
 | |
|             self.music_map = dict(zip(musiclist_o, musiclist_s))
 | |
|         elif self.multiworld.music_shuffle[self.player] == "singularity":
 | |
|             musiclist_o = list(range(0, 78))
 | |
|             musiclist_s = [self.multiworld.random.choice(musiclist_o)] * len(musiclist_o)
 | |
| 
 | |
|             if self.multiworld.sadx_music[self.player].value == 1:
 | |
|                 musiclist_s = [x+100 for x in musiclist_s]
 | |
|             elif self.multiworld.sadx_music[self.player].value == 2:
 | |
|                 if self.multiworld.random.randint(0,1):
 | |
|                     musiclist_s = [x+100 for x in musiclist_s]
 | |
| 
 | |
|             self.music_map = dict(zip(musiclist_o, musiclist_s))
 | |
|         else:
 | |
|             musiclist_o = list(range(0, 78))
 | |
|             musiclist_s = musiclist_o.copy()
 | |
| 
 | |
|             if self.multiworld.sadx_music[self.player].value == 1:
 | |
|                 musiclist_s = [x+100 for x in musiclist_s]
 | |
|             elif self.multiworld.sadx_music[self.player].value == 2:
 | |
|                 for i in range(len(musiclist_s)):
 | |
|                     if self.multiworld.random.randint(0,1):
 | |
|                         musiclist_s[i] += 100
 | |
| 
 | |
|             self.music_map = dict(zip(musiclist_o, musiclist_s))
 | |
| 
 | |
|     def create_item(self, name: str, force_non_progression=False) -> Item:
 | |
|         data = item_table[name]
 | |
| 
 | |
|         if force_non_progression:
 | |
|             classification = ItemClassification.filler
 | |
|         elif name == ItemName.emblem:
 | |
|             classification = ItemClassification.progression_skip_balancing
 | |
|         elif data.progression:
 | |
|             classification = ItemClassification.progression
 | |
|         elif data.trap:
 | |
|             classification = ItemClassification.trap
 | |
|         else:
 | |
|             classification = ItemClassification.filler
 | |
| 
 | |
|         created_item = SA2BItem(name, classification, data.code, self.player)
 | |
| 
 | |
|         return created_item
 | |
| 
 | |
|     def get_filler_item_name(self) -> str:
 | |
|         self.multiworld.random.choice(junk_table.keys())
 | |
| 
 | |
|     def set_rules(self):
 | |
|         set_rules(self.multiworld, self.player, self.gate_bosses, self.mission_map, self.mission_count_map)
 | |
| 
 | |
|     def write_spoiler(self, spoiler_handle: typing.TextIO):
 | |
|         if self.multiworld.number_of_level_gates[self.player].value > 0:
 | |
|             spoiler_handle.write("\n")
 | |
|             header_text = "Sonic Adventure 2 Bosses for {}:\n"
 | |
|             header_text = header_text.format(self.multiworld.player_name[self.player])
 | |
|             spoiler_handle.write(header_text)
 | |
|             for x in range(len(self.gate_bosses.values())):
 | |
|                 text = "Gate {0} Boss: {1}\n"
 | |
|                 text = text.format((x + 1), get_boss_name(self.gate_bosses[x + 1]))
 | |
|                 spoiler_handle.writelines(text)
 | |
| 
 | |
|     def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
 | |
|         gate_names = [
 | |
|             LocationName.gate_0_region,
 | |
|             LocationName.gate_1_region,
 | |
|             LocationName.gate_2_region,
 | |
|             LocationName.gate_3_region,
 | |
|             LocationName.gate_4_region,
 | |
|             LocationName.gate_5_region,
 | |
|         ]
 | |
|         no_hint_region_names = [
 | |
|             LocationName.cannon_core_region,
 | |
|             LocationName.chao_garden_beginner_region,
 | |
|             LocationName.chao_garden_intermediate_region,
 | |
|             LocationName.chao_garden_expert_region,
 | |
|         ]
 | |
|         er_hint_data = {}
 | |
|         for i in range(self.multiworld.number_of_level_gates[self.player].value + 1):
 | |
|             gate_name = gate_names[i]
 | |
|             gate_region = self.multiworld.get_region(gate_name, self.player)
 | |
|             if not gate_region:
 | |
|                 continue
 | |
|             for exit in gate_region.exits:
 | |
|                 if exit.connected_region.name in gate_names or exit.connected_region.name in no_hint_region_names:
 | |
|                     continue
 | |
|                 level_region = exit.connected_region
 | |
|                 for location in level_region.locations:
 | |
|                     er_hint_data[location.address] = gate_name
 | |
| 
 | |
|         hint_data[self.player] = er_hint_data
 | |
| 
 | |
|     @classmethod
 | |
|     def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fill_locations):
 | |
|         if world.get_game_players("Sonic Adventure 2 Battle"):
 | |
|             progitempool.sort(
 | |
|                 key=lambda item: 0 if (item.name != 'Emblem') else 1)
 |