Changelog:
Features:
- New goal
  - Chaos Chao
    - Raise a Chaos Chao to win!
- New optional Location Checks
  - Chao Animal Parts
    - Each body part from each type of animal is a location
  - Chao Stats
    - 0-99 levels of each of the 7 Chao stats can be locations
    - The frequency of Chao Stat locations can be set (every level, every 2nd level, etc)
  - Kindergartensanity
    - Classroom lessons are locations
      - Either all lessons or any one of each category can be set as locations
  - Shopsanity
    - A specified number of locations can be placed in the Chao Black Market
    - These locations are unlocked by acquiring `Chao Coin`s
    - Ring costs for these items can be adjusted 
  - Chao Karate can now be set to one location per fight, instead of one per tournament
- Items
  - If any Chao locations are active, the following will be in the item pool:
    - Chao Eggs
    - Garden Seeds
    - Garden Fruit
    - Chao Hats
    - Chaos Drives
- The starting eggs in the garden can be a random color
- Chao World entrances can be shuffled
- Chao are given default names
- New Traps
  - Reverse Trap
Quality of Life:
- Chao Save Data is now separate per-slot in addition to per-seed
  - This allows a single player to have multiple slots in the same seed, each having separate Chao progress
- Chao Race/Karate progress is now displayed on Stage Select (when hovering over Chao World)
- All Chao can now enter the Hero and Dark races
- Chao Karate difficulty can be set separately from Chao Race difficulty
- Chao Aging can be sped up at will, up to 15×
- New mod `config` option to fine-tune Chao Stat multiplication
  - Note: This does not mix well with the Mod Manager "`Chao Stat Multiplier`" code
- Pong Traps can now activate in Chao World
- Maximum range for possible number of Emblems is now 1000
- General APWorld cleanup and optimization
  - Option access has moved to the new options system
  - An item group now exists for trap items
Bug Fixes:
- Dry Lagoon now has all 11 Animals
- Eternal Engine - 2 (Standard and Hard Logic) now requires only `Tails - Booster`
- Lost Colony - 2 (Hard Logic) now requires no upgrades
- Lost Colony - Animal 9 (Hard Logic) now requires either `Eggman - Jet Engine` or `Eggman - Large Cannon`
		
	
		
			
				
	
	
		
			341 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			341 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import typing
 | |
| import copy
 | |
| 
 | |
| from BaseClasses import MultiWorld
 | |
| from worlds.AutoWorld import World
 | |
| 
 | |
| 
 | |
| mission_orders: typing.List[typing.List[int]] = [
 | |
|     [1, 2, 3, 4, 5],
 | |
|     [1, 2, 3, 5, 4],
 | |
|     [1, 2, 4, 3, 5],
 | |
|     [1, 2, 4, 5, 3],
 | |
|     [1, 2, 5, 3, 4],
 | |
|     [1, 2, 5, 4, 3],
 | |
| 
 | |
|     [1, 3, 2, 4, 5],
 | |
|     [1, 3, 2, 5, 4],
 | |
|     [1, 3, 4, 2, 5],
 | |
|     [1, 3, 4, 5, 2],
 | |
|     [1, 3, 5, 2, 4],
 | |
|     [1, 3, 5, 4, 2],
 | |
| 
 | |
|     [1, 4, 2, 3, 5],
 | |
|     [1, 4, 2, 5, 3],
 | |
|     [1, 4, 3, 2, 5],
 | |
|     [1, 4, 3, 5, 2],
 | |
|     [1, 4, 5, 2, 3],
 | |
|     [1, 4, 5, 3, 2],
 | |
| 
 | |
|     [1, 5, 2, 3, 4],
 | |
|     [1, 5, 2, 4, 3],
 | |
|     [1, 5, 3, 2, 4],
 | |
|     [1, 5, 3, 4, 2],
 | |
|     [1, 5, 4, 2, 3],
 | |
|     [1, 5, 4, 3, 2],
 | |
| 
 | |
|     [2, 1, 3, 4, 5],
 | |
|     [2, 1, 3, 5, 4],
 | |
|     [2, 1, 4, 3, 5],
 | |
|     [2, 1, 4, 5, 3],
 | |
|     [2, 1, 5, 3, 4],
 | |
|     [2, 1, 5, 4, 3],
 | |
| 
 | |
|     [2, 3, 1, 4, 5],
 | |
|     [2, 3, 1, 5, 4],
 | |
|     [2, 3, 4, 1, 5],
 | |
|     [2, 3, 4, 5, 1],
 | |
|     [2, 3, 5, 1, 4],
 | |
|     [2, 3, 5, 4, 1],
 | |
| 
 | |
|     [2, 4, 1, 3, 5],
 | |
|     [2, 4, 1, 5, 3],
 | |
|     [2, 4, 3, 1, 5],
 | |
|     [2, 4, 3, 5, 1],
 | |
|     [2, 4, 5, 1, 3],
 | |
|     [2, 4, 5, 3, 1],
 | |
| 
 | |
|     [2, 5, 1, 3, 4],
 | |
|     [2, 5, 1, 4, 3],
 | |
|     [2, 5, 3, 1, 4],
 | |
|     [2, 5, 3, 4, 1],
 | |
|     [2, 5, 4, 1, 3],
 | |
|     [2, 5, 4, 3, 1],
 | |
| 
 | |
|     [3, 1, 2, 4, 5],
 | |
|     [3, 1, 2, 5, 4],
 | |
|     [3, 1, 4, 2, 5],
 | |
|     [3, 1, 4, 5, 2],
 | |
|     [3, 1, 5, 4, 2],
 | |
|     [3, 1, 5, 2, 4],
 | |
| 
 | |
|     [3, 2, 1, 4, 5],
 | |
|     [3, 2, 1, 5, 4],
 | |
|     [3, 2, 4, 1, 5],
 | |
|     [3, 2, 4, 5, 1],
 | |
|     [3, 2, 5, 1, 4],
 | |
|     [3, 2, 5, 4, 1],
 | |
| 
 | |
|     [3, 4, 1, 2, 5],
 | |
|     [3, 4, 1, 5, 2],
 | |
|     [3, 4, 2, 1, 5],
 | |
|     [3, 4, 2, 5, 1],
 | |
|     [3, 4, 5, 1, 2],
 | |
|     [3, 4, 5, 2, 1],
 | |
| 
 | |
|     [3, 5, 1, 4, 2],
 | |
|     [3, 5, 1, 2, 4],
 | |
|     [3, 5, 2, 1, 4],
 | |
|     [3, 5, 2, 4, 1],
 | |
|     [3, 5, 4, 1, 2],
 | |
|     [3, 5, 4, 2, 1],
 | |
| 
 | |
|     [4, 1, 2, 3, 5],
 | |
|     [4, 1, 2, 5, 3],
 | |
|     [4, 1, 3, 2, 5],
 | |
|     [4, 1, 3, 5, 2],
 | |
|     [4, 1, 5, 3, 2],
 | |
|     [4, 1, 5, 2, 3],
 | |
| 
 | |
|     [4, 2, 1, 3, 5],
 | |
|     [4, 2, 1, 5, 3],
 | |
|     [4, 2, 3, 1, 5],
 | |
|     [4, 2, 3, 5, 1],
 | |
|     [4, 2, 5, 1, 3],
 | |
|     [4, 2, 5, 3, 1],
 | |
| 
 | |
|     [4, 3, 1, 2, 5],
 | |
|     [4, 3, 1, 5, 2],
 | |
|     [4, 3, 2, 1, 5],
 | |
|     [4, 3, 2, 5, 1],
 | |
|     [4, 3, 5, 1, 2],
 | |
|     [4, 3, 5, 2, 1],
 | |
| 
 | |
|     [4, 5, 1, 3, 2],
 | |
|     [4, 5, 1, 2, 3],
 | |
|     [4, 5, 2, 1, 3],
 | |
|     [4, 5, 2, 3, 1],
 | |
|     [4, 5, 3, 1, 2],
 | |
|     [4, 5, 3, 2, 1],
 | |
| ]
 | |
| 
 | |
| ### 0: Speed
 | |
| ### 1: Mech
 | |
| ### 2: Hunt
 | |
| ### 3: Kart
 | |
| ### 4: Cannon's Core
 | |
| level_styles: typing.List[int] = [
 | |
|     0,
 | |
|     2,
 | |
|     1,
 | |
|     0,
 | |
|     0,
 | |
|     2,
 | |
|     1,
 | |
|     2,
 | |
|     3,
 | |
|     1,
 | |
|     0,
 | |
|     2,
 | |
|     1,
 | |
|     2,
 | |
|     0,
 | |
|     0,
 | |
| 
 | |
|     1,
 | |
|     2,
 | |
|     1,
 | |
|     0,
 | |
|     2,
 | |
|     1,
 | |
|     1,
 | |
|     2,
 | |
|     0,
 | |
|     3,
 | |
|     0,
 | |
|     2,
 | |
|     1,
 | |
|     0,
 | |
| 
 | |
|     4,
 | |
| ]
 | |
| 
 | |
| stage_name_prefixes: typing.List[str] = [
 | |
|     "City Escape - ",
 | |
|     "Wild Canyon - ",
 | |
|     "Prison Lane - ",
 | |
|     "Metal Harbor - ",
 | |
|     "Green Forest - ",
 | |
|     "Pumpkin Hill - ",
 | |
|     "Mission Street - ",
 | |
|     "Aquatic Mine - ",
 | |
|     "Route 101 - ",
 | |
|     "Hidden Base - ",
 | |
|     "Pyramid Cave - ",
 | |
|     "Death Chamber - ",
 | |
|     "Eternal Engine - ",
 | |
|     "Meteor Herd - ",
 | |
|     "Crazy Gadget - ",
 | |
|     "Final Rush - ",
 | |
|     "Iron Gate - ",
 | |
|     "Dry Lagoon - ",
 | |
|     "Sand Ocean - ",
 | |
|     "Radical Highway - ",
 | |
|     "Egg Quarters - ",
 | |
|     "Lost Colony - ",
 | |
|     "Weapons Bed - ",
 | |
|     "Security Hall - ",
 | |
|     "White Jungle - ",
 | |
|     "Route 280 - ",
 | |
|     "Sky Rail - ",
 | |
|     "Mad Space - ",
 | |
|     "Cosmic Wall - ",
 | |
|     "Final Chase - ",
 | |
|     "Cannon's Core - ",
 | |
| ]
 | |
| 
 | |
| def get_mission_count_table(multiworld: MultiWorld, world: World, player: int):
 | |
|     mission_count_table: typing.Dict[int, int] = {}
 | |
| 
 | |
|     if world.options.goal == 3:
 | |
|         for level in range(31):
 | |
|             mission_count_table[level] = 0
 | |
|     else:
 | |
|         speed_active_missions = 1
 | |
|         mech_active_missions = 1
 | |
|         hunt_active_missions = 1
 | |
|         kart_active_missions = 1
 | |
|         cannons_core_active_missions = 1
 | |
| 
 | |
|         for i in range(2,6):
 | |
|             if getattr(world.options, "speed_mission_" + str(i), None):
 | |
|                 speed_active_missions += 1
 | |
| 
 | |
|             if getattr(world.options, "mech_mission_" + str(i), None):
 | |
|                 mech_active_missions += 1
 | |
| 
 | |
|             if getattr(world.options, "hunt_mission_" + str(i), None):
 | |
|                 hunt_active_missions += 1
 | |
| 
 | |
|             if getattr(world.options, "kart_mission_" + str(i), None):
 | |
|                 kart_active_missions += 1
 | |
| 
 | |
|             if getattr(world.options, "cannons_core_mission_" + str(i), None):
 | |
|                 cannons_core_active_missions += 1
 | |
| 
 | |
|         speed_active_missions        = min(speed_active_missions, world.options.speed_mission_count.value)
 | |
|         mech_active_missions         = min(mech_active_missions, world.options.mech_mission_count.value)
 | |
|         hunt_active_missions         = min(hunt_active_missions, world.options.hunt_mission_count.value)
 | |
|         kart_active_missions         = min(kart_active_missions, world.options.kart_mission_count.value)
 | |
|         cannons_core_active_missions = min(cannons_core_active_missions, world.options.cannons_core_mission_count.value)
 | |
| 
 | |
|         active_missions: typing.List[typing.List[int]] = [
 | |
|             speed_active_missions,
 | |
|             mech_active_missions,
 | |
|             hunt_active_missions,
 | |
|             kart_active_missions,
 | |
|             cannons_core_active_missions
 | |
|         ]
 | |
| 
 | |
|         for level in range(31):
 | |
|             level_style = level_styles[level]
 | |
|             level_mission_count = active_missions[level_style]
 | |
|             mission_count_table[level] = level_mission_count
 | |
| 
 | |
|     return mission_count_table
 | |
| 
 | |
| 
 | |
| def get_mission_table(multiworld: MultiWorld, world: World, player: int):
 | |
|     mission_table: typing.Dict[int, int] = {}
 | |
| 
 | |
|     if world.options.goal == 3:
 | |
|         for level in range(31):
 | |
|             mission_table[level] = 0
 | |
|     else:
 | |
|         speed_active_missions: typing.List[int] = [1]
 | |
|         mech_active_missions: typing.List[int] = [1]
 | |
|         hunt_active_missions: typing.List[int] = [1]
 | |
|         kart_active_missions: typing.List[int] = [1]
 | |
|         cannons_core_active_missions: typing.List[int] = [1]
 | |
| 
 | |
|         # Add included missions
 | |
|         for i in range(2,6):
 | |
|             if getattr(world.options, "speed_mission_" + str(i), None):
 | |
|                 speed_active_missions.append(i)
 | |
| 
 | |
|             if getattr(world.options, "mech_mission_" + str(i), None):
 | |
|                 mech_active_missions.append(i)
 | |
| 
 | |
|             if getattr(world.options, "hunt_mission_" + str(i), None):
 | |
|                 hunt_active_missions.append(i)
 | |
| 
 | |
|             if getattr(world.options, "kart_mission_" + str(i), None):
 | |
|                 kart_active_missions.append(i)
 | |
| 
 | |
|             if getattr(world.options, "cannons_core_mission_" + str(i), None):
 | |
|                 cannons_core_active_missions.append(i)
 | |
| 
 | |
|         active_missions: typing.List[typing.List[int]] = [
 | |
|             speed_active_missions,
 | |
|             mech_active_missions,
 | |
|             hunt_active_missions,
 | |
|             kart_active_missions,
 | |
|             cannons_core_active_missions
 | |
|         ]
 | |
| 
 | |
|         for level in range(31):
 | |
|             level_style = level_styles[level]
 | |
| 
 | |
|             level_active_missions: typing.List[int] = copy.deepcopy(active_missions[level_style])
 | |
|             level_chosen_missions: typing.List[int] = []
 | |
| 
 | |
|             # The first mission must be M1, M2, M3, or M4
 | |
|             first_mission = 1
 | |
|             first_mission_options = [1, 2, 3]
 | |
| 
 | |
|             if not world.options.animalsanity:
 | |
|                 first_mission_options.append(4)
 | |
| 
 | |
|             if world.options.mission_shuffle:
 | |
|                 first_mission = multiworld.random.choice([mission for mission in level_active_missions if mission in first_mission_options])
 | |
| 
 | |
|             level_active_missions.remove(first_mission)
 | |
| 
 | |
|             # Place Active Missions in the chosen mission list
 | |
|             for mission in level_active_missions:
 | |
|                 if mission not in level_chosen_missions:
 | |
|                     level_chosen_missions.append(mission)
 | |
| 
 | |
|             if world.options.mission_shuffle:
 | |
|                 multiworld.random.shuffle(level_chosen_missions)
 | |
| 
 | |
|             level_chosen_missions.insert(0, first_mission)
 | |
| 
 | |
|             # Fill in the non-included missions
 | |
|             for i in range(2,6):
 | |
|                 if i not in level_chosen_missions:
 | |
|                     level_chosen_missions.append(i)
 | |
| 
 | |
|             # Determine which mission order index we have, for conveying to the mod
 | |
|             for i in range(len(mission_orders)):
 | |
|                 if mission_orders[i] == level_chosen_missions:
 | |
|                     level_mission_index = i
 | |
|                     break
 | |
| 
 | |
|             mission_table[level] = level_mission_index
 | |
| 
 | |
|     return mission_table
 | |
| 
 | |
| 
 | |
| def get_first_and_last_cannons_core_missions(mission_map: typing.Dict[int, int], mission_count_map: typing.Dict[int, int]):
 | |
|         mission_count = mission_count_map[30]
 | |
|         mission_order: typing.List[int] = mission_orders[mission_map[30]]
 | |
|         stage_prefix: str = stage_name_prefixes[30]
 | |
| 
 | |
|         first_mission_number = mission_order[0]
 | |
|         last_mission_number = mission_order[mission_count - 1]
 | |
|         first_location_name: str = stage_prefix + str(first_mission_number)
 | |
|         last_location_name: str = stage_prefix + str(last_mission_number)
 | |
| 
 | |
|         return first_location_name, last_location_name
 |