SA2B: v2.3 - The Chao Update ()

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`
This commit is contained in:
PoryGone 2023-11-16 02:08:38 -05:00 committed by GitHub
parent 829c664304
commit 85d02b2dc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 3581 additions and 1326 deletions

View File

@ -0,0 +1,342 @@
chao_name_conversion = {
"!": 0x01,
"!": 0x02,
"#": 0x03,
"$": 0x04,
"%": 0x05,
"&": 0x06,
"\\": 0x07,
"(": 0x08,
")": 0x09,
"*": 0x0A,
"+": 0x0B,
",": 0x0C,
"-": 0x0D,
".": 0x0E,
"/": 0x0F,
"0": 0x10,
"1": 0x11,
"2": 0x12,
"3": 0x13,
"4": 0x14,
"5": 0x15,
"6": 0x16,
"7": 0x17,
"8": 0x18,
"9": 0x19,
":": 0x1A,
";": 0x1B,
"<": 0x1C,
"=": 0x1D,
">": 0x1E,
"?": 0x1F,
"@": 0x20,
"A": 0x21,
"B": 0x22,
"C": 0x23,
"D": 0x24,
"E": 0x25,
"F": 0x26,
"G": 0x27,
"H": 0x28,
"I": 0x29,
"J": 0x2A,
"K": 0x2B,
"L": 0x2C,
"M": 0x2D,
"N": 0x2E,
"O": 0x2F,
"P": 0x30,
"Q": 0x31,
"R": 0x32,
"S": 0x33,
"T": 0x34,
"U": 0x35,
"V": 0x36,
"W": 0x37,
"X": 0x38,
"Y": 0x39,
"Z": 0x3A,
"[": 0x3B,
"¥": 0x3C,
"]": 0x3D,
"^": 0x3E,
"_": 0x3F,
"`": 0x40,
"a": 0x41,
"b": 0x42,
"c": 0x43,
"d": 0x44,
"e": 0x45,
"f": 0x46,
"g": 0x47,
"h": 0x48,
"i": 0x49,
"j": 0x4A,
"k": 0x4B,
"l": 0x4C,
"m": 0x4D,
"n": 0x4E,
"o": 0x4F,
"p": 0x50,
"q": 0x51,
"r": 0x52,
"s": 0x53,
"t": 0x54,
"u": 0x55,
"v": 0x56,
"w": 0x57,
"x": 0x58,
"y": 0x59,
"z": 0x5A,
"{": 0x5B,
"|": 0x5C,
"}": 0x5D,
"~": 0x5E,
" ": 0x5F,
}
sample_chao_names = [
"Aginah",
"Biter",
"Steve",
"Ryley",
"Watcher",
"Acrid",
"Sheik",
"Lunais",
"Samus",
"The Kid",
"Jack",
"Sir Lee",
"Viridian",
"Rouhi",
"Toad",
"Merit",
"Ridley",
"Hornet",
"Carl",
"Raynor",
"Dixie",
"Wolnir",
"Mario",
"Gary",
"Wayne",
"Kevin",
"J.J.",
"Maxim",
"Redento",
"Caesar",
"Abigail",
"Link",
"Ninja",
"Roxas",
"Marin",
"Yorgle",
"DLC",
"Mina",
"Sans",
"Lan",
"Rin",
"Doomguy",
"Guide",
]
totally_real_item_names = [
"Mallet",
"Lava Rod",
"Master Knife",
"Slippers",
"Spade",
"Progressive Car Upgrade",
"Bonus Token",
"Shortnail",
"Runmaster",
"Courage Form",
"Auto Courage",
"Donald Defender",
"Goofy Blizzard",
"Ultimate Weapon",
"Song of the Sky Whale",
"Gryphon Shoes",
"Wing Key",
"Strength Anklet",
"Hairclip",
"Key of Wisdom",
"Baking",
"Progressive Block Mining",
"Jar",
"Whistle of Space",
"Rito Tunic",
"Kitchen Sink",
"Rock Badge",
"Key Card",
"Pikachu",
"Eevee",
"HM02 Strength",
"Progressive Astromancers",
"Progressive Chefs",
"The Living Safe",
"Lady Quinn",
"Dio's Worst Enemy",
"Pink Chaos Emerald",
"Black Chaos Emerald",
"Tails - Large Cannon",
"Eggman - Bazooka",
"Eggman - Booster",
"Knuckles - Shades",
"Sonic - Magic Shoes",
"Shadow - Bounce Bracelet",
"Rouge - Air Necklace",
"Big Key (Eggman's Pyramid)",
"Sensor Bunker",
"Phantom",
"Soldier",
"Plasma Suit",
"Gravity Beam",
"Hi-Jump Ball",
"Cannon Unlock LLL",
"Feather Cap",
"Progressive Yoshi",
"Purple Switch Palace",
"Cape Feather",
"Cane of Bryan",
"Van Repair",
"Autumn",
"Galaxy Knife",
"Green Cabbage Seeds",
"Timespinner Cog 1",
"Ladder",
"Visible Dots",
]
all_exits = [
0x00, # Lobby to Neutral
0x01, # Lobby to Hero
0x02, # Lobby to Dark
0x03, # Lobby to Kindergarten
0x04, # Neutral to Lobby
0x05, # Neutral to Cave
0x06, # Neutral to Transporter
0x07, # Hero to Lobby
0x08, # Hero to Transporter
0x09, # Dark to Lobby
0x0A, # Dark to Transporter
0x0B, # Cave to Neutral
0x0C, # Cave to Race
0x0D, # Cave to Karate
0x0E, # Race to Cave
0x0F, # Karate to Cave
0x10, # Transporter to Neutral
#0x11, # Transporter to Hero
#0x12, # Transporter to Dark
0x13, # Kindergarten to Lobby
]
all_destinations = [
0x07, # Lobby
0x07,
0x07,
0x07,
0x01, # Neutral
0x01,
0x01,
0x02, # Hero
0x02,
0x03, # Dark
0x03,
0x09, # Cave
0x09,
0x09,
0x05, # Chao Race
0x0A, # Chao Karate
0x0C, # Transporter
#0x0C,
#0x0C,
0x06, # Kindergarten
]
multi_rooms = [
0x07,
0x01,
0x02,
0x03,
0x09,
]
single_rooms = [
0x05,
0x0A,
0x0C,
0x06,
]
room_to_exits_map = {
0x07: [0x00, 0x01, 0x02, 0x03],
0x01: [0x04, 0x05, 0x06],
0x02: [0x07, 0x08],
0x03: [0x09, 0x0A],
0x09: [0x0B, 0x0C, 0x0D],
0x05: [0x0E],
0x0A: [0x0F],
0x0C: [0x10],#, 0x11, 0x12],
0x06: [0x13],
}
exit_to_room_map = {
0x00: 0x07, # Lobby to Neutral
0x01: 0x07, # Lobby to Hero
0x02: 0x07, # Lobby to Dark
0x03: 0x07, # Lobby to Kindergarten
0x04: 0x01, # Neutral to Lobby
0x05: 0x01, # Neutral to Cave
0x06: 0x01, # Neutral to Transporter
0x07: 0x02, # Hero to Lobby
0x08: 0x02, # Hero to Transporter
0x09: 0x03, # Dark to Lobby
0x0A: 0x03, # Dark to Transporter
0x0B: 0x09, # Cave to Neutral
0x0C: 0x09, # Cave to Race
0x0D: 0x09, # Cave to Karate
0x0E: 0x05, # Race to Cave
0x0F: 0x0A, # Karate to Cave
0x10: 0x0C, # Transporter to Neutral
#0x11: 0x0C, # Transporter to Hero
#0x12: 0x0C, # Transporter to Dark
0x13: 0x06, # Kindergarten to Lobby
}
valid_kindergarten_exits = [
0x04, # Neutral to Lobby
0x05, # Neutral to Cave
0x07, # Hero to Lobby
0x09, # Dark to Lobby
]

View File

@ -1,4 +1,6 @@
import typing
from BaseClasses import MultiWorld
from worlds.AutoWorld import World
speed_characters_1 = "Sonic vs Shadow 1"
speed_characters_2 = "Sonic vs Shadow 2"
@ -59,17 +61,17 @@ def boss_has_requirement(boss: int):
return boss >= len(gate_bosses_no_requirements_table)
def get_gate_bosses(world, player: int):
def get_gate_bosses(multiworld: MultiWorld, 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)
multiworld.random.shuffle(available_bosses)
halfway = False
for x in range(world.number_of_level_gates[player]):
if (not halfway) and ((x + 1) / world.number_of_level_gates[player]) > 0.5:
for x in range(world.options.number_of_level_gates):
if (not halfway) and ((x + 1) / world.options.number_of_level_gates) > 0.5:
available_bosses.extend(gate_bosses_with_requirements_table)
world.random.shuffle(available_bosses)
multiworld.random.shuffle(available_bosses)
halfway = True
selected_bosses.append(all_gate_bosses_table[available_bosses[0]])
boss_gates.append(x + 1)
@ -80,27 +82,27 @@ def get_gate_bosses(world, player: int):
return bosses
def get_boss_rush_bosses(multiworld, player: int):
def get_boss_rush_bosses(multiworld: MultiWorld, world: World):
if multiworld.boss_rush_shuffle[player] == 0:
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 multiworld.boss_rush_shuffle[player] == 1:
elif world.options.boss_rush_shuffle == 1:
boss_list_o = list(range(0, 16))
boss_list_s = boss_list_o.copy()
multiworld.random.shuffle(boss_list_s)
return dict(zip(boss_list_o, boss_list_s))
elif multiworld.boss_rush_shuffle[player] == 2:
elif world.options.boss_rush_shuffle == 2:
boss_list_o = list(range(0, 16))
boss_list_s = [multiworld.random.choice(boss_list_o) for i in range(0, 16)]
if 10 not in boss_list_s:
boss_list_s[multiworld.random.randint(0, 15)] = 10
return dict(zip(boss_list_o, boss_list_s))
elif multiworld.boss_rush_shuffle[player] == 3:
elif world.options.boss_rush_shuffle == 3:
boss_list_o = list(range(0, 16))
boss_list_s = [multiworld.random.choice(boss_list_o)] * len(boss_list_o)
if 10 not in boss_list_s:

View File

@ -22,7 +22,8 @@ class SA2BItem(Item):
# Separate tables for each type of item.
emblems_table = {
ItemName.emblem: ItemData(0xFF0000, True),
ItemName.emblem: ItemData(0xFF0000, True),
ItemName.market_token: ItemData(0xFF001F, True),
}
upgrades_table = {
@ -82,6 +83,7 @@ trap_table = {
ItemName.ice_trap: ItemData(0xFF0037, False, True),
ItemName.slow_trap: ItemData(0xFF0038, False, True),
ItemName.cutscene_trap: ItemData(0xFF0039, False, True),
ItemName.reverse_trap: ItemData(0xFF003A, False, True),
ItemName.pong_trap: ItemData(0xFF0050, False, True),
}
@ -96,6 +98,142 @@ emeralds_table = {
ItemName.blue_emerald: ItemData(0xFF0046, True),
}
eggs_table = {
ItemName.normal_egg: ItemData(0xFF0100, False),
ItemName.yellow_monotone_egg: ItemData(0xFF0101, False),
ItemName.white_monotone_egg: ItemData(0xFF0102, False),
ItemName.brown_monotone_egg: ItemData(0xFF0103, False),
ItemName.sky_blue_monotone_egg: ItemData(0xFF0104, False),
ItemName.pink_monotone_egg: ItemData(0xFF0105, False),
ItemName.blue_monotone_egg: ItemData(0xFF0106, False),
ItemName.grey_monotone_egg: ItemData(0xFF0107, False),
ItemName.green_monotone_egg: ItemData(0xFF0108, False),
ItemName.red_monotone_egg: ItemData(0xFF0109, False),
ItemName.lime_green_monotone_egg: ItemData(0xFF010A, False),
ItemName.purple_monotone_egg: ItemData(0xFF010B, False),
ItemName.orange_monotone_egg: ItemData(0xFF010C, False),
ItemName.black_monotone_egg: ItemData(0xFF010D, False),
ItemName.yellow_twotone_egg: ItemData(0xFF010E, False),
ItemName.white_twotone_egg: ItemData(0xFF010F, False),
ItemName.brown_twotone_egg: ItemData(0xFF0110, False),
ItemName.sky_blue_twotone_egg: ItemData(0xFF0111, False),
ItemName.pink_twotone_egg: ItemData(0xFF0112, False),
ItemName.blue_twotone_egg: ItemData(0xFF0113, False),
ItemName.grey_twotone_egg: ItemData(0xFF0114, False),
ItemName.green_twotone_egg: ItemData(0xFF0115, False),
ItemName.red_twotone_egg: ItemData(0xFF0116, False),
ItemName.lime_green_twotone_egg: ItemData(0xFF0117, False),
ItemName.purple_twotone_egg: ItemData(0xFF0118, False),
ItemName.orange_twotone_egg: ItemData(0xFF0119, False),
ItemName.black_twotone_egg: ItemData(0xFF011A, False),
ItemName.normal_shiny_egg: ItemData(0xFF011B, False),
ItemName.yellow_shiny_egg: ItemData(0xFF011C, False),
ItemName.white_shiny_egg: ItemData(0xFF011D, False),
ItemName.brown_shiny_egg: ItemData(0xFF011E, False),
ItemName.sky_blue_shiny_egg: ItemData(0xFF011F, False),
ItemName.pink_shiny_egg: ItemData(0xFF0120, False),
ItemName.blue_shiny_egg: ItemData(0xFF0121, False),
ItemName.grey_shiny_egg: ItemData(0xFF0122, False),
ItemName.green_shiny_egg: ItemData(0xFF0123, False),
ItemName.red_shiny_egg: ItemData(0xFF0124, False),
ItemName.lime_green_shiny_egg: ItemData(0xFF0125, False),
ItemName.purple_shiny_egg: ItemData(0xFF0126, False),
ItemName.orange_shiny_egg: ItemData(0xFF0127, False),
ItemName.black_shiny_egg: ItemData(0xFF0128, False),
}
fruits_table = {
ItemName.chao_garden_fruit: ItemData(0xFF0200, False),
ItemName.hero_garden_fruit: ItemData(0xFF0201, False),
ItemName.dark_garden_fruit: ItemData(0xFF0202, False),
ItemName.strong_fruit: ItemData(0xFF0203, False),
ItemName.tasty_fruit: ItemData(0xFF0204, False),
ItemName.hero_fruit: ItemData(0xFF0205, False),
ItemName.dark_fruit: ItemData(0xFF0206, False),
ItemName.round_fruit: ItemData(0xFF0207, False),
ItemName.triangle_fruit: ItemData(0xFF0208, False),
ItemName.square_fruit: ItemData(0xFF0209, False),
ItemName.heart_fruit: ItemData(0xFF020A, False),
ItemName.chao_fruit: ItemData(0xFF020B, False),
ItemName.smart_fruit: ItemData(0xFF020C, False),
ItemName.orange_fruit: ItemData(0xFF020D, False),
ItemName.blue_fruit: ItemData(0xFF020E, False),
ItemName.pink_fruit: ItemData(0xFF020F, False),
ItemName.green_fruit: ItemData(0xFF0210, False),
ItemName.purple_fruit: ItemData(0xFF0211, False),
ItemName.yellow_fruit: ItemData(0xFF0212, False),
ItemName.red_fruit: ItemData(0xFF0213, False),
ItemName.mushroom_fruit: ItemData(0xFF0214, False),
ItemName.super_mushroom_fruit: ItemData(0xFF0215, False),
ItemName.mint_candy_fruit: ItemData(0xFF0216, False),
ItemName.grapes_fruit: ItemData(0xFF0217, False),
}
seeds_table = {
ItemName.strong_seed: ItemData(0xFF0300, False),
ItemName.tasty_seed: ItemData(0xFF0301, False),
ItemName.hero_seed: ItemData(0xFF0302, False),
ItemName.dark_seed: ItemData(0xFF0303, False),
ItemName.round_seed: ItemData(0xFF0304, False),
ItemName.triangle_seed: ItemData(0xFF0305, False),
ItemName.square_seed: ItemData(0xFF0306, False),
}
hats_table = {
ItemName.pumpkin_hat: ItemData(0xFF0401, False),
ItemName.skull_hat: ItemData(0xFF0402, False),
ItemName.apple_hat: ItemData(0xFF0403, False),
ItemName.bucket_hat: ItemData(0xFF0404, False),
ItemName.empty_can_hat: ItemData(0xFF0405, False),
ItemName.cardboard_box_hat: ItemData(0xFF0406, False),
ItemName.flower_pot_hat: ItemData(0xFF0407, False),
ItemName.paper_bag_hat: ItemData(0xFF0408, False),
ItemName.pan_hat: ItemData(0xFF0409, False),
ItemName.stump_hat: ItemData(0xFF040A, False),
ItemName.watermelon_hat: ItemData(0xFF040B, False),
ItemName.red_wool_beanie_hat: ItemData(0xFF040C, False),
ItemName.blue_wool_beanie_hat: ItemData(0xFF040D, False),
ItemName.black_wool_beanie_hat: ItemData(0xFF040E, False),
ItemName.pacifier_hat: ItemData(0xFF040F, False),
}
animals_table = {
ItemName.animal_penguin: ItemData(0xFF0500, False),
ItemName.animal_seal: ItemData(0xFF0501, False),
ItemName.animal_otter: ItemData(0xFF0502, False),
ItemName.animal_rabbit: ItemData(0xFF0503, False),
ItemName.animal_cheetah: ItemData(0xFF0504, False),
ItemName.animal_warthog: ItemData(0xFF0505, False),
ItemName.animal_bear: ItemData(0xFF0506, False),
ItemName.animal_tiger: ItemData(0xFF0507, False),
ItemName.animal_gorilla: ItemData(0xFF0508, False),
ItemName.animal_peacock: ItemData(0xFF0509, False),
ItemName.animal_parrot: ItemData(0xFF050A, False),
ItemName.animal_condor: ItemData(0xFF050B, False),
ItemName.animal_skunk: ItemData(0xFF050C, False),
ItemName.animal_sheep: ItemData(0xFF050D, False),
ItemName.animal_raccoon: ItemData(0xFF050E, False),
ItemName.animal_halffish: ItemData(0xFF050F, False),
ItemName.animal_skeleton_dog: ItemData(0xFF0510, False),
ItemName.animal_bat: ItemData(0xFF0511, False),
ItemName.animal_dragon: ItemData(0xFF0512, False),
ItemName.animal_unicorn: ItemData(0xFF0513, False),
ItemName.animal_phoenix: ItemData(0xFF0514, False),
}
chaos_drives_table = {
ItemName.chaos_drive_yellow: ItemData(0xFF0515, False),
ItemName.chaos_drive_green: ItemData(0xFF0516, False),
ItemName.chaos_drive_red: ItemData(0xFF0517, False),
ItemName.chaos_drive_purple: ItemData(0xFF0518, False),
}
event_table = {
ItemName.maria: ItemData(0xFF001D, True),
}
@ -107,12 +245,25 @@ item_table = {
**junk_table,
**trap_table,
**emeralds_table,
**eggs_table,
**fruits_table,
**seeds_table,
**hats_table,
**animals_table,
**chaos_drives_table,
**event_table,
}
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
item_groups: typing.Dict[str, str] = {"Chaos Emeralds": [item_name for item_name, data in emeralds_table.items()]}
item_groups: typing.Dict[str, str] = {
"Chaos Emeralds": list(emeralds_table.keys()),
"Eggs": list(eggs_table.keys()),
"Fruits": list(fruits_table.keys()),
"Seeds": list(seeds_table.keys()),
"Hats": list(hats_table.keys()),
"Traps": list(trap_table.keys()),
}
ALTTPWorld.pedestal_credit_texts[item_table[ItemName.sonic_light_shoes].code] = "and the Soap Shoes"
ALTTPWorld.pedestal_credit_texts[item_table[ItemName.shadow_air_shoes].code] = "and the Soap Shoes"

View File

@ -1,6 +1,7 @@
import typing
from BaseClasses import Location, MultiWorld
from worlds.AutoWorld import World
from .Names import LocationName
from .Missions import stage_name_prefixes, mission_orders
@ -1066,6 +1067,7 @@ animal_location_table = {
LocationName.final_rush_animal_11: 0xFF0C4F,
LocationName.iron_gate_animal_11: 0xFF0C50,
LocationName.dry_lagoon_animal_11: 0xFF0C51,
LocationName.sand_ocean_animal_11: 0xFF0C52,
LocationName.radical_highway_animal_11: 0xFF0C53,
LocationName.lost_colony_animal_11: 0xFF0C55,
@ -1241,7 +1243,7 @@ boss_rush_location_table = {
LocationName.boss_rush_16: 0xFF0114,
}
chao_garden_beginner_location_table = {
chao_race_beginner_location_table = {
LocationName.chao_race_crab_pool_1: 0xFF0200,
LocationName.chao_race_crab_pool_2: 0xFF0201,
LocationName.chao_race_crab_pool_3: 0xFF0202,
@ -1254,11 +1256,17 @@ chao_garden_beginner_location_table = {
LocationName.chao_race_block_canyon_1: 0xFF0209,
LocationName.chao_race_block_canyon_2: 0xFF020A,
LocationName.chao_race_block_canyon_3: 0xFF020B,
LocationName.chao_beginner_karate: 0xFF0300,
}
chao_garden_intermediate_location_table = {
chao_karate_beginner_location_table = {
LocationName.chao_beginner_karate_1: 0xFF0300,
LocationName.chao_beginner_karate_2: 0xFF0301,
LocationName.chao_beginner_karate_3: 0xFF0302,
LocationName.chao_beginner_karate_4: 0xFF0303,
LocationName.chao_beginner_karate_5: 0xFF0304,
}
chao_race_intermediate_location_table = {
LocationName.chao_race_challenge_1: 0xFF022A,
LocationName.chao_race_challenge_2: 0xFF022B,
LocationName.chao_race_challenge_3: 0xFF022C,
@ -1281,11 +1289,17 @@ chao_garden_intermediate_location_table = {
LocationName.chao_race_dark_2: 0xFF023B,
LocationName.chao_race_dark_3: 0xFF023C,
LocationName.chao_race_dark_4: 0xFF023D,
LocationName.chao_standard_karate: 0xFF0301,
}
chao_garden_expert_location_table = {
chao_karate_intermediate_location_table = {
LocationName.chao_standard_karate_1: 0xFF0305,
LocationName.chao_standard_karate_2: 0xFF0306,
LocationName.chao_standard_karate_3: 0xFF0307,
LocationName.chao_standard_karate_4: 0xFF0308,
LocationName.chao_standard_karate_5: 0xFF0309,
}
chao_race_expert_location_table = {
LocationName.chao_race_aquamarine_1: 0xFF020C,
LocationName.chao_race_aquamarine_2: 0xFF020D,
LocationName.chao_race_aquamarine_3: 0xFF020E,
@ -1316,11 +1330,187 @@ chao_garden_expert_location_table = {
LocationName.chao_race_diamond_3: 0xFF0227,
LocationName.chao_race_diamond_4: 0xFF0228,
LocationName.chao_race_diamond_5: 0xFF0229,
LocationName.chao_expert_karate: 0xFF0302,
LocationName.chao_super_karate: 0xFF0303,
}
chao_karate_expert_location_table = {
LocationName.chao_expert_karate_1: 0xFF030A,
LocationName.chao_expert_karate_2: 0xFF030B,
LocationName.chao_expert_karate_3: 0xFF030C,
LocationName.chao_expert_karate_4: 0xFF030D,
LocationName.chao_expert_karate_5: 0xFF030E,
}
chao_karate_super_location_table = {
LocationName.chao_super_karate_1: 0xFF030F,
LocationName.chao_super_karate_2: 0xFF0310,
LocationName.chao_super_karate_3: 0xFF0311,
LocationName.chao_super_karate_4: 0xFF0312,
LocationName.chao_super_karate_5: 0xFF0313,
}
chao_stat_swim_table = { LocationName.chao_stat_swim_base + str(index): (0xFF0E00 + index) for index in range(1,100) }
chao_stat_fly_table = { LocationName.chao_stat_fly_base + str(index): (0xFF0E80 + index) for index in range(1,100) }
chao_stat_run_table = { LocationName.chao_stat_run_base + str(index): (0xFF0F00 + index) for index in range(1,100) }
chao_stat_power_table = { LocationName.chao_stat_power_base + str(index): (0xFF0F80 + index) for index in range(1,100) }
chao_stat_stamina_table = { LocationName.chao_stat_stamina_base + str(index): (0xFF1000 + index) for index in range(1,100) }
chao_stat_luck_table = { LocationName.chao_stat_luck_base + str(index): (0xFF1080 + index) for index in range(1,100) }
chao_stat_intelligence_table = { LocationName.chao_stat_intelligence_base + str(index): (0xFF1100 + index) for index in range(1,100) }
chao_animal_event_location_table = {
LocationName.animal_penguin: None,
LocationName.animal_seal: None,
LocationName.animal_otter: None,
LocationName.animal_rabbit: None,
LocationName.animal_cheetah: None,
LocationName.animal_warthog: None,
LocationName.animal_bear: None,
LocationName.animal_tiger: None,
LocationName.animal_gorilla: None,
LocationName.animal_peacock: None,
LocationName.animal_parrot: None,
LocationName.animal_condor: None,
LocationName.animal_skunk: None,
LocationName.animal_sheep: None,
LocationName.animal_raccoon: None,
LocationName.animal_halffish: None,
LocationName.animal_skeleton_dog: None,
LocationName.animal_bat: None,
LocationName.animal_dragon: None,
LocationName.animal_unicorn: None,
LocationName.animal_phoenix: None,
}
chao_animal_part_location_table = {
LocationName.chao_penguin_arms: 0xFF1220,
LocationName.chao_penguin_forehead: 0xFF1222,
LocationName.chao_penguin_legs: 0xFF1224,
LocationName.chao_seal_arms: 0xFF1228,
LocationName.chao_seal_tail: 0xFF122E,
LocationName.chao_otter_arms: 0xFF1230,
LocationName.chao_otter_ears: 0xFF1231,
LocationName.chao_otter_face: 0xFF1233,
LocationName.chao_otter_legs: 0xFF1234,
LocationName.chao_otter_tail: 0xFF1236,
LocationName.chao_rabbit_arms: 0xFF1238,
LocationName.chao_rabbit_ears: 0xFF1239,
LocationName.chao_rabbit_legs: 0xFF123C,
LocationName.chao_rabbit_tail: 0xFF123E,
LocationName.chao_cheetah_arms: 0xFF1240,
LocationName.chao_cheetah_ears: 0xFF1241,
LocationName.chao_cheetah_legs: 0xFF1244,
LocationName.chao_cheetah_tail: 0xFF1246,
LocationName.chao_warthog_arms: 0xFF1248,
LocationName.chao_warthog_ears: 0xFF1249,
LocationName.chao_warthog_face: 0xFF124B,
LocationName.chao_warthog_legs: 0xFF124C,
LocationName.chao_warthog_tail: 0xFF124E,
LocationName.chao_bear_arms: 0xFF1250,
LocationName.chao_bear_ears: 0xFF1251,
LocationName.chao_bear_legs: 0xFF1254,
LocationName.chao_tiger_arms: 0xFF1258,
LocationName.chao_tiger_ears: 0xFF1259,
LocationName.chao_tiger_legs: 0xFF125C,
LocationName.chao_tiger_tail: 0xFF125E,
LocationName.chao_gorilla_arms: 0xFF1260,
LocationName.chao_gorilla_ears: 0xFF1261,
LocationName.chao_gorilla_forehead: 0xFF1262,
LocationName.chao_gorilla_legs: 0xFF1264,
LocationName.chao_peacock_forehead: 0xFF126A,
LocationName.chao_peacock_legs: 0xFF126C,
LocationName.chao_peacock_tail: 0xFF126E,
LocationName.chao_peacock_wings: 0xFF126F,
LocationName.chao_parrot_forehead: 0xFF1272,
LocationName.chao_parrot_legs: 0xFF1274,
LocationName.chao_parrot_tail: 0xFF1276,
LocationName.chao_parrot_wings: 0xFF1277,
LocationName.chao_condor_ears: 0xFF1279,
LocationName.chao_condor_legs: 0xFF127C,
LocationName.chao_condor_tail: 0xFF127E,
LocationName.chao_condor_wings: 0xFF127F,
LocationName.chao_skunk_arms: 0xFF1280,
LocationName.chao_skunk_forehead: 0xFF1282,
LocationName.chao_skunk_legs: 0xFF1284,
LocationName.chao_skunk_tail: 0xFF1286,
LocationName.chao_sheep_arms: 0xFF1288,
LocationName.chao_sheep_ears: 0xFF1289,
LocationName.chao_sheep_legs: 0xFF128C,
LocationName.chao_sheep_horn: 0xFF128D,
LocationName.chao_sheep_tail: 0xFF128E,
LocationName.chao_raccoon_arms: 0xFF1290,
LocationName.chao_raccoon_ears: 0xFF1291,
LocationName.chao_raccoon_legs: 0xFF1294,
LocationName.chao_dragon_arms: 0xFF12A0,
LocationName.chao_dragon_ears: 0xFF12A1,
LocationName.chao_dragon_legs: 0xFF12A4,
LocationName.chao_dragon_horn: 0xFF12A5,
LocationName.chao_dragon_tail: 0xFF12A6,
LocationName.chao_dragon_wings: 0xFF12A7,
LocationName.chao_unicorn_arms: 0xFF12A8,
LocationName.chao_unicorn_ears: 0xFF12A9,
LocationName.chao_unicorn_forehead: 0xFF12AA,
LocationName.chao_unicorn_legs: 0xFF12AC,
LocationName.chao_unicorn_tail: 0xFF12AE,
LocationName.chao_phoenix_forehead: 0xFF12B2,
LocationName.chao_phoenix_legs: 0xFF12B4,
LocationName.chao_phoenix_tail: 0xFF12B6,
LocationName.chao_phoenix_wings: 0xFF12B7,
}
chao_kindergarten_location_table = {
LocationName.chao_kindergarten_drawing_1: 0xFF12D0,
LocationName.chao_kindergarten_drawing_2: 0xFF12D1,
LocationName.chao_kindergarten_drawing_3: 0xFF12D2,
LocationName.chao_kindergarten_drawing_4: 0xFF12D3,
LocationName.chao_kindergarten_drawing_5: 0xFF12D4,
LocationName.chao_kindergarten_shake_dance: 0xFF12D8,
LocationName.chao_kindergarten_spin_dance: 0xFF12D9,
LocationName.chao_kindergarten_step_dance: 0xFF12DA,
LocationName.chao_kindergarten_gogo_dance: 0xFF12DB,
LocationName.chao_kindergarten_exercise: 0xFF12DC,
LocationName.chao_kindergarten_song_1: 0xFF12E0,
LocationName.chao_kindergarten_song_2: 0xFF12E1,
LocationName.chao_kindergarten_song_3: 0xFF12E2,
LocationName.chao_kindergarten_song_4: 0xFF12E3,
LocationName.chao_kindergarten_song_5: 0xFF12E4,
LocationName.chao_kindergarten_bell: 0xFF12E8,
LocationName.chao_kindergarten_castanets: 0xFF12E9,
LocationName.chao_kindergarten_cymbals: 0xFF12EA,
LocationName.chao_kindergarten_drum: 0xFF12EB,
LocationName.chao_kindergarten_flute: 0xFF12EC,
LocationName.chao_kindergarten_maracas: 0xFF12ED,
LocationName.chao_kindergarten_trumpet: 0xFF12EE,
LocationName.chao_kindergarten_tambourine: 0xFF12EF,
}
chao_kindergarten_basics_location_table = {
LocationName.chao_kindergarten_any_drawing: 0xFF12F0,
LocationName.chao_kindergarten_any_dance: 0xFF12F1,
LocationName.chao_kindergarten_any_song: 0xFF12F2,
LocationName.chao_kindergarten_any_instrument: 0xFF12F3,
}
black_market_location_table = { LocationName.chao_black_market_base + str(index): (0xFF1300 + index) for index in range(1,65) }
kart_race_beginner_location_table = {
LocationName.kart_race_beginner_sonic: 0xFF0A00,
LocationName.kart_race_beginner_tails: 0xFF0A01,
@ -1375,6 +1565,10 @@ grand_prix_location_table = {
LocationName.grand_prix: 0xFF007F,
}
chaos_chao_location_table = {
LocationName.chaos_chao: 0xFF009F,
}
all_locations = {
**mission_location_table,
**upgrade_location_table,
@ -1386,9 +1580,13 @@ all_locations = {
**beetle_location_table,
**omochao_location_table,
**animal_location_table,
**chao_garden_beginner_location_table,
**chao_garden_intermediate_location_table,
**chao_garden_expert_location_table,
**chao_race_beginner_location_table,
**chao_karate_beginner_location_table,
**chao_race_intermediate_location_table,
**chao_karate_intermediate_location_table,
**chao_race_expert_location_table,
**chao_karate_expert_location_table,
**chao_karate_super_location_table,
**kart_race_beginner_location_table,
**kart_race_standard_location_table,
**kart_race_expert_location_table,
@ -1398,6 +1596,18 @@ all_locations = {
**green_hill_animal_location_table,
**final_boss_location_table,
**grand_prix_location_table,
**chaos_chao_location_table,
**chao_stat_swim_table,
**chao_stat_fly_table,
**chao_stat_run_table,
**chao_stat_power_table,
**chao_stat_stamina_table,
**chao_stat_luck_table,
**chao_stat_intelligence_table,
**chao_animal_part_location_table,
**chao_kindergarten_location_table,
**chao_kindergarten_basics_location_table,
**black_market_location_table,
}
boss_gate_set = [
@ -1408,13 +1618,6 @@ boss_gate_set = [
LocationName.gate_5_boss,
]
chao_karate_set = [
LocationName.chao_beginner_karate,
LocationName.chao_standard_karate,
LocationName.chao_expert_karate,
LocationName.chao_super_karate,
]
chao_race_prize_set = [
LocationName.chao_race_crab_pool_3,
LocationName.chao_race_stump_valley_3,
@ -1437,19 +1640,24 @@ chao_race_prize_set = [
LocationName.chao_race_dark_2,
LocationName.chao_race_dark_4,
LocationName.chao_beginner_karate_5,
LocationName.chao_standard_karate_5,
LocationName.chao_expert_karate_5,
LocationName.chao_super_karate_5,
]
def setup_locations(world: MultiWorld, player: int, mission_map: typing.Dict[int, int], mission_count_map: typing.Dict[int, int]):
def setup_locations(world: World, player: int, mission_map: typing.Dict[int, int], mission_count_map: typing.Dict[int, int]):
location_table = {}
chao_location_table = {}
if world.goal[player] == 3:
if world.kart_race_checks[player] == 2:
if world.options.goal == 3:
if world.options.kart_race_checks == 2:
location_table.update({**kart_race_beginner_location_table})
location_table.update({**kart_race_standard_location_table})
location_table.update({**kart_race_expert_location_table})
elif world.kart_race_checks[player] == 1:
elif world.options.kart_race_checks == 1:
location_table.update({**kart_race_mini_location_table})
location_table.update({**grand_prix_location_table})
else:
@ -1465,67 +1673,100 @@ def setup_locations(world: MultiWorld, player: int, mission_map: typing.Dict[int
location_table.update({**upgrade_location_table})
if world.keysanity[player]:
if world.options.keysanity:
location_table.update({**chao_key_location_table})
if world.whistlesanity[player].value == 1:
if world.options.whistlesanity.value == 1:
location_table.update({**pipe_location_table})
elif world.whistlesanity[player].value == 2:
elif world.options.whistlesanity.value == 2:
location_table.update({**hidden_whistle_location_table})
elif world.whistlesanity[player].value == 3:
elif world.options.whistlesanity.value == 3:
location_table.update({**pipe_location_table})
location_table.update({**hidden_whistle_location_table})
if world.beetlesanity[player]:
if world.options.beetlesanity:
location_table.update({**beetle_location_table})
if world.omosanity[player]:
if world.options.omosanity:
location_table.update({**omochao_location_table})
if world.animalsanity[player]:
if world.options.animalsanity:
location_table.update({**animal_location_table})
if world.kart_race_checks[player] == 2:
if world.options.kart_race_checks == 2:
location_table.update({**kart_race_beginner_location_table})
location_table.update({**kart_race_standard_location_table})
location_table.update({**kart_race_expert_location_table})
elif world.kart_race_checks[player] == 1:
elif world.options.kart_race_checks == 1:
location_table.update({**kart_race_mini_location_table})
if world.goal[player].value in [0, 2, 4, 5, 6]:
if world.options.goal.value in [0, 2, 4, 5, 6]:
location_table.update({**final_boss_location_table})
elif world.options.goal.value in [7]:
location_table.update({**chaos_chao_location_table})
if world.goal[player].value in [1, 2]:
if world.options.goal.value in [1, 2]:
location_table.update({**green_hill_location_table})
if world.keysanity[player]:
if world.options.keysanity:
location_table.update({**green_hill_chao_location_table})
if world.animalsanity[player]:
if world.options.animalsanity:
location_table.update({**green_hill_animal_location_table})
if world.goal[player].value in [4, 5, 6]:
if world.options.goal.value in [4, 5, 6]:
location_table.update({**boss_rush_location_table})
if world.chao_garden_difficulty[player].value >= 1:
chao_location_table.update({**chao_garden_beginner_location_table})
if world.chao_garden_difficulty[player].value >= 2:
chao_location_table.update({**chao_garden_intermediate_location_table})
if world.chao_garden_difficulty[player].value >= 3:
chao_location_table.update({**chao_garden_expert_location_table})
if world.options.chao_race_difficulty.value >= 1:
chao_location_table.update({**chao_race_beginner_location_table})
if world.options.chao_race_difficulty.value >= 2:
chao_location_table.update({**chao_race_intermediate_location_table})
if world.options.chao_race_difficulty.value >= 3:
chao_location_table.update({**chao_race_expert_location_table})
if world.options.chao_karate_difficulty.value >= 1:
chao_location_table.update({**chao_karate_beginner_location_table})
if world.options.chao_karate_difficulty.value >= 2:
chao_location_table.update({**chao_karate_intermediate_location_table})
if world.options.chao_karate_difficulty.value >= 3:
chao_location_table.update({**chao_karate_expert_location_table})
if world.options.chao_karate_difficulty.value >= 4:
chao_location_table.update({**chao_karate_super_location_table})
for key, value in chao_location_table.items():
if key in chao_karate_set:
if world.include_chao_karate[player]:
location_table[key] = value
elif key not in chao_race_prize_set:
if world.chao_race_checks[player] == "all":
if key not in chao_race_prize_set:
if world.options.chao_stadium_checks == "all":
location_table[key] = value
else:
location_table[key] = value
for index in range(1, world.options.chao_stats.value + 1):
if (index % world.options.chao_stats_frequency.value) == (world.options.chao_stats.value % world.options.chao_stats_frequency.value):
location_table[LocationName.chao_stat_swim_base + str(index)] = chao_stat_swim_table[ LocationName.chao_stat_swim_base + str(index)]
location_table[LocationName.chao_stat_fly_base + str(index)] = chao_stat_fly_table[ LocationName.chao_stat_fly_base + str(index)]
location_table[LocationName.chao_stat_run_base + str(index)] = chao_stat_run_table[ LocationName.chao_stat_run_base + str(index)]
location_table[LocationName.chao_stat_power_base + str(index)] = chao_stat_power_table[ LocationName.chao_stat_power_base + str(index)]
if world.options.chao_stats_stamina:
location_table[LocationName.chao_stat_stamina_base + str(index)] = chao_stat_stamina_table[LocationName.chao_stat_stamina_base + str(index)]
if world.options.chao_stats_hidden:
location_table[LocationName.chao_stat_luck_base + str(index)] = chao_stat_luck_table[ LocationName.chao_stat_luck_base + str(index)]
location_table[LocationName.chao_stat_intelligence_base + str(index)] = chao_stat_intelligence_table[LocationName.chao_stat_intelligence_base + str(index)]
if world.options.chao_animal_parts:
location_table.update({**chao_animal_part_location_table})
if world.options.chao_kindergarten.value == 1:
location_table.update({**chao_kindergarten_basics_location_table})
elif world.options.chao_kindergarten.value == 2:
location_table.update({**chao_kindergarten_location_table})
for index in range(1, world.options.black_market_slots.value + 1):
location_table[LocationName.chao_black_market_base + str(index)] = black_market_location_table[LocationName.chao_black_market_base + str(index)]
for x in range(len(boss_gate_set)):
if x < world.number_of_level_gates[player].value:
if x < world.options.number_of_level_gates.value:
location_table[boss_gate_set[x]] = boss_gate_location_table[boss_gate_set[x]]
return location_table

View File

@ -2,6 +2,7 @@ import typing
import copy
from BaseClasses import MultiWorld
from worlds.AutoWorld import World
mission_orders: typing.List[typing.List[int]] = [
@ -193,10 +194,10 @@ stage_name_prefixes: typing.List[str] = [
"Cannon's Core - ",
]
def get_mission_count_table(multiworld: MultiWorld, player: int):
def get_mission_count_table(multiworld: MultiWorld, world: World, player: int):
mission_count_table: typing.Dict[int, int] = {}
if multiworld.goal[player] == 3:
if world.options.goal == 3:
for level in range(31):
mission_count_table[level] = 0
else:
@ -207,26 +208,26 @@ def get_mission_count_table(multiworld: MultiWorld, player: int):
cannons_core_active_missions = 1
for i in range(2,6):
if getattr(multiworld, "speed_mission_" + str(i), None)[player]:
if getattr(world.options, "speed_mission_" + str(i), None):
speed_active_missions += 1
if getattr(multiworld, "mech_mission_" + str(i), None)[player]:
if getattr(world.options, "mech_mission_" + str(i), None):
mech_active_missions += 1
if getattr(multiworld, "hunt_mission_" + str(i), None)[player]:
if getattr(world.options, "hunt_mission_" + str(i), None):
hunt_active_missions += 1
if getattr(multiworld, "kart_mission_" + str(i), None)[player]:
if getattr(world.options, "kart_mission_" + str(i), None):
kart_active_missions += 1
if getattr(multiworld, "cannons_core_mission_" + str(i), None)[player]:
if getattr(world.options, "cannons_core_mission_" + str(i), None):
cannons_core_active_missions += 1
speed_active_missions = min(speed_active_missions, multiworld.speed_mission_count[player].value)
mech_active_missions = min(mech_active_missions, multiworld.mech_mission_count[player].value)
hunt_active_missions = min(hunt_active_missions, multiworld.hunt_mission_count[player].value)
kart_active_missions = min(kart_active_missions, multiworld.kart_mission_count[player].value)
cannons_core_active_missions = min(cannons_core_active_missions, multiworld.cannons_core_mission_count[player].value)
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,
@ -244,10 +245,10 @@ def get_mission_count_table(multiworld: MultiWorld, player: int):
return mission_count_table
def get_mission_table(multiworld: MultiWorld, player: int):
def get_mission_table(multiworld: MultiWorld, world: World, player: int):
mission_table: typing.Dict[int, int] = {}
if multiworld.goal[player] == 3:
if world.options.goal == 3:
for level in range(31):
mission_table[level] = 0
else:
@ -259,19 +260,19 @@ def get_mission_table(multiworld: MultiWorld, player: int):
# Add included missions
for i in range(2,6):
if getattr(multiworld, "speed_mission_" + str(i), None)[player]:
if getattr(world.options, "speed_mission_" + str(i), None):
speed_active_missions.append(i)
if getattr(multiworld, "mech_mission_" + str(i), None)[player]:
if getattr(world.options, "mech_mission_" + str(i), None):
mech_active_missions.append(i)
if getattr(multiworld, "hunt_mission_" + str(i), None)[player]:
if getattr(world.options, "hunt_mission_" + str(i), None):
hunt_active_missions.append(i)
if getattr(multiworld, "kart_mission_" + str(i), None)[player]:
if getattr(world.options, "kart_mission_" + str(i), None):
kart_active_missions.append(i)
if getattr(multiworld, "cannons_core_mission_" + str(i), None)[player]:
if getattr(world.options, "cannons_core_mission_" + str(i), None):
cannons_core_active_missions.append(i)
active_missions: typing.List[typing.List[int]] = [
@ -292,10 +293,10 @@ def get_mission_table(multiworld: MultiWorld, player: int):
first_mission = 1
first_mission_options = [1, 2, 3]
if not multiworld.animalsanity[player]:
if not world.options.animalsanity:
first_mission_options.append(4)
if multiworld.mission_shuffle[player]:
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)
@ -305,7 +306,7 @@ def get_mission_table(multiworld: MultiWorld, player: int):
if mission not in level_chosen_missions:
level_chosen_missions.append(mission)
if multiworld.mission_shuffle[player]:
if world.options.mission_shuffle:
multiworld.random.shuffle(level_chosen_missions)
level_chosen_missions.insert(0, first_mission)

View File

@ -1,6 +1,9 @@
# Emblem Definition
emblem = "Emblem"
# Market Token Definition
market_token = "Chao Coin"
# Upgrade Definitions
sonic_gloves = "Sonic - Magic Glove"
sonic_light_shoes = "Sonic - Light Shoes"
@ -36,6 +39,8 @@ rouge_pick_nails = "Rouge - Pick Nails"
rouge_treasure_scope = "Rouge - Treasure Scope"
rouge_iron_boots = "Rouge - Iron Boots"
# Junk
five_rings = "Five Rings"
ten_rings = "Ten Rings"
twenty_rings = "Twenty Rings"
@ -44,6 +49,8 @@ shield = "Shield"
magnetic_shield = "Magnetic Shield"
invincibility = "Invincibility"
# Traps
omochao_trap = "OmoTrap"
timestop_trap = "Chaos Control Trap"
confuse_trap = "Confusion Trap"
@ -54,9 +61,12 @@ darkness_trap = "Darkness Trap"
ice_trap = "Ice Trap"
slow_trap = "Slow Trap"
cutscene_trap = "Cutscene Trap"
reverse_trap = "Reverse Trap"
pong_trap = "Pong Trap"
# Chaos Emeralds
white_emerald = "White Chaos Emerald"
red_emerald = "Red Chaos Emerald"
cyan_emerald = "Cyan Chaos Emerald"
@ -65,4 +75,140 @@ green_emerald = "Green Chaos Emerald"
yellow_emerald = "Yellow Chaos Emerald"
blue_emerald = "Blue Chaos Emerald"
# Chao Eggs
normal_egg = "Normal Egg"
yellow_monotone_egg = "Yellow Mono-Tone Egg"
white_monotone_egg = "White Mono-Tone Egg"
brown_monotone_egg = "Brown Mono-Tone Egg"
sky_blue_monotone_egg = "Sky Blue Mono-Tone Egg"
pink_monotone_egg = "Pink Mono-Tone Egg"
blue_monotone_egg = "Blue Mono-Tone Egg"
grey_monotone_egg = "Grey Mono-Tone Egg"
green_monotone_egg = "Green Mono-Tone Egg"
red_monotone_egg = "Red Mono-Tone Egg"
lime_green_monotone_egg = "Lime Green Mono-Tone Egg"
purple_monotone_egg = "Purple Mono-Tone Egg"
orange_monotone_egg = "Orange Mono-Tone Egg"
black_monotone_egg = "Black Mono-Tone Egg"
yellow_twotone_egg = "Yellow Two-Tone Egg"
white_twotone_egg = "White Two-Tone Egg"
brown_twotone_egg = "Brown Two-Tone Egg"
sky_blue_twotone_egg = "Sky Blue Two-Tone Egg"
pink_twotone_egg = "Pink Two-Tone Egg"
blue_twotone_egg = "Blue Two-Tone Egg"
grey_twotone_egg = "Grey Two-Tone Egg"
green_twotone_egg = "Green Two-Tone Egg"
red_twotone_egg = "Red Two-Tone Egg"
lime_green_twotone_egg = "Lime Green Two-Tone Egg"
purple_twotone_egg = "Purple Two-Tone Egg"
orange_twotone_egg = "Orange Two-Tone Egg"
black_twotone_egg = "Black Two-Tone Egg"
normal_shiny_egg = "Normal Shiny Egg"
yellow_shiny_egg = "Yellow Shiny Egg"
white_shiny_egg = "White Shiny Egg"
brown_shiny_egg = "Brown Shiny Egg"
sky_blue_shiny_egg = "Sky Blue Shiny Egg"
pink_shiny_egg = "Pink Shiny Egg"
blue_shiny_egg = "Blue Shiny Egg"
grey_shiny_egg = "Grey Shiny Egg"
green_shiny_egg = "Green Shiny Egg"
red_shiny_egg = "Red Shiny Egg"
lime_green_shiny_egg = "Lime Green Shiny Egg"
purple_shiny_egg = "Purple Shiny Egg"
orange_shiny_egg = "Orange Shiny Egg"
black_shiny_egg = "Black Shiny Egg"
# Chao Fruit
chao_garden_fruit = "Chao Garden Fruit"
hero_garden_fruit = "Hero Garden Fruit"
dark_garden_fruit = "Dark Garden Fruit"
strong_fruit = "Strong Fruit"
tasty_fruit = "Tasty Fruit"
hero_fruit = "Hero Fruit"
dark_fruit = "Dark Fruit"
round_fruit = "Round Fruit"
triangle_fruit = "Triangle Fruit"
square_fruit = "Square Fruit"
heart_fruit = "Heart Fruit"
chao_fruit = "Chao Fruit"
smart_fruit = "Smart Fruit"
orange_fruit = "Orange Fruit"
blue_fruit = "Blue Fruit"
pink_fruit = "Pink Fruit"
green_fruit = "Green Fruit"
purple_fruit = "Purple Fruit"
yellow_fruit = "Yellow Fruit"
red_fruit = "Red Fruit"
mushroom_fruit = "Mushroom"
super_mushroom_fruit = "Super Mushroom"
mint_candy_fruit = "Mint Candy"
grapes_fruit = "Grapes"
# Chao Seeds
strong_seed = "Strong Seed"
tasty_seed = "Tasty Seed"
hero_seed = "Hero Seed"
dark_seed = "Dark Seed"
round_seed = "Round Seed"
triangle_seed = "Triangle Seed"
square_seed = "Square Seed"
# Chao Hats
pumpkin_hat = "Pumpkin"
skull_hat = "Skull"
apple_hat = "Apple"
bucket_hat = "Bucket"
empty_can_hat = "Empty Can"
cardboard_box_hat = "Cardboard Box"
flower_pot_hat = "Flower Pot"
paper_bag_hat = "Paper Bag"
pan_hat = "Pan"
stump_hat = "Stump"
watermelon_hat = "Watermelon"
red_wool_beanie_hat = "Red Wool Beanie"
blue_wool_beanie_hat = "Blue Wool Beanie"
black_wool_beanie_hat = "Black Wool Beanie"
pacifier_hat = "Pacifier"
# Animal Items
animal_penguin = "Penguin"
animal_seal = "Seal"
animal_otter = "Otter"
animal_rabbit = "Rabbit"
animal_cheetah = "Cheetah"
animal_warthog = "Warthog"
animal_bear = "Bear"
animal_tiger = "Tiger"
animal_gorilla = "Gorilla"
animal_peacock = "Peacock"
animal_parrot = "Parrot"
animal_condor = "Condor"
animal_skunk = "Skunk"
animal_sheep = "Sheep"
animal_raccoon = "Raccoon"
animal_halffish = "HalfFish"
animal_skeleton_dog = "Skeleton Dog"
animal_bat = "Bat"
animal_dragon = "Dragon"
animal_unicorn = "Unicorn"
animal_phoenix = "Phoenix"
chaos_drive_yellow = "Yellow Chaos Drive"
chaos_drive_green = "Green Chaos Drive"
chaos_drive_red = "Red Chaos Drive"
chaos_drive_purple = "Purple Chaos Drive"
# Goal Item
maria = "What Maria Wanted"

View File

@ -909,6 +909,7 @@ dry_lagoon_animal_7 = "Dry Lagoon - 7 Animals"
dry_lagoon_animal_8 = "Dry Lagoon - 8 Animals"
dry_lagoon_animal_9 = "Dry Lagoon - 9 Animals"
dry_lagoon_animal_10 = "Dry Lagoon - 10 Animals"
dry_lagoon_animal_11 = "Dry Lagoon - 11 Animals"
dry_lagoon_upgrade = "Dry Lagoon - Upgrade"
egg_quarters_1 = "Egg Quarters - 1"
egg_quarters_2 = "Egg Quarters - 2"
@ -1150,10 +1151,190 @@ chao_race_dark_2 = "Chao Race - Dark 2"
chao_race_dark_3 = "Chao Race - Dark 3"
chao_race_dark_4 = "Chao Race - Dark 4"
chao_beginner_karate = "Chao Karate - Beginner"
chao_standard_karate = "Chao Karate - Standard"
chao_expert_karate = "Chao Karate - Expert"
chao_super_karate = "Chao Karate - Super"
chao_beginner_karate_1 = "Chao Karate - Beginner 1"
chao_beginner_karate_2 = "Chao Karate - Beginner 2"
chao_beginner_karate_3 = "Chao Karate - Beginner 3"
chao_beginner_karate_4 = "Chao Karate - Beginner 4"
chao_beginner_karate_5 = "Chao Karate - Beginner 5"
chao_standard_karate_1 = "Chao Karate - Standard 1"
chao_standard_karate_2 = "Chao Karate - Standard 2"
chao_standard_karate_3 = "Chao Karate - Standard 3"
chao_standard_karate_4 = "Chao Karate - Standard 4"
chao_standard_karate_5 = "Chao Karate - Standard 5"
chao_expert_karate_1 = "Chao Karate - Expert 1"
chao_expert_karate_2 = "Chao Karate - Expert 2"
chao_expert_karate_3 = "Chao Karate - Expert 3"
chao_expert_karate_4 = "Chao Karate - Expert 4"
chao_expert_karate_5 = "Chao Karate - Expert 5"
chao_super_karate_1 = "Chao Karate - Super 1"
chao_super_karate_2 = "Chao Karate - Super 2"
chao_super_karate_3 = "Chao Karate - Super 3"
chao_super_karate_4 = "Chao Karate - Super 4"
chao_super_karate_5 = "Chao Karate - Super 5"
chao_stat_swim_base = "Chao Stat - Swim - "
chao_stat_fly_base = "Chao Stat - Fly - "
chao_stat_run_base = "Chao Stat - Run - "
chao_stat_power_base = "Chao Stat - Power - "
chao_stat_stamina_base = "Chao Stat - Stamina - "
chao_stat_luck_base = "Chao Stat - Luck - "
chao_stat_intelligence_base = "Chao Stat - Intelligence - "
chao_black_market_base = "Black Market - "
# Animal Event Locations
animal_penguin = "Penguin Behavior"
animal_seal = "Seal Behavior"
animal_otter = "Otter Behavior"
animal_rabbit = "Rabbit Behavior"
animal_cheetah = "Cheetah Behavior"
animal_warthog = "Warthog Behavior"
animal_bear = "Bear Behavior"
animal_tiger = "Tiger Behavior"
animal_gorilla = "Gorilla Behavior"
animal_peacock = "Peacock Behavior"
animal_parrot = "Parrot Behavior"
animal_condor = "Condor Behavior"
animal_skunk = "Skunk Behavior"
animal_sheep = "Sheep Behavior"
animal_raccoon = "Raccoon Behavior"
animal_halffish = "HalfFish Behavior"
animal_skeleton_dog = "Skeleton Dog Behavior"
animal_bat = "Bat Behavior"
animal_dragon = "Dragon Behavior"
animal_unicorn = "Unicorn Behavior"
animal_phoenix = "Phoenix Behavior"
# Animal Body Part Locations
chao_penguin_arms = "Chao - Penguin Arms"
chao_penguin_forehead = "Chao - Penguin Forehead"
chao_penguin_legs = "Chao - Penguin Legs"
chao_seal_arms = "Chao - Seal Arms"
chao_seal_tail = "Chao - Seal Tail"
chao_otter_arms = "Chao - Otter Arms"
chao_otter_ears = "Chao - Otter Ears"
chao_otter_face = "Chao - Otter Face"
chao_otter_legs = "Chao - Otter Legs"
chao_otter_tail = "Chao - Otter Tail"
chao_rabbit_arms = "Chao - Rabbit Arms"
chao_rabbit_ears = "Chao - Rabbit Ears"
chao_rabbit_legs = "Chao - Rabbit Legs"
chao_rabbit_tail = "Chao - Rabbit Tail"
chao_cheetah_arms = "Chao - Cheetah Arms"
chao_cheetah_ears = "Chao - Cheetah Ears"
chao_cheetah_legs = "Chao - Cheetah Legs"
chao_cheetah_tail = "Chao - Cheetah Tail"
chao_warthog_arms = "Chao - Warthog Arms"
chao_warthog_ears = "Chao - Warthog Ears"
chao_warthog_face = "Chao - Warthog Face"
chao_warthog_legs = "Chao - Warthog Legs"
chao_warthog_tail = "Chao - Warthog Tail"
chao_bear_arms = "Chao - Bear Arms"
chao_bear_ears = "Chao - Bear Ears"
chao_bear_legs = "Chao - Bear Legs"
chao_tiger_arms = "Chao - Tiger Arms"
chao_tiger_ears = "Chao - Tiger Ears"
chao_tiger_legs = "Chao - Tiger Legs"
chao_tiger_tail = "Chao - Tiger Tail"
chao_gorilla_arms = "Chao - Gorilla Arms"
chao_gorilla_ears = "Chao - Gorilla Ears"
chao_gorilla_forehead = "Chao - Gorilla Forehead"
chao_gorilla_legs = "Chao - Gorilla Legs"
chao_peacock_forehead = "Chao - Peacock Forehead"
chao_peacock_legs = "Chao - Peacock Legs"
chao_peacock_tail = "Chao - Peacock Tail"
chao_peacock_wings = "Chao - Peacock Wings"
chao_parrot_forehead = "Chao - Parrot Forehead"
chao_parrot_legs = "Chao - Parrot Legs"
chao_parrot_tail = "Chao - Parrot Tail"
chao_parrot_wings = "Chao - Parrot Wings"
chao_condor_ears = "Chao - Condor Ears"
chao_condor_legs = "Chao - Condor Legs"
chao_condor_tail = "Chao - Condor Tail"
chao_condor_wings = "Chao - Condor Wings"
chao_skunk_arms = "Chao - Skunk Arms"
chao_skunk_forehead = "Chao - Skunk Forehead"
chao_skunk_legs = "Chao - Skunk Legs"
chao_skunk_tail = "Chao - Skunk Tail"
chao_sheep_arms = "Chao - Sheep Arms"
chao_sheep_ears = "Chao - Sheep Ears"
chao_sheep_legs = "Chao - Sheep Legs"
chao_sheep_horn = "Chao - Sheep Horn"
chao_sheep_tail = "Chao - Sheep Tail"
chao_raccoon_arms = "Chao - Raccoon Arms"
chao_raccoon_ears = "Chao - Raccoon Ears"
chao_raccoon_legs = "Chao - Raccoon Legs"
chao_dragon_arms = "Chao - Dragon Arms"
chao_dragon_ears = "Chao - Dragon Ears"
chao_dragon_legs = "Chao - Dragon Legs"
chao_dragon_horn = "Chao - Dragon Horn"
chao_dragon_tail = "Chao - Dragon Tail"
chao_dragon_wings = "Chao - Dragon Wings"
chao_unicorn_arms = "Chao - Unicorn Arms"
chao_unicorn_ears = "Chao - Unicorn Ears"
chao_unicorn_forehead = "Chao - Unicorn Forehead"
chao_unicorn_legs = "Chao - Unicorn Legs"
chao_unicorn_tail = "Chao - Unicorn Tail"
chao_phoenix_forehead = "Chao - Phoenix Forehead"
chao_phoenix_legs = "Chao - Phoenix Legs"
chao_phoenix_tail = "Chao - Phoenix Tail"
chao_phoenix_wings = "Chao - Phoenix Wings"
# Chao Kindergarten Locations
chao_kindergarten_drawing_1 = "Chao Kindergarten - Drawing 1"
chao_kindergarten_drawing_2 = "Chao Kindergarten - Drawing 2"
chao_kindergarten_drawing_3 = "Chao Kindergarten - Drawing 3"
chao_kindergarten_drawing_4 = "Chao Kindergarten - Drawing 4"
chao_kindergarten_drawing_5 = "Chao Kindergarten - Drawing 5"
chao_kindergarten_shake_dance = "Chao Kindergarten - Shake Dance"
chao_kindergarten_spin_dance = "Chao Kindergarten - Spin Dance"
chao_kindergarten_step_dance = "Chao Kindergarten - Step Dance"
chao_kindergarten_gogo_dance = "Chao Kindergarten - Go-Go Dance"
chao_kindergarten_exercise = "Chao Kindergarten - Exercise"
chao_kindergarten_song_1 = "Chao Kindergarten - Song 1"
chao_kindergarten_song_2 = "Chao Kindergarten - Song 2"
chao_kindergarten_song_3 = "Chao Kindergarten - Song 3"
chao_kindergarten_song_4 = "Chao Kindergarten - Song 4"
chao_kindergarten_song_5 = "Chao Kindergarten - Song 5"
chao_kindergarten_bell = "Chao Kindergarten - Bell"
chao_kindergarten_castanets = "Chao Kindergarten - Castanets"
chao_kindergarten_cymbals = "Chao Kindergarten - Cymbals"
chao_kindergarten_drum = "Chao Kindergarten - Drum"
chao_kindergarten_flute = "Chao Kindergarten - Flute"
chao_kindergarten_maracas = "Chao Kindergarten - Maracas"
chao_kindergarten_trumpet = "Chao Kindergarten - Trumpet"
chao_kindergarten_tambourine = "Chao Kindergarten - Tambourine"
chao_kindergarten_any_drawing = "Chao Kindergarten - Any Drawing"
chao_kindergarten_any_dance = "Chao Kindergarten - Any Dance"
chao_kindergarten_any_song = "Chao Kindergarten - Any Song"
chao_kindergarten_any_instrument = "Chao Kindergarten - Any Instrument"
# Chao Goal Locations
chaos_chao = "Chaos Chao"
chaos_chao_region = "Chaos Chao"
# Kart Race Definitions
kart_race_beginner_sonic = "Kart Race - Beginner - Sonic"
@ -1261,9 +1442,18 @@ green_hill_region = "Green Hill"
grand_prix = "Grand Prix"
grand_prix_region = "Grand Prix"
chao_garden_beginner_region = "Chao Garden - Beginner"
chao_garden_intermediate_region = "Chao Garden - Intermediate"
chao_garden_expert_region = "Chao Garden - Expert"
chao_race_beginner_region = "Chao Race - Beginner"
chao_race_intermediate_region = "Chao Race - Intermediate"
chao_race_expert_region = "Chao Race - Expert"
chao_karate_beginner_region = "Chao Karate - Beginner"
chao_karate_intermediate_region = "Chao Karate - Standard"
chao_karate_expert_region = "Chao Karate - Expert"
chao_karate_super_region = "Chao Karate - Super"
chao_kindergarten_region = "Chao Kindergarten"
black_market_region = "Black Market"
kart_race_beginner_region = "Kart Race - Beginner"
kart_race_standard_region = "Kart Race - Intermediate"

View File

@ -13,6 +13,7 @@ class Goal(Choice):
Boss Rush: Beat all of the bosses in the Boss Rush, ending with Finalhazard
Cannon's Core Boss Rush: Beat Cannon's Core, then beat all of the bosses in the Boss Rush, ending with Finalhazard
Boss Rush Chaos Emerald Hunt: Find the Seven Chaos Emeralds, then beat all of the bosses in the Boss Rush, ending with Finalhazard
Chaos Chao: Raise a Chaos Chao to win
"""
display_name = "Goal"
option_biolizard = 0
@ -22,6 +23,7 @@ class Goal(Choice):
option_boss_rush = 4
option_cannons_core_boss_rush = 5
option_boss_rush_chaos_emerald_hunt = 6
option_chaos_chao = 7
default = 0
@classmethod
@ -70,74 +72,81 @@ class BaseTrapWeight(Choice):
class OmochaoTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which spawns several Omochao around the player
Likelihood of receiving a trap which spawns several Omochao around the player
"""
display_name = "OmoTrap Weight"
class TimestopTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which briefly stops time
Likelihood of receiving a trap which briefly stops time
"""
display_name = "Chaos Control Trap Weight"
class ConfusionTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the controls to be skewed for a period of time
Likelihood of receiving a trap which causes the controls to be skewed for a period of time
"""
display_name = "Confusion Trap Weight"
class TinyTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the player to become tiny
Likelihood of receiving a trap which causes the player to become tiny
"""
display_name = "Tiny Trap Weight"
class GravityTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which increases gravity
Likelihood of receiving a trap which increases gravity
"""
display_name = "Gravity Trap Weight"
class ExpositionTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which tells you the story
Likelihood of receiving a trap which tells you the story
"""
display_name = "Exposition Trap Weight"
class DarknessTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which makes the world dark
Likelihood of receiving a trap which makes the world dark
"""
display_name = "Darkness Trap Weight"
class IceTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which makes the world slippery
Likelihood of receiving a trap which makes the world slippery
"""
display_name = "Ice Trap Weight"
class SlowTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which makes you gotta go slow
Likelihood of receiving a trap which makes you gotta go slow
"""
display_name = "Slow Trap Weight"
class CutsceneTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which makes you watch an unskippable cutscene
Likelihood of receiving a trap which makes you watch an unskippable cutscene
"""
display_name = "Cutscene Trap Weight"
class ReverseTrapWeight(BaseTrapWeight):
"""
Likelihood of receiving a trap which reverses your controls
"""
display_name = "Reverse Trap Weight"
class PongTrapWeight(BaseTrapWeight):
"""
Likelihood of receiving a trap which forces you to play a Pong minigame
@ -219,7 +228,7 @@ class Omosanity(Toggle):
class Animalsanity(Toggle):
"""
Determines whether picking up counted small animals grants checks
(420 Locations)
(421 Locations)
"""
display_name = "Animalsanity"
@ -291,7 +300,7 @@ class MaximumEmblemCap(Range):
"""
display_name = "Max Emblem Cap"
range_start = 50
range_end = 500
range_end = 1000
default = 180
@ -308,15 +317,15 @@ class RequiredRank(Choice):
default = 0
class ChaoGardenDifficulty(Choice):
class ChaoRaceDifficulty(Choice):
"""
Determines the number of chao garden difficulty levels included. Easier difficulty settings means fewer chao garden checks
None: No Chao Garden Activities have checks
Determines the number of Chao Race difficulty levels included. Easier difficulty settings means fewer Chao Race checks
None: No Chao Races have checks
Beginner: Beginner Races
Intermediate: Beginner, Challenge, Hero, and Dark Races
Expert: Beginner, Challenge, Hero, Dark and Jewel Races
"""
display_name = "Chao Garden Difficulty"
display_name = "Chao Race Difficulty"
option_none = 0
option_beginner = 1
option_intermediate = 2
@ -324,26 +333,138 @@ class ChaoGardenDifficulty(Choice):
default = 0
class IncludeChaoKarate(Toggle):
class ChaoKarateDifficulty(Choice):
"""
Determines whether the Chao Karate should be included as checks (Note: This setting requires purchase of the "Battle" DLC)
Determines the number of Chao Karate difficulty levels included. (Note: This setting requires purchase of the "Battle" DLC)
"""
display_name = "Include Chao Karate"
display_name = "Chao Karate Difficulty"
option_none = 0
option_beginner = 1
option_standard = 2
option_expert = 3
option_super = 4
default = 0
class ChaoRaceChecks(Choice):
class ChaoStadiumChecks(Choice):
"""
Determines which Chao Races grant checks
All: Each individual race grants a check
Determines which Chao Stadium activities grant checks
All: Each individual race and karate fight grants a check
Prize: Only the races which grant Chao Toys grant checks (final race of each Beginner and Jewel cup, 4th, 8th, and
12th Challenge Races, 2nd and 4th Hero and Dark Races)
12th Challenge Races, 2nd and 4th Hero and Dark Races, final fight of each Karate difficulty)
"""
display_name = "Chao Race Checks"
display_name = "Chao Stadium Checks"
option_all = 0
option_prize = 1
default = 0
class ChaoStats(Range):
"""
Determines the highest level in each Chao Stat that grants checks
(Swim, Fly, Run, Power)
"""
display_name = "Chao Stats"
range_start = 0
range_end = 99
default = 0
class ChaoStatsFrequency(Range):
"""
Determines how many levels in each Chao Stat grant checks (up to the maximum set in the `chao_stats` option)
`1` means every level is included, `2` means every other level is included, `3` means every third, and so on
"""
display_name = "Chao Stats Frequency"
range_start = 1
range_end = 20
default = 5
class ChaoStatsStamina(Toggle):
"""
Determines whether Stamina is included in the `chao_stats` option
"""
display_name = "Chao Stats - Stamina"
class ChaoStatsHidden(Toggle):
"""
Determines whether the hidden stats (Luck and Intelligence) are included in the `chao_stats` option
"""
display_name = "Chao Stats - Luck and Intelligence"
class ChaoAnimalParts(Toggle):
"""
Determines whether giving Chao various animal parts grants checks
(73 Locations)
"""
display_name = "Chao Animal Parts"
class ChaoKindergarten(Choice):
"""
Determines whether learning the lessons from the Kindergarten Classroom grants checks
(WARNING: VERY SLOW)
None: No Kindergarten classes have checks
Basics: One class from each category (Drawing, Dance, Song, and Instrument) is a check (4 Locations)
Full: Every class is a check (23 Locations)
"""
display_name = "Chao Kindergarten Checks"
option_none = 0
option_basics = 1
option_full = 2
default = 0
class BlackMarketSlots(Range):
"""
Determines how many multiworld items are available to purchase from the Black Market
"""
display_name = "Black Market Slots"
range_start = 0
range_end = 64
default = 0
class BlackMarketUnlockCosts(Choice):
"""
Determines how many Chao Coins are required to unlock sets of Black Market items
"""
display_name = "Black Market Unlock Costs"
option_low = 0
option_medium = 1
option_high = 2
default = 1
class BlackMarketPriceMultiplier(Range):
"""
Determines how many rings the Black Market items cost
The base ring costs of items in the Black Market range from 50-100,
and are then multiplied by this value
"""
display_name = "Black Market Price Multiplier"
range_start = 0
range_end = 40
default = 1
class ShuffleStartingChaoEggs(DefaultOnToggle):
"""
Determines whether the starting Chao eggs in the gardens are random
"""
display_name = "Shuffle Starting Chao Eggs"
class ChaoEntranceRandomization(Toggle):
"""
Determines whether entrances in Chao World are randomized
"""
display_name = "Chao Entrance Randomization"
class RequiredCannonsCoreMissions(Choice):
"""
Determines how many Cannon's Core missions must be completed (for Biolizard or Cannon's Core goals)
@ -657,24 +778,42 @@ class LogicDifficulty(Choice):
sa2b_options: typing.Dict[str, type(Option)] = {
"goal": Goal,
"mission_shuffle": MissionShuffle,
"boss_rush_shuffle": BossRushShuffle,
"keysanity": Keysanity,
"whistlesanity": Whistlesanity,
"beetlesanity": Beetlesanity,
"omosanity": Omosanity,
"animalsanity": Animalsanity,
"kart_race_checks": KartRaceChecks,
"logic_difficulty": LogicDifficulty,
"required_rank": RequiredRank,
"emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore,
"required_cannons_core_missions": RequiredCannonsCoreMissions,
"emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore,
"number_of_level_gates": NumberOfLevelGates,
"level_gate_distribution": LevelGateDistribution,
"level_gate_costs": LevelGateCosts,
"max_emblem_cap": MaximumEmblemCap,
"chao_garden_difficulty": ChaoGardenDifficulty,
"include_chao_karate": IncludeChaoKarate,
"chao_race_checks": ChaoRaceChecks,
"chao_race_difficulty": ChaoRaceDifficulty,
"chao_karate_difficulty": ChaoKarateDifficulty,
"chao_stadium_checks": ChaoStadiumChecks,
"chao_stats": ChaoStats,
"chao_stats_frequency": ChaoStatsFrequency,
"chao_stats_stamina": ChaoStatsStamina,
"chao_stats_hidden": ChaoStatsHidden,
"chao_animal_parts": ChaoAnimalParts,
"chao_kindergarten": ChaoKindergarten,
"black_market_slots": BlackMarketSlots,
"black_market_unlock_costs": BlackMarketUnlockCosts,
"black_market_price_multiplier": BlackMarketPriceMultiplier,
"shuffle_starting_chao_eggs": ShuffleStartingChaoEggs,
"chao_entrance_randomization": ChaoEntranceRandomization,
"junk_fill_percentage": JunkFillPercentage,
"trap_fill_percentage": TrapFillPercentage,
"omochao_trap_weight": OmochaoTrapWeight,
@ -687,39 +826,46 @@ sa2b_options: typing.Dict[str, type(Option)] = {
"ice_trap_weight": IceTrapWeight,
"slow_trap_weight": SlowTrapWeight,
"cutscene_trap_weight": CutsceneTrapWeight,
"reverse_trap_weight": ReverseTrapWeight,
"pong_trap_weight": PongTrapWeight,
"minigame_trap_difficulty": MinigameTrapDifficulty,
"ring_loss": RingLoss,
"ring_link": RingLink,
"sadx_music": SADXMusic,
"music_shuffle": MusicShuffle,
"voice_shuffle": VoiceShuffle,
"narrator": Narrator,
"logic_difficulty": LogicDifficulty,
"ring_loss": RingLoss,
"speed_mission_count": SpeedMissionCount,
"speed_mission_2": SpeedMission2,
"speed_mission_3": SpeedMission3,
"speed_mission_4": SpeedMission4,
"speed_mission_5": SpeedMission5,
"mech_mission_count": MechMissionCount,
"mech_mission_2": MechMission2,
"mech_mission_3": MechMission3,
"mech_mission_4": MechMission4,
"mech_mission_5": MechMission5,
"hunt_mission_count": HuntMissionCount,
"hunt_mission_2": HuntMission2,
"hunt_mission_3": HuntMission3,
"hunt_mission_4": HuntMission4,
"hunt_mission_5": HuntMission5,
"kart_mission_count": KartMissionCount,
"kart_mission_2": KartMission2,
"kart_mission_3": KartMission3,
"kart_mission_4": KartMission4,
"kart_mission_5": KartMission5,
"cannons_core_mission_count": CannonsCoreMissionCount,
"cannons_core_mission_2": CannonsCoreMission2,
"cannons_core_mission_3": CannonsCoreMission3,
"cannons_core_mission_4": CannonsCoreMission4,
"cannons_core_mission_5": CannonsCoreMission5,
"ring_link": RingLink,
"death_link": DeathLink,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,18 @@
import typing
import math
import logging
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 .Items import SA2BItem, ItemData, item_table, upgrades_table, emeralds_table, junk_table, trap_table, item_groups, \
eggs_table, fruits_table, seeds_table, hats_table, animals_table, chaos_drives_table
from .Locations import SA2BLocation, all_locations, setup_locations, chao_animal_event_location_table, black_market_location_table
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 .AestheticData import chao_name_conversion, sample_chao_names, totally_real_item_names, \
all_exits, all_destinations, multi_rooms, single_rooms, room_to_exits_map, exit_to_room_map, valid_kindergarten_exits
from worlds.AutoWorld import WebWorld, World
from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name
from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions
@ -52,7 +56,7 @@ class SA2BWorld(World):
game: str = "Sonic Adventure 2 Battle"
option_definitions = sa2b_options
topology_present = False
data_version = 6
data_version = 7
item_name_groups = item_groups
item_name_to_id = {name: data.code for name, data in item_table.items()}
@ -60,8 +64,6 @@ class SA2BWorld(World):
location_table: typing.Dict[str, int]
music_map: typing.Dict[int, int]
voice_map: typing.Dict[int, int]
mission_map: typing.Dict[int, int]
mission_count_map: typing.Dict[int, int]
emblems_for_cannons_core: int
@ -69,138 +71,126 @@ class SA2BWorld(World):
gate_costs: typing.Dict[int, int]
gate_bosses: typing.Dict[int, int]
boss_rush_map: typing.Dict[int, int]
black_market_costs: typing.Dict[int, int]
web = SA2BWeb()
def _get_slot_data(self):
def fill_slot_data(self) -> dict:
return {
"ModVersion": 202,
"Goal": self.multiworld.goal[self.player].value,
"MusicMap": self.music_map,
"VoiceMap": self.voice_map,
"ModVersion": 203,
"Goal": self.options.goal.value,
"MusicMap": self.generate_music_data(),
"VoiceMap": self.generate_voice_data(),
"DefaultEggMap": self.generate_chao_egg_data(),
"DefaultChaoNameMap": self.generate_chao_name_data(),
"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,
"RingLink": self.multiworld.ring_link[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,
"AnimalChecks": self.multiworld.animalsanity[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,
"MusicShuffle": self.options.music_shuffle.value,
"Narrator": self.options.narrator.value,
"MinigameTrapDifficulty": self.options.minigame_trap_difficulty.value,
"RingLoss": self.options.ring_loss.value,
"RingLink": self.options.ring_link.value,
"RequiredRank": self.options.required_rank.value,
"ChaoKeys": self.options.keysanity.value,
"Whistlesanity": self.options.whistlesanity.value,
"GoldBeetles": self.options.beetlesanity.value,
"OmochaoChecks": self.options.omosanity.value,
"AnimalChecks": self.options.animalsanity.value,
"KartRaceChecks": self.options.kart_race_checks.value,
"ChaoStadiumChecks": self.options.chao_stadium_checks.value,
"ChaoRaceDifficulty": self.options.chao_race_difficulty.value,
"ChaoKarateDifficulty": self.options.chao_karate_difficulty.value,
"ChaoStats": self.options.chao_stats.value,
"ChaoStatsFrequency": self.options.chao_stats_frequency.value,
"ChaoStatsStamina": self.options.chao_stats_stamina.value,
"ChaoStatsHidden": self.options.chao_stats_hidden.value,
"ChaoAnimalParts": self.options.chao_animal_parts.value,
"ChaoKindergarten": self.options.chao_kindergarten.value,
"BlackMarketSlots": self.options.black_market_slots.value,
"BlackMarketData": self.generate_black_market_data(),
"BlackMarketUnlockCosts": self.black_market_costs,
"BlackMarketUnlockSetting": self.options.black_market_unlock_costs.value,
"ChaoERLayout": self.generate_er_layout(),
"DeathLink": self.options.death_link.value,
"EmblemPercentageForCannonsCore": self.options.emblem_percentage_for_cannons_core.value,
"RequiredCannonsCoreMissions": self.options.required_cannons_core_missions.value,
"NumberOfLevelGates": self.options.number_of_level_gates.value,
"LevelGateDistribution": self.options.level_gate_distribution.value,
"EmblemsForCannonsCore": self.emblems_for_cannons_core,
"RegionEmblemMap": self.region_emblem_map,
"GateCosts": self.gate_costs,
"GateBosses": self.gate_bosses,
"BossRushMap": self.boss_rush_map,
"PlayerNum": self.player,
}
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:
if self.options.goal.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
self.multiworld.ice_trap_weight[self.player].value = 0
self.multiworld.slow_trap_weight[self.player].value = 0
self.options.number_of_level_gates.value = 0
self.options.emblem_percentage_for_cannons_core.value = 0
valid_trap_weights = self.multiworld.exposition_trap_weight[self.player].value + \
self.multiworld.cutscene_trap_weight[self.player].value + \
self.multiworld.pong_trap_weight[self.player].value
self.options.chao_race_difficulty.value = 0
self.options.chao_karate_difficulty.value = 0
self.options.chao_stats.value = 0
self.options.chao_animal_parts.value = 0
self.options.chao_kindergarten.value = 0
self.options.black_market_slots.value = 0
self.options.junk_fill_percentage.value = 100
self.options.trap_fill_percentage.value = 100
self.options.omochao_trap_weight.value = 0
self.options.timestop_trap_weight.value = 0
self.options.confusion_trap_weight.value = 0
self.options.tiny_trap_weight.value = 0
self.options.gravity_trap_weight.value = 0
self.options.ice_trap_weight.value = 0
self.options.slow_trap_weight.value = 0
self.options.cutscene_trap_weight.value = 0
valid_trap_weights = self.options.exposition_trap_weight.value + \
self.options.reverse_trap_weight.value + \
self.options.pong_trap_weight.value
if valid_trap_weights == 0:
self.multiworld.exposition_trap_weight[self.player].value = 4
self.multiworld.cutscene_trap_weight[self.player].value = 4
self.multiworld.pong_trap_weight[self.player].value = 4
self.options.exposition_trap_weight.value = 4
self.options.reverse_trap_weight.value = 4
self.options.pong_trap_weight.value = 4
if self.multiworld.kart_race_checks[self.player].value == 0:
self.multiworld.kart_race_checks[self.player].value = 2
if self.options.kart_race_checks.value == 0:
self.options.kart_race_checks.value = 2
self.gate_bosses = {}
self.boss_rush_map = {}
else:
self.gate_bosses = get_gate_bosses(self.multiworld, self.player)
self.boss_rush_map = get_boss_rush_bosses(self.multiworld, self.player)
self.gate_bosses = get_gate_bosses(self.multiworld, self)
self.boss_rush_map = get_boss_rush_bosses(self.multiworld, self)
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.mission_map = get_mission_table(self.multiworld, self, self.player)
self.mission_count_map = get_mission_count_table(self.multiworld, self, 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)
self.location_table = setup_locations(self, self.player, self.mission_map, self.mission_count_map)
create_regions(self.multiworld, self, self.player, self.location_table)
# Not Generate Basic
if self.multiworld.goal[self.player].value in [0, 2, 4, 5, 6]:
self.black_market_costs = dict()
if self.options.goal.value in [0, 2, 4, 5, 6]:
self.multiworld.get_location(LocationName.finalhazard, self.player).place_locked_item(self.create_item(ItemName.maria))
elif self.multiworld.goal[self.player].value == 1:
elif self.options.goal.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:
elif self.options.goal.value == 3:
self.multiworld.get_location(LocationName.grand_prix, self.player).place_locked_item(self.create_item(ItemName.maria))
elif self.options.goal.value == 7:
self.multiworld.get_location(LocationName.chaos_chao, self.player).place_locked_item(self.create_item(ItemName.maria))
for animal_name in chao_animal_event_location_table.keys():
animal_region = self.multiworld.get_region(animal_name, self.player)
animal_event_location = SA2BLocation(self.player, animal_name, None, animal_region)
animal_region.locations.append(animal_event_location)
animal_event_item = SA2BItem(animal_name, ItemClassification.progression, None, self.player)
self.multiworld.get_location(animal_name, self.player).place_locked_item(animal_event_item)
itempool: typing.List[SA2BItem] = []
@ -208,28 +198,40 @@ class SA2BWorld(World):
total_required_locations = len(self.location_table)
total_required_locations -= 1; # Locked Victory Location
if self.multiworld.goal[self.player].value != 3:
if self.options.goal.value != 3:
# Fill item pool with all required items
for item in {**upgrades_table}:
itempool += [self.create_item(item, False, self.multiworld.goal[self.player].value)]
itempool += [self.create_item(item, False, self.options.goal.value)]
if self.multiworld.goal[self.player].value in [1, 2, 6]:
if self.options.goal.value in [1, 2, 6]:
# Some flavor of Chaos Emerald Hunt
for item in {**emeralds_table}:
itempool += self._create_items(item)
itempool.append(self.create_item(item))
# Black Market
itempool += [self.create_item(ItemName.market_token) for _ in range(self.options.black_market_slots.value)]
black_market_unlock_mult = 1.0
if self.options.black_market_unlock_costs.value == 0:
black_market_unlock_mult = 0.5
elif self.options.black_market_unlock_costs.value == 1:
black_market_unlock_mult = 0.75
for i in range(self.options.black_market_slots.value):
self.black_market_costs[i] = math.floor((i + 1) * black_market_unlock_mult)
# Cap at player-specified Emblem count
raw_emblem_count = total_required_locations - len(itempool)
total_emblem_count = min(raw_emblem_count, self.multiworld.max_emblem_cap[self.player].value)
total_emblem_count = min(raw_emblem_count, self.options.max_emblem_cap.value)
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))
total_emblem_count * (self.options.emblem_percentage_for_cannons_core.value / 100.0))
gate_cost_mult = 1.0
if self.multiworld.level_gate_costs[self.player].value == 0:
if self.options.level_gate_costs.value == 0:
gate_cost_mult = 0.6
elif self.multiworld.level_gate_costs[self.player].value == 1:
elif self.options.level_gate_costs.value == 1:
gate_cost_mult = 0.8
shuffled_region_list = list(range(30))
@ -253,8 +255,8 @@ class SA2BWorld(World):
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
if current_gate > self.options.number_of_level_gates.value:
current_gate = self.options.number_of_level_gates.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)
@ -266,38 +268,70 @@ class SA2BWorld(World):
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, self.boss_rush_map, first_cannons_core_mission, final_cannons_core_mission)
connect_regions(self.multiworld, self, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses, self.boss_rush_map, 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))
junk_count = math.floor(non_required_emblems * (self.options.junk_fill_percentage.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.ice_trap] * self.multiworld.ice_trap_weight[self.player].value)
trap_weights += ([ItemName.slow_trap] * self.multiworld.slow_trap_weight[self.player].value)
trap_weights += ([ItemName.cutscene_trap] * self.multiworld.cutscene_trap_weight[self.player].value)
trap_weights += ([ItemName.pong_trap] * self.multiworld.pong_trap_weight[self.player].value)
trap_weights += ([ItemName.omochao_trap] * self.options.omochao_trap_weight.value)
trap_weights += ([ItemName.timestop_trap] * self.options.timestop_trap_weight.value)
trap_weights += ([ItemName.confuse_trap] * self.options.confusion_trap_weight.value)
trap_weights += ([ItemName.tiny_trap] * self.options.tiny_trap_weight.value)
trap_weights += ([ItemName.gravity_trap] * self.options.gravity_trap_weight.value)
trap_weights += ([ItemName.exposition_trap] * self.options.exposition_trap_weight.value)
#trap_weights += ([ItemName.darkness_trap] * self.options.darkness_trap_weight.value)
trap_weights += ([ItemName.ice_trap] * self.options.ice_trap_weight.value)
trap_weights += ([ItemName.slow_trap] * self.options.slow_trap_weight.value)
trap_weights += ([ItemName.cutscene_trap] * self.options.cutscene_trap_weight.value)
trap_weights += ([ItemName.reverse_trap] * self.options.reverse_trap_weight.value)
trap_weights += ([ItemName.pong_trap] * self.options.pong_trap_weight.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))
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.options.trap_fill_percentage.value / 100.0))
junk_count -= trap_count
chao_active = self.any_chao_locations_active()
junk_pool = []
junk_keys = list(junk_table.keys())
# Chao Junk
if chao_active:
junk_keys += list(chaos_drives_table.keys())
eggs_keys = list(eggs_table.keys())
fruits_keys = list(fruits_table.keys())
seeds_keys = list(seeds_table.keys())
hats_keys = list(hats_table.keys())
eggs_count = 0
seeds_count = 0
hats_count = 0
for i in range(junk_count):
junk_item = self.multiworld.random.choice(junk_keys)
junk_pool.append(self.create_item(junk_item))
junk_type = self.random.randint(0, len(junk_keys) + 3)
if chao_active and junk_type == len(junk_keys) + 0 and eggs_count < 20:
junk_item = self.multiworld.random.choice(eggs_keys)
junk_pool.append(self.create_item(junk_item))
eggs_count += 1
elif chao_active and junk_type == len(junk_keys) + 1:
junk_item = self.multiworld.random.choice(fruits_keys)
junk_pool.append(self.create_item(junk_item))
elif chao_active and junk_type == len(junk_keys) + 2 and seeds_count < 12:
junk_item = self.multiworld.random.choice(seeds_keys)
junk_pool.append(self.create_item(junk_item))
seeds_count += 1
elif chao_active and junk_type == len(junk_keys) + 3 and hats_count < 20:
junk_item = self.multiworld.random.choice(hats_keys)
junk_pool.append(self.create_item(junk_item))
hats_count += 1
else:
junk_item = self.multiworld.random.choice(junk_keys)
junk_pool.append(self.create_item(junk_item))
itempool += junk_pool
@ -310,95 +344,6 @@ class SA2BWorld(World):
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))
# Voice Shuffle
if self.multiworld.voice_shuffle[self.player] == "shuffled":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.multiworld.random.shuffle(voicelist_s)
self.voice_map = dict(zip(voicelist_o, voicelist_s))
elif self.multiworld.voice_shuffle[self.player] == "rude":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.multiworld.random.shuffle(voicelist_s)
for i in range(len(voicelist_s)):
if self.multiworld.random.randint(1,100) > 80:
voicelist_s[i] = 17
self.voice_map = dict(zip(voicelist_o, voicelist_s))
elif self.multiworld.voice_shuffle[self.player] == "chao":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.multiworld.random.shuffle(voicelist_s)
for i in range(len(voicelist_s)):
voicelist_s[i] = self.multiworld.random.choice(range(2586, 2608))
self.voice_map = dict(zip(voicelist_o, voicelist_s))
elif self.multiworld.voice_shuffle[self.player] == "singularity":
voicelist_o = list(range(0, 2623))
voicelist_s = [self.multiworld.random.choice(voicelist_o)] * len(voicelist_o)
self.voice_map = dict(zip(voicelist_o, voicelist_s))
else:
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.voice_map = dict(zip(voicelist_o, voicelist_s))
def create_item(self, name: str, force_non_progression=False, goal=0) -> Item:
@ -422,26 +367,32 @@ class SA2BWorld(World):
return created_item
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(list(junk_table.keys()))
junk_keys = list(junk_table.keys())
# Chao Junk
if self.any_chao_locations_active():
junk_keys += list(chaos_drives_table.keys())
return self.multiworld.random.choice(junk_keys)
def set_rules(self):
set_rules(self.multiworld, self.player, self.gate_bosses, self.boss_rush_map, self.mission_map, self.mission_count_map)
set_rules(self.multiworld, self, self.player, self.gate_bosses, self.boss_rush_map, self.mission_map, self.mission_count_map, self.black_market_costs)
def write_spoiler(self, spoiler_handle: typing.TextIO):
if self.multiworld.number_of_level_gates[self.player].value > 0 or self.multiworld.goal[self.player].value in [4, 5, 6]:
if self.options.number_of_level_gates.value > 0 or self.options.goal.value in [4, 5, 6]:
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)
if self.multiworld.number_of_level_gates[self.player].value > 0:
if self.options.number_of_level_gates.value > 0:
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)
spoiler_handle.write("\n")
if self.multiworld.goal[self.player].value in [4, 5, 6]:
if self.options.goal.value in [4, 5, 6]:
for x in range(len(self.boss_rush_map.values())):
text = "Boss Rush Boss {0}: {1}\n"
text = text.format((x + 1), get_boss_name(self.boss_rush_map[x]))
@ -459,12 +410,21 @@ class SA2BWorld(World):
]
no_hint_region_names = [
LocationName.cannon_core_region,
LocationName.chao_garden_beginner_region,
LocationName.chao_garden_intermediate_region,
LocationName.chao_garden_expert_region,
LocationName.chao_race_beginner_region,
LocationName.chao_race_intermediate_region,
LocationName.chao_race_expert_region,
LocationName.chao_karate_beginner_region,
LocationName.chao_karate_intermediate_region,
LocationName.chao_karate_expert_region,
LocationName.chao_karate_super_region,
LocationName.kart_race_beginner_region,
LocationName.kart_race_standard_region,
LocationName.kart_race_expert_region,
LocationName.chao_kindergarten_region,
LocationName.black_market_region,
]
er_hint_data = {}
for i in range(self.multiworld.number_of_level_gates[self.player].value + 1):
for i in range(self.options.number_of_level_gates.value + 1):
gate_name = gate_names[i]
gate_region = self.multiworld.get_region(gate_name, self.player)
if not gate_region:
@ -476,10 +436,353 @@ class SA2BWorld(World):
for location in level_region.locations:
er_hint_data[location.address] = gate_name
for i in range(self.options.black_market_slots.value):
location = self.multiworld.get_location(LocationName.chao_black_market_base + str(i + 1), self.player)
er_hint_data[location.address] = str(self.black_market_costs[i]) + " " + str(ItemName.market_token)
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"):
def stage_fill_hook(cls, multiworld: MultiWorld, progitempool, usefulitempool, filleritempool, fill_locations):
if multiworld.get_game_players("Sonic Adventure 2 Battle"):
progitempool.sort(
key=lambda item: 0 if (item.name != 'Emblem') else 1)
def get_levels_per_gate(self) -> list:
levels_per_gate = list()
max_gate_index = self.options.number_of_level_gates
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.options.level_gate_distribution == 0 or self.options.level_gate_distribution == 2:
early_distribution = self.options.level_gate_distribution == 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 any_chao_locations_active(self) -> bool:
if self.options.chao_race_difficulty.value > 0 or \
self.options.chao_karate_difficulty.value > 0 or \
self.options.chao_stats.value > 0 or \
self.options.chao_animal_parts or \
self.options.chao_kindergarten or \
self.options.black_market_slots.value > 0:
return True;
return False
def generate_music_data(self) -> typing.Dict[int, int]:
if self.options.music_shuffle == "levels":
musiclist_o = list(range(0, 47))
musiclist_s = musiclist_o.copy()
self.random.shuffle(musiclist_s)
musiclist_o.extend(range(47, 78))
musiclist_s.extend(range(47, 78))
if self.options.sadx_music.value == 1:
musiclist_s = [x+100 for x in musiclist_s]
elif self.options.sadx_music.value == 2:
for i in range(len(musiclist_s)):
if self.random.randint(0,1):
musiclist_s[i] += 100
return dict(zip(musiclist_o, musiclist_s))
elif self.options.music_shuffle == "full":
musiclist_o = list(range(0, 78))
musiclist_s = musiclist_o.copy()
self.random.shuffle(musiclist_s)
if self.options.sadx_music.value == 1:
musiclist_s = [x+100 for x in musiclist_s]
elif self.options.sadx_music.value == 2:
for i in range(len(musiclist_s)):
if self.random.randint(0,1):
musiclist_s[i] += 100
return dict(zip(musiclist_o, musiclist_s))
elif self.options.music_shuffle == "singularity":
musiclist_o = list(range(0, 78))
musiclist_s = [self.random.choice(musiclist_o)] * len(musiclist_o)
if self.options.sadx_music.value == 1:
musiclist_s = [x+100 for x in musiclist_s]
elif self.options.sadx_music.value == 2:
if self.random.randint(0,1):
musiclist_s = [x+100 for x in musiclist_s]
return dict(zip(musiclist_o, musiclist_s))
else:
musiclist_o = list(range(0, 78))
musiclist_s = musiclist_o.copy()
if self.options.sadx_music.value == 1:
musiclist_s = [x+100 for x in musiclist_s]
elif self.options.sadx_music.value == 2:
for i in range(len(musiclist_s)):
if self.random.randint(0,1):
musiclist_s[i] += 100
return dict(zip(musiclist_o, musiclist_s))
def generate_voice_data(self) -> typing.Dict[int, int]:
if self.options.voice_shuffle == "shuffled":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.random.shuffle(voicelist_s)
return dict(zip(voicelist_o, voicelist_s))
elif self.options.voice_shuffle == "rude":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.random.shuffle(voicelist_s)
for i in range(len(voicelist_s)):
if self.random.randint(1,100) > 80:
voicelist_s[i] = 17
return dict(zip(voicelist_o, voicelist_s))
elif self.options.voice_shuffle == "chao":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.random.shuffle(voicelist_s)
for i in range(len(voicelist_s)):
voicelist_s[i] = self.random.choice(range(2586, 2608))
return dict(zip(voicelist_o, voicelist_s))
elif self.options.voice_shuffle == "singularity":
voicelist_o = list(range(0, 2623))
voicelist_s = [self.random.choice(voicelist_o)] * len(voicelist_o)
return dict(zip(voicelist_o, voicelist_s))
else:
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
return dict(zip(voicelist_o, voicelist_s))
def generate_chao_egg_data(self) -> typing.Dict[int, int]:
if self.options.shuffle_starting_chao_eggs:
egglist_o = list(range(0, 4))
egglist_s = self.random.sample(range(0,54), 4)
return dict(zip(egglist_o, egglist_s))
else:
# Indicate these are not shuffled
egglist_o = [0, 1, 2, 3]
egglist_s = [255, 255, 255, 255]
return dict(zip(egglist_o, egglist_s))
def generate_chao_name_data(self) -> typing.Dict[int, int]:
number_of_names = 30
name_list_o = list(range(number_of_names * 7))
name_list_s = []
name_list_base = []
name_list_copy = list(self.multiworld.player_name.values())
name_list_copy.remove(self.multiworld.player_name[self.player])
if len(name_list_copy) >= number_of_names:
name_list_base = self.random.sample(name_list_copy, number_of_names)
else:
name_list_base = name_list_copy
self.random.shuffle(name_list_base)
name_list_base += self.random.sample(sample_chao_names, number_of_names - len(name_list_base))
for name in name_list_base:
for char_idx in range(7):
if char_idx < len(name):
name_list_s.append(chao_name_conversion[name[char_idx]])
else:
name_list_s.append(0x00)
return dict(zip(name_list_o, name_list_s))
def generate_black_market_data(self) -> typing.Dict[int, int]:
if self.options.black_market_slots.value == 0:
return {}
ring_costs = [50, 75, 100]
market_data = {}
item_names = []
player_names = []
progression_flags = []
totally_real_item_names_copy = totally_real_item_names.copy()
location_names = [(LocationName.chao_black_market_base + str(i)) for i in range(1, self.options.black_market_slots.value + 1)]
locations = [self.multiworld.get_location(location_name, self.player) for location_name in location_names]
for location in locations:
if location.item.classification & ItemClassification.trap:
item_name = self.random.choice(totally_real_item_names_copy)
totally_real_item_names_copy.remove(item_name)
item_names.append(item_name)
else:
item_names.append(location.item.name)
player_names.append(self.multiworld.player_name[location.item.player])
if location.item.classification & ItemClassification.progression or location.item.classification & ItemClassification.trap:
progression_flags.append(2)
elif location.item.classification & ItemClassification.useful:
progression_flags.append(1)
else:
progression_flags.append(0)
for item_idx in range(self.options.black_market_slots.value):
for chr_idx in range(len(item_names[item_idx][:26])):
market_data[(item_idx * 46) + chr_idx] = ord(item_names[item_idx][chr_idx])
for chr_idx in range(len(player_names[item_idx][:16])):
market_data[(item_idx * 46) + 26 + chr_idx] = ord(player_names[item_idx][chr_idx])
market_data[(item_idx * 46) + 42] = ring_costs[progression_flags[item_idx]] * self.options.black_market_price_multiplier.value
return market_data
def generate_er_layout(self) -> typing.Dict[int, int]:
if not self.options.chao_entrance_randomization:
return {}
er_layout = {}
start_exit = self.random.randint(0, 3)
accessible_rooms = []
multi_rooms_copy = multi_rooms.copy()
single_rooms_copy = single_rooms.copy()
all_exits_copy = all_exits.copy()
all_destinations_copy = all_destinations.copy()
multi_rooms_copy.remove(0x07)
accessible_rooms.append(0x07)
# Place Kindergarten somewhere sane
exit_choice = self.random.choice(valid_kindergarten_exits)
exit_room = exit_to_room_map[exit_choice]
all_exits_copy.remove(exit_choice)
multi_rooms_copy.remove(exit_room)
destination = 0x06
single_rooms_copy.remove(destination)
all_destinations_copy.remove(destination)
er_layout[exit_choice] = destination
reverse_exit = self.random.choice(room_to_exits_map[destination])
er_layout[reverse_exit] = exit_to_room_map[exit_choice]
all_exits_copy.remove(reverse_exit)
all_destinations_copy.remove(exit_room)
# Connect multi-exit rooms
loop_guard = 0
while len(multi_rooms_copy) > 0:
loop_guard += 1
if loop_guard > 2000:
logging.warning(f"Failed to generate Chao Entrance Randomization for player: {self.multiworld.player_name[self.player]}")
return {}
exit_room = self.random.choice(accessible_rooms)
possible_exits = [exit for exit in room_to_exits_map[exit_room] if exit in all_exits_copy]
if len(possible_exits) == 0:
continue
exit_choice = self.random.choice(possible_exits)
all_exits_copy.remove(exit_choice)
destination = self.random.choice(multi_rooms_copy)
multi_rooms_copy.remove(destination)
all_destinations_copy.remove(destination)
accessible_rooms.append(destination)
er_layout[exit_choice] = destination
reverse_exit = self.random.choice(room_to_exits_map[destination])
er_layout[reverse_exit] = exit_room
all_exits_copy.remove(reverse_exit)
all_destinations_copy.remove(exit_room)
# Connect dead-end rooms
loop_guard = 0
while len(single_rooms_copy) > 0:
loop_guard += 1
if loop_guard > 2000:
logging.warning(f"Failed to generate Chao Entrance Randomization for player: {self.multiworld.player_name[self.player]}")
return {}
exit_room = self.random.choice(accessible_rooms)
possible_exits = [exit for exit in room_to_exits_map[exit_room] if exit in all_exits_copy]
if len(possible_exits) == 0:
continue
exit_choice = self.random.choice(possible_exits)
all_exits_copy.remove(exit_choice)
destination = self.random.choice(single_rooms_copy)
single_rooms_copy.remove(destination)
all_destinations_copy.remove(destination)
er_layout[exit_choice] = destination
reverse_exit = self.random.choice(room_to_exits_map[destination])
er_layout[reverse_exit] = exit_room
all_exits_copy.remove(reverse_exit)
all_destinations_copy.remove(exit_room)
# Connect remaining exits
loop_guard = 0
while len(all_exits_copy) > 0:
loop_guard += 1
if loop_guard > 2000:
logging.warning(f"Failed to generate Chao Entrance Randomization for player: {self.multiworld.player_name[self.player]}")
return {}
exit_room = self.random.choice(all_destinations_copy)
possible_exits = [exit for exit in room_to_exits_map[exit_room] if exit in all_exits_copy]
if len(possible_exits) == 0:
continue
exit_choice = self.random.choice(possible_exits)
all_exits_copy.remove(exit_choice)
all_destinations_copy.remove(exit_room)
destination = self.random.choice(all_destinations_copy)
all_destinations_copy.remove(destination)
er_layout[exit_choice] = destination
possible_reverse_exits = [exit for exit in room_to_exits_map[destination] if exit in all_exits_copy]
reverse_exit = self.random.choice(possible_reverse_exits)
er_layout[reverse_exit] = exit_room
all_exits_copy.remove(reverse_exit)
return er_layout

View File

@ -127,9 +127,6 @@ If you wish to use the `SADX Music` option of the Randomizer, you must own a cop
- Mission 1 is missing a texture in the stage select UI.
- Most likely another mod is conflicting and overwriting the texture pack. It is recommeded to have the SA2B Archipelago mod load last in the mod loader.
- Received Cutscene Traps don't play after beating a level.
- Make sure you don't have the "`Skip Intro`" option enabled in the mod manager.
## Save File Safeguard (Advanced Option)
The mod contains a save file safeguard which associates a savefile to a specific Archipelago seed. By default, save files can only connect to Archipelago servers that match their seed. The safeguard can be disabled in the mod config.ini by setting `IgnoreFileSafety` to true. This is NOT recommended for the standard user as it will allow any save file to connect and send items to the Archipelago server.