 294a67a4b4
			
		
	
	294a67a4b4
	
	
	
		
			
			Changelog:
Features:
- New Goal
  - Minigame Madness
    - Win a certain number of each type of Minigame Trap, then defeat the Finalhazard to win!
	- How many of each Minigame are required can be set by an Option
	- When the required amount of a Minigame has been received, that Minigame can be replayed in the Chao World Lobby
- New optional Location Checks
  - Bigsanity
    - Go fishing with Big in each stage for a Location Check
  - Itemboxsanity
    - Either Extra Life Boxes or All Item Boxes
- New Items
  - New Traps
    - Literature Trap
	- Controller Drift Trap
	- Poison Trap
	- Bee Trap
  - New Minigame Traps
    - Breakout Trap
	- Fishing Trap
	- Trivia Trap
	- Pokemon Trivia Trap
	- Pokemon Count Trap
	- Number Sequence Trap
	- Light Up Path Trap
	- Pinball Trap
	- Math Quiz Trap
	- Snake Trap
	- Input Sequence Trap
- Trap Link
  - When you receive a trap, you send a copy of it to every other player with Trap Link enabled
- Boss Gate Plando
- Expert Logic Difficulty
	- Use at your own risk. This difficulty requires complete mastery of SA2.
- Missions can now be enabled and disabled per-character, instead of just per-style
- Minigame Difficulty can now be set to "Chaos", which selects a new difficulty randomly per-trap received
Quality of Life:
- Gate Stages and Mission Orders are now displayed in the spoiler log
- Additional play stats are saved and displayed with the randomizer credits
- Stage Locations progress UI now displays in multiple pages when Itemboxsanity is enabled
- Current stage mission order and progress are now shown when paused in-level
- Chaos Emeralds are now shown when paused in-level
- Location Name Groups were created
- Moved SA2B to the new Options system
- Option Presets were created
- Error Messages are more obvious
Bug Fixes:
- Added missing `Dry Lagoon - 12 Animals` location
- Flying Dog boss should no longer crash when you have done at least 3 Intermediate Kart Races
- Invincibility can no longer be received in the King Boom Boo fight, preventing a crash
- Chaos Emeralds should no longer disproportionately end up in Cannon's Core or the final Level Gate
- Going into submenus from the pause menu should no longer reset traps
- `Sonic - Magic Gloves` are now plural
- Junk items will no longer cause a crash when in a falling state
- Chao Garden:
	- Prevent races from occasionally becoming uncompletable when using the "Prize Only" option
	- Properly allow Hero Chao to participate in Dark Races
	- Don't allow the Chao Garden to send locations when connected to an invalid server
	- Prevent the Chao Garden from resetting your life count
	- Fix Chao World Entrance Shuffle causing inaccessible Neutral Garden
	- Fix pressing the 'B' button to take you to the proper location in Chao World Entrance Shuffle
	- Prevent Chao Karate progress icon overflow
	- Prevent changing Chao Timescale while paused or while a Minigame is active
- Logic Fixes:
	- `Mission Street - Chao Key 1` (Hard Logic) now requires no upgrades
	- `Mission Street - Chao Key 2` (Hard Logic) now requires no upgrades
	- `Crazy Gadget - Hidden 1` (Standard Logic) now requires `Sonic - Bounce Bracelet` instead of `Sonic - Light Shoes`
	- `Lost Colony - Hidden 1` (Standard Logic) now requires `Eggman - Jet Engine`
	- `Mad Space - Gold Beetle` (Standard Logic) now only requires `Rouge - Iron Boots`
	- `Cosmic Wall - Gold Beetle` (Standard and Hard Logic) now only requires `Eggman - Jet Engine`
		
	
		
			
				
	
	
		
			158 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import typing
 | |
| 
 | |
| from BaseClasses import MultiWorld
 | |
| from worlds.AutoWorld import World
 | |
| 
 | |
| from .Names import LocationName
 | |
| from .Options import GateBossPlando
 | |
| 
 | |
| 
 | |
| speed_characters_1 = "sonic vs shadow 1"
 | |
| speed_characters_2 = "sonic vs shadow 2"
 | |
| mech_characters_1 = "tails vs eggman 1"
 | |
| mech_characters_2 = "tails vs eggman 2"
 | |
| hunt_characters_1 = "knuckles vs rouge 1"
 | |
| big_foot = "big foot"
 | |
| hot_shot = "hot shot"
 | |
| flying_dog = "flying dog"
 | |
| egg_golem_sonic = "egg golem (sonic)"
 | |
| egg_golem_eggman = "egg golem (eggman)"
 | |
| king_boom_boo = "king boom boo"
 | |
| 
 | |
| gate_bosses_no_requirements_table = {
 | |
|     speed_characters_1: 0,
 | |
|     speed_characters_2: 1,
 | |
|     mech_characters_1: 2,
 | |
|     mech_characters_2: 3,
 | |
|     hunt_characters_1: 4,
 | |
|     big_foot: 5,
 | |
|     hot_shot: 6,
 | |
|     flying_dog: 7,
 | |
|     egg_golem_sonic: 8,
 | |
|     egg_golem_eggman: 9,
 | |
| }
 | |
| 
 | |
| gate_bosses_with_requirements_table = {
 | |
|     king_boom_boo: 10,
 | |
| }
 | |
| 
 | |
| extra_boss_rush_bosses_table = {
 | |
|     speed_characters_1: 11,
 | |
|     speed_characters_2: 12,
 | |
|     mech_characters_1: 13,
 | |
|     mech_characters_2: 14,
 | |
|     hunt_characters_1: 15,
 | |
| }
 | |
| 
 | |
| all_gate_bosses_table = {
 | |
|     **gate_bosses_no_requirements_table,
 | |
|     **gate_bosses_with_requirements_table,
 | |
| }
 | |
| 
 | |
| 
 | |
| boss_id_to_name = {
 | |
|     0: "Sonic vs Shadow 1",
 | |
|     1: "Sonic vs Shadow 2",
 | |
|     2: "Tails vs Eggman 1",
 | |
|     3: "Tails vs Eggman 2",
 | |
|     4: "Knuckles vs Rouge 1",
 | |
|     5: "F-6t BIG FOOT",
 | |
|     6: "B-3x HOT SHOT",
 | |
|     7: "R-1/A FLYING DOG",
 | |
|     8: "Egg Golem (Sonic)",
 | |
|     9: "Egg Golem (Eggman)",
 | |
|     10: "King Boom Boo",
 | |
|     11: "Sonic vs Shadow 1",
 | |
|     12: "Sonic vs Shadow 2",
 | |
|     13: "Tails vs Eggman 1",
 | |
|     14: "Tails vs Eggman 2",
 | |
|     15: "Knuckles vs Rouge 1",
 | |
| }
 | |
| 
 | |
| def get_boss_name(boss: int):
 | |
|     return boss_id_to_name[boss]
 | |
| 
 | |
| 
 | |
| def boss_has_requirement(boss: int):
 | |
|     return boss >= len(gate_bosses_no_requirements_table)
 | |
| 
 | |
| 
 | |
| def get_gate_bosses(world: World):
 | |
|     selected_bosses: typing.List[int] = []
 | |
|     boss_gates: typing.List[int] = []
 | |
|     available_bosses: typing.List[str] = list(gate_bosses_no_requirements_table.keys())
 | |
|     world.random.shuffle(available_bosses)
 | |
| 
 | |
|     gate_boss_plando: typing.Union[int, str] = world.options.gate_boss_plando.value
 | |
|     plando_bosses = ["None", "None", "None", "None", "None"]
 | |
|     if isinstance(gate_boss_plando, str):
 | |
|         # boss plando
 | |
|         options = gate_boss_plando.split(";")
 | |
|         gate_boss_plando = GateBossPlando.options[options.pop()]
 | |
|         for option in options:
 | |
|             if "-" in option:
 | |
|                 loc, boss = option.split("-")
 | |
|                 boss_num = LocationName.boss_gate_names[loc]
 | |
| 
 | |
|                 if boss_num >= world.options.number_of_level_gates.value:
 | |
|                     # Don't reject bosses plando'd into gate bosses that won't exist
 | |
|                     pass
 | |
| 
 | |
|                 if boss in plando_bosses:
 | |
|                     # TODO: Raise error here. Duplicates not allowed
 | |
|                     pass
 | |
| 
 | |
|                 plando_bosses[boss_num] = boss
 | |
| 
 | |
|                 if boss in available_bosses:
 | |
|                     available_bosses.remove(boss)
 | |
| 
 | |
|     for x in range(world.options.number_of_level_gates):
 | |
|         if ("king boom boo" not in selected_bosses) and ("king boom boo" not in available_bosses) and ((x + 1) / world.options.number_of_level_gates) > 0.5:
 | |
|             available_bosses.extend(gate_bosses_with_requirements_table)
 | |
|             world.random.shuffle(available_bosses)
 | |
| 
 | |
|         chosen_boss = available_bosses[0]
 | |
|         if plando_bosses[x] != "None":
 | |
|             available_bosses.append(plando_bosses[x])
 | |
|             chosen_boss = plando_bosses[x]
 | |
| 
 | |
|         selected_bosses.append(all_gate_bosses_table[chosen_boss])
 | |
|         boss_gates.append(x + 1)
 | |
|         available_bosses.remove(chosen_boss)
 | |
| 
 | |
|     bosses: typing.Dict[int, int] = dict(zip(boss_gates, selected_bosses))
 | |
| 
 | |
|     return bosses
 | |
| 
 | |
| 
 | |
| def get_boss_rush_bosses(world: World):
 | |
| 
 | |
|     if world.options.boss_rush_shuffle == 0:
 | |
|         boss_list_o = list(range(0, 16))
 | |
|         boss_list_s = [5, 2, 0, 10, 8, 4, 3, 1, 6, 13, 7, 11, 9, 15, 14, 12]
 | |
| 
 | |
|         return dict(zip(boss_list_o, boss_list_s))
 | |
|     elif world.options.boss_rush_shuffle == 1:
 | |
|         boss_list_o = list(range(0, 16))
 | |
|         boss_list_s = boss_list_o.copy()
 | |
|         world.random.shuffle(boss_list_s)
 | |
| 
 | |
|         return dict(zip(boss_list_o, boss_list_s))
 | |
|     elif world.options.boss_rush_shuffle == 2:
 | |
|         boss_list_o = list(range(0, 16))
 | |
|         boss_list_s = [world.random.choice(boss_list_o) for i in range(0, 16)]
 | |
|         if 10 not in boss_list_s:
 | |
|             boss_list_s[world.random.randint(0, 15)] = 10
 | |
| 
 | |
|         return dict(zip(boss_list_o, boss_list_s))
 | |
|     elif world.options.boss_rush_shuffle == 3:
 | |
|         boss_list_o = list(range(0, 16))
 | |
|         boss_list_s = [world.random.choice(boss_list_o)] * len(boss_list_o)
 | |
|         if 10 not in boss_list_s:
 | |
|             boss_list_s[world.random.randint(0, 15)] = 10
 | |
| 
 | |
|         return dict(zip(boss_list_o, boss_list_s))
 | |
|     else:
 | |
|         return dict()
 |