diff --git a/README.md b/README.md index 98caa273..e073c383 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Currently, the following games are supported: * ArchipIDLE * Hollow Knight * The Witness +* Sonic Adventure 2: Battle For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/worlds/sa2b/Items.py b/worlds/sa2b/Items.py new file mode 100644 index 00000000..0d1f60ce --- /dev/null +++ b/worlds/sa2b/Items.py @@ -0,0 +1,76 @@ +import typing + +from BaseClasses import Item +from .Names import ItemName + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + progression: bool + quantity: int = 1 + event: bool = False + + +class SA2BItem(Item): + game: str = "Sonic Adventure 2: Battle" + + def __init__(self, name, advancement: bool = False, code: int = None, player: int = None): + super(SA2BItem, self).__init__(name, advancement, code, player) + + if self.name == ItemName.sonic_light_shoes or self.name == ItemName.shadow_air_shoes: + self.pedestal_credit_text = "and the Soap Shoes" + + +# Separate tables for each type of item. +emblems_table = { + ItemName.emblem: ItemData(0xFF0000, True), +} + +upgrades_table = { + ItemName.sonic_gloves: ItemData(0xFF0001, False), + ItemName.sonic_light_shoes: ItemData(0xFF0002, True), + ItemName.sonic_ancient_light: ItemData(0xFF0003, False), + ItemName.sonic_bounce_bracelet: ItemData(0xFF0004, True), + ItemName.sonic_flame_ring: ItemData(0xFF0005, True), + ItemName.sonic_mystic_melody: ItemData(0xFF0006, True), + + ItemName.tails_laser_blaster: ItemData(0xFF0007, False), + ItemName.tails_booster: ItemData(0xFF0008, True), + ItemName.tails_mystic_melody: ItemData(0xFF0009, True), + ItemName.tails_bazooka: ItemData(0xFF000A, True), + + ItemName.knuckles_mystic_melody: ItemData(0xFF000B, True), + ItemName.knuckles_shovel_claws: ItemData(0xFF000C, True), + ItemName.knuckles_air_necklace: ItemData(0xFF000D, True), + ItemName.knuckles_hammer_gloves: ItemData(0xFF000E, True), + ItemName.knuckles_sunglasses: ItemData(0xFF000F, True), + + ItemName.shadow_flame_ring: ItemData(0xFF0010, True), + ItemName.shadow_air_shoes: ItemData(0xFF0011, True), + ItemName.shadow_ancient_light: ItemData(0xFF0012, False), + ItemName.shadow_mystic_melody: ItemData(0xFF0013, True), + + ItemName.eggman_laser_blaster: ItemData(0xFF0014, False), + ItemName.eggman_mystic_melody: ItemData(0xFF0015, True), + ItemName.eggman_jet_engine: ItemData(0xFF0016, True), + ItemName.eggman_large_cannon: ItemData(0xFF0017, True), + ItemName.eggman_protective_armor: ItemData(0xFF0018, False), + + ItemName.rouge_mystic_melody: ItemData(0xFF0019, True), + ItemName.rouge_pick_nails: ItemData(0xFF001A, True), + ItemName.rouge_treasure_scope: ItemData(0xFF001B, True), + ItemName.rouge_iron_boots: ItemData(0xFF001C, True), +} + +event_table = { + ItemName.maria: ItemData(0xFF001D, True), +} + +# Complete item table. +item_table = { + **emblems_table, + **upgrades_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} diff --git a/worlds/sa2b/Locations.py b/worlds/sa2b/Locations.py new file mode 100644 index 00000000..11c301f8 --- /dev/null +++ b/worlds/sa2b/Locations.py @@ -0,0 +1,277 @@ +import typing + +from BaseClasses import Location +from .Names import LocationName + + +class SA2BLocation(Location): + game: str = "Sonic Adventure 2: Battle" + + +first_mission_location_table = { + LocationName.city_escape_1: 0xFF0000, + LocationName.wild_canyon_1: 0xFF0001, + LocationName.prison_lane_1: 0xFF0002, + LocationName.metal_harbor_1: 0xFF0003, + LocationName.green_forest_1: 0xFF0004, + LocationName.pumpkin_hill_1: 0xFF0005, + LocationName.mission_street_1: 0xFF0006, + LocationName.aquatic_mine_1: 0xFF0007, + LocationName.route_101_1: 0xFF0008, + LocationName.hidden_base_1: 0xFF0009, + LocationName.pyramid_cave_1: 0xFF000A, + LocationName.death_chamber_1: 0xFF000B, + LocationName.eternal_engine_1: 0xFF000C, + LocationName.meteor_herd_1: 0xFF000D, + LocationName.crazy_gadget_1: 0xFF000E, + LocationName.final_rush_1: 0xFF000F, + + LocationName.iron_gate_1: 0xFF0010, + LocationName.dry_lagoon_1: 0xFF0011, + LocationName.sand_ocean_1: 0xFF0012, + LocationName.radical_highway_1: 0xFF0013, + LocationName.egg_quarters_1: 0xFF0014, + LocationName.lost_colony_1: 0xFF0015, + LocationName.weapons_bed_1: 0xFF0016, + LocationName.security_hall_1: 0xFF0017, + LocationName.white_jungle_1: 0xFF0018, + LocationName.route_280_1: 0xFF0019, + LocationName.sky_rail_1: 0xFF001A, + LocationName.mad_space_1: 0xFF001B, + LocationName.cosmic_wall_1: 0xFF001C, + LocationName.final_chase_1: 0xFF001D, + + LocationName.cannon_core_1: 0xFF001E, +} + +second_mission_location_table = { + LocationName.city_escape_2: 0xFF0020, + LocationName.wild_canyon_2: 0xFF0021, + LocationName.prison_lane_2: 0xFF0022, + LocationName.metal_harbor_2: 0xFF0023, + LocationName.green_forest_2: 0xFF0024, + LocationName.pumpkin_hill_2: 0xFF0025, + LocationName.mission_street_2: 0xFF0026, + LocationName.aquatic_mine_2: 0xFF0027, + LocationName.route_101_2: 0xFF0028, + LocationName.hidden_base_2: 0xFF0029, + LocationName.pyramid_cave_2: 0xFF002A, + LocationName.death_chamber_2: 0xFF002B, + LocationName.eternal_engine_2: 0xFF002C, + LocationName.meteor_herd_2: 0xFF002D, + LocationName.crazy_gadget_2: 0xFF002E, + LocationName.final_rush_2: 0xFF002F, + + LocationName.iron_gate_2: 0xFF0030, + LocationName.dry_lagoon_2: 0xFF0031, + LocationName.sand_ocean_2: 0xFF0032, + LocationName.radical_highway_2: 0xFF0033, + LocationName.egg_quarters_2: 0xFF0034, + LocationName.lost_colony_2: 0xFF0035, + LocationName.weapons_bed_2: 0xFF0036, + LocationName.security_hall_2: 0xFF0037, + LocationName.white_jungle_2: 0xFF0038, + LocationName.route_280_2: 0xFF0039, + LocationName.sky_rail_2: 0xFF003A, + LocationName.mad_space_2: 0xFF003B, + LocationName.cosmic_wall_2: 0xFF003C, + LocationName.final_chase_2: 0xFF003D, + + LocationName.cannon_core_2: 0xFF003E, +} + +third_mission_location_table = { + LocationName.city_escape_3: 0xFF0040, + LocationName.wild_canyon_3: 0xFF0041, + LocationName.prison_lane_3: 0xFF0042, + LocationName.metal_harbor_3: 0xFF0043, + LocationName.green_forest_3: 0xFF0044, + LocationName.pumpkin_hill_3: 0xFF0045, + LocationName.mission_street_3: 0xFF0046, + LocationName.aquatic_mine_3: 0xFF0047, + LocationName.route_101_3: 0xFF0048, + LocationName.hidden_base_3: 0xFF0049, + LocationName.pyramid_cave_3: 0xFF004A, + LocationName.death_chamber_3: 0xFF004B, + LocationName.eternal_engine_3: 0xFF004C, + LocationName.meteor_herd_3: 0xFF004D, + LocationName.crazy_gadget_3: 0xFF004E, + LocationName.final_rush_3: 0xFF004F, + + LocationName.iron_gate_3: 0xFF0050, + LocationName.dry_lagoon_3: 0xFF0051, + LocationName.sand_ocean_3: 0xFF0052, + LocationName.radical_highway_3: 0xFF0053, + LocationName.egg_quarters_3: 0xFF0054, + LocationName.lost_colony_3: 0xFF0055, + LocationName.weapons_bed_3: 0xFF0056, + LocationName.security_hall_3: 0xFF0057, + LocationName.white_jungle_3: 0xFF0058, + LocationName.route_280_3: 0xFF0059, + LocationName.sky_rail_3: 0xFF005A, + LocationName.mad_space_3: 0xFF005B, + LocationName.cosmic_wall_3: 0xFF005C, + LocationName.final_chase_3: 0xFF005D, + + LocationName.cannon_core_3: 0xFF005E, +} + +fourth_mission_location_table = { + LocationName.city_escape_4: 0xFF0060, + LocationName.wild_canyon_4: 0xFF0061, + LocationName.prison_lane_4: 0xFF0062, + LocationName.metal_harbor_4: 0xFF0063, + LocationName.green_forest_4: 0xFF0064, + LocationName.pumpkin_hill_4: 0xFF0065, + LocationName.mission_street_4: 0xFF0066, + LocationName.aquatic_mine_4: 0xFF0067, + LocationName.route_101_4: 0xFF0068, + LocationName.hidden_base_4: 0xFF0069, + LocationName.pyramid_cave_4: 0xFF006A, + LocationName.death_chamber_4: 0xFF006B, + LocationName.eternal_engine_4: 0xFF006C, + LocationName.meteor_herd_4: 0xFF006D, + LocationName.crazy_gadget_4: 0xFF006E, + LocationName.final_rush_4: 0xFF006F, + + LocationName.iron_gate_4: 0xFF0070, + LocationName.dry_lagoon_4: 0xFF0071, + LocationName.sand_ocean_4: 0xFF0072, + LocationName.radical_highway_4: 0xFF0073, + LocationName.egg_quarters_4: 0xFF0074, + LocationName.lost_colony_4: 0xFF0075, + LocationName.weapons_bed_4: 0xFF0076, + LocationName.security_hall_4: 0xFF0077, + LocationName.white_jungle_4: 0xFF0078, + LocationName.route_280_4: 0xFF0079, + LocationName.sky_rail_4: 0xFF007A, + LocationName.mad_space_4: 0xFF007B, + LocationName.cosmic_wall_4: 0xFF007C, + LocationName.final_chase_4: 0xFF007D, + + LocationName.cannon_core_4: 0xFF007E, +} + +fifth_mission_location_table = { + LocationName.city_escape_5: 0xFF0080, + LocationName.wild_canyon_5: 0xFF0081, + LocationName.prison_lane_5: 0xFF0082, + LocationName.metal_harbor_5: 0xFF0083, + LocationName.green_forest_5: 0xFF0084, + LocationName.pumpkin_hill_5: 0xFF0085, + LocationName.mission_street_5: 0xFF0086, + LocationName.aquatic_mine_5: 0xFF0087, + LocationName.route_101_5: 0xFF0088, + LocationName.hidden_base_5: 0xFF0089, + LocationName.pyramid_cave_5: 0xFF008A, + LocationName.death_chamber_5: 0xFF008B, + LocationName.eternal_engine_5: 0xFF008C, + LocationName.meteor_herd_5: 0xFF008D, + LocationName.crazy_gadget_5: 0xFF008E, + LocationName.final_rush_5: 0xFF008F, + + LocationName.iron_gate_5: 0xFF0090, + LocationName.dry_lagoon_5: 0xFF0091, + LocationName.sand_ocean_5: 0xFF0092, + LocationName.radical_highway_5: 0xFF0093, + LocationName.egg_quarters_5: 0xFF0094, + LocationName.lost_colony_5: 0xFF0095, + LocationName.weapons_bed_5: 0xFF0096, + LocationName.security_hall_5: 0xFF0097, + LocationName.white_jungle_5: 0xFF0098, + LocationName.route_280_5: 0xFF0099, + LocationName.sky_rail_5: 0xFF009A, + LocationName.mad_space_5: 0xFF009B, + LocationName.cosmic_wall_5: 0xFF009C, + LocationName.final_chase_5: 0xFF009D, + + LocationName.cannon_core_5: 0xFF009E, +} + +upgrade_location_table = { + LocationName.city_escape_upgrade: 0xFF00A0, + LocationName.wild_canyon_upgrade: 0xFF00A1, + LocationName.prison_lane_upgrade: 0xFF00A2, + LocationName.metal_harbor_upgrade: 0xFF00A3, + LocationName.green_forest_upgrade: 0xFF00A4, + LocationName.pumpkin_hill_upgrade: 0xFF00A5, + LocationName.mission_street_upgrade: 0xFF00A6, + LocationName.aquatic_mine_upgrade: 0xFF00A7, + LocationName.hidden_base_upgrade: 0xFF00A9, + LocationName.pyramid_cave_upgrade: 0xFF00AA, + LocationName.death_chamber_upgrade: 0xFF00AB, + LocationName.eternal_engine_upgrade: 0xFF00AC, + LocationName.meteor_herd_upgrade: 0xFF00AD, + LocationName.crazy_gadget_upgrade: 0xFF00AE, + LocationName.final_rush_upgrade: 0xFF00AF, + + LocationName.iron_gate_upgrade: 0xFF00B0, + LocationName.dry_lagoon_upgrade: 0xFF00B1, + LocationName.sand_ocean_upgrade: 0xFF00B2, + LocationName.radical_highway_upgrade: 0xFF00B3, + LocationName.egg_quarters_upgrade: 0xFF00B4, + LocationName.lost_colony_upgrade: 0xFF00B5, + LocationName.weapons_bed_upgrade: 0xFF00B6, + LocationName.security_hall_upgrade: 0xFF00B7, + LocationName.white_jungle_upgrade: 0xFF00B8, + LocationName.sky_rail_upgrade: 0xFF00BA, + LocationName.mad_space_upgrade: 0xFF00BB, + LocationName.cosmic_wall_upgrade: 0xFF00BC, + LocationName.final_chase_upgrade: 0xFF00BD, +} + +chao_garden_location_table = { + LocationName.chao_beginner_race: 0xFF00C0, + LocationName.chao_jewel_race: 0xFF00C1, + LocationName.chao_challenge_race: 0xFF00C2, + LocationName.chao_hero_race: 0xFF00C3, + LocationName.chao_dark_race: 0xFF00C4, + + LocationName.chao_beginner_karate: 0xFF00C5, + LocationName.chao_standard_karate: 0xFF00C6, + LocationName.chao_expert_karate: 0xFF00C7, + LocationName.chao_super_karate: 0xFF00C8, +} + +other_location_table = { + # LocationName.green_hill: 0xFF001F, + LocationName.biolizard: 0xFF003F, +} + +all_locations = { + **first_mission_location_table, + **second_mission_location_table, + **third_mission_location_table, + **fourth_mission_location_table, + **fifth_mission_location_table, + **upgrade_location_table, + **chao_garden_location_table, + **other_location_table, +} + +location_table = {} + + +def setup_locations(world, player: int): + location_table = {**first_mission_location_table} + + if world.IncludeMissions[player].value >= 2: + location_table.update({**second_mission_location_table}) + + if world.IncludeMissions[player].value >= 3: + location_table.update({**third_mission_location_table}) + + if world.IncludeMissions[player].value >= 4: + location_table.update({**fourth_mission_location_table}) + + if world.IncludeMissions[player].value >= 5: + location_table.update({**fifth_mission_location_table}) + + location_table.update({**upgrade_location_table}) + # location_table.update(**chao_garden_location_table}) + location_table.update({**other_location_table}) + + return location_table + + +lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in all_locations.items()} diff --git a/worlds/sa2b/Names/ItemName.py b/worlds/sa2b/Names/ItemName.py new file mode 100644 index 00000000..db6cc304 --- /dev/null +++ b/worlds/sa2b/Names/ItemName.py @@ -0,0 +1,39 @@ +# Emblem Definition +emblem = "Emblem" + +# Upgrade Definitions +sonic_gloves = "Sonic - Magic Glove" +sonic_light_shoes = "Sonic - Light Shoes" +sonic_ancient_light = "Sonic - Ancient Light" +sonic_bounce_bracelet = "Sonic - Bounce Bracelet" +sonic_flame_ring = "Sonic - Flame Ring" +sonic_mystic_melody = "Sonic - Mystic Melody" + +tails_laser_blaster = "Tails - Laser Blaster" +tails_booster = "Tails - Booster" +tails_mystic_melody = "Tails - Mystic Melody" +tails_bazooka = "Tails - Bazooka" + +knuckles_mystic_melody = "Knuckles - Mystic Melody" +knuckles_shovel_claws = "Knuckles - Shovel Claws" +knuckles_air_necklace = "Knuckles - Air Necklace" +knuckles_hammer_gloves = "Knuckles - Hammer Gloves" +knuckles_sunglasses = "Knuckles - Sunglasses" + +shadow_flame_ring = "Shadow - Flame Ring" +shadow_air_shoes = "Shadow - Air Shoes" +shadow_ancient_light = "Shadow - Ancient Light" +shadow_mystic_melody = "Shadow - Mystic Melody" + +eggman_laser_blaster = "Eggman - Laser Blaster" +eggman_mystic_melody = "Eggman - Mystic Melody" +eggman_jet_engine = "Eggman - Jet Engine" +eggman_large_cannon = "Eggman - Large Cannon" +eggman_protective_armor = "Eggman - Protective Armor" + +rouge_mystic_melody = "Rouge - Mystic Melody" +rouge_pick_nails = "Rouge - Pick Nails" +rouge_treasure_scope = "Rouge - Treasure Scope" +rouge_iron_boots = "Rouge - Iron Boots" + +maria = "What Maria Wanted" diff --git a/worlds/sa2b/Names/LocationName.py b/worlds/sa2b/Names/LocationName.py new file mode 100644 index 00000000..cf9721d3 --- /dev/null +++ b/worlds/sa2b/Names/LocationName.py @@ -0,0 +1,264 @@ +# Sonic Mission Definitions +city_escape_1 = "City Escape - 1" +city_escape_2 = "City Escape - 2" +city_escape_3 = "City Escape - 3" +city_escape_4 = "City Escape - 4" +city_escape_5 = "City Escape - 5" +city_escape_upgrade = "City Escape - Upgrade" +metal_harbor_1 = "Metal Harbor - 1" +metal_harbor_2 = "Metal Harbor - 2" +metal_harbor_3 = "Metal Harbor - 3" +metal_harbor_4 = "Metal Harbor - 4" +metal_harbor_5 = "Metal Harbor - 5" +metal_harbor_upgrade = "Metal Harbor - Upgrade" +green_forest_1 = "Green Forest - 1" +green_forest_2 = "Green Forest - 2" +green_forest_3 = "Green Forest - 3" +green_forest_4 = "Green Forest - 4" +green_forest_5 = "Green Forest - 5" +green_forest_upgrade = "Green Forest - Upgrade" +pyramid_cave_1 = "Pyramid Cave - 1" +pyramid_cave_2 = "Pyramid Cave - 2" +pyramid_cave_3 = "Pyramid Cave - 3" +pyramid_cave_4 = "Pyramid Cave - 4" +pyramid_cave_5 = "Pyramid Cave - 5" +pyramid_cave_upgrade = "Pyramid Cave - Upgrade" +crazy_gadget_1 = "Crazy Gadget - 1" +crazy_gadget_2 = "Crazy Gadget - 2" +crazy_gadget_3 = "Crazy Gadget - 3" +crazy_gadget_4 = "Crazy Gadget - 4" +crazy_gadget_5 = "Crazy Gadget - 5" +crazy_gadget_upgrade = "Crazy Gadget - Upgrade" +final_rush_1 = "Final Rush - 1" +final_rush_2 = "Final Rush - 2" +final_rush_3 = "Final Rush - 3" +final_rush_4 = "Final Rush - 4" +final_rush_5 = "Final Rush - 5" +final_rush_upgrade = "Final Rush - Upgrade" + +# Tails Mission Definitions +prison_lane_1 = "Prison Lane - 1" +prison_lane_2 = "Prison Lane - 2" +prison_lane_3 = "Prison Lane - 3" +prison_lane_4 = "Prison Lane - 4" +prison_lane_5 = "Prison Lane - 5" +prison_lane_upgrade = "Prison Lane - Upgrade" +mission_street_1 = "Mission Street - 1" +mission_street_2 = "Mission Street - 2" +mission_street_3 = "Mission Street - 3" +mission_street_4 = "Mission Street - 4" +mission_street_5 = "Mission Street - 5" +mission_street_upgrade = "Mission Street - Upgrade" +route_101_1 = "Route 101 - 1" +route_101_2 = "Route 101 - 2" +route_101_3 = "Route 101 - 3" +route_101_4 = "Route 101 - 4" +route_101_5 = "Route 101 - 5" +hidden_base_1 = "Hidden Base - 1" +hidden_base_2 = "Hidden Base - 2" +hidden_base_3 = "Hidden Base - 3" +hidden_base_4 = "Hidden Base - 4" +hidden_base_5 = "Hidden Base - 5" +hidden_base_upgrade = "Hidden Base - Upgrade" +eternal_engine_1 = "Eternal Engine - 1" +eternal_engine_2 = "Eternal Engine - 2" +eternal_engine_3 = "Eternal Engine - 3" +eternal_engine_4 = "Eternal Engine - 4" +eternal_engine_5 = "Eternal Engine - 5" +eternal_engine_upgrade = "Eternal Engine - Upgrade" + +# Knuckles Mission Definitions +wild_canyon_1 = "Wild Canyon - 1" +wild_canyon_2 = "Wild Canyon - 2" +wild_canyon_3 = "Wild Canyon - 3" +wild_canyon_4 = "Wild Canyon - 4" +wild_canyon_5 = "Wild Canyon - 5" +wild_canyon_upgrade = "Wild Canyon - Upgrade" +pumpkin_hill_1 = "Pumpkin Hill - 1" +pumpkin_hill_2 = "Pumpkin Hill - 2" +pumpkin_hill_3 = "Pumpkin Hill - 3" +pumpkin_hill_4 = "Pumpkin Hill - 4" +pumpkin_hill_5 = "Pumpkin Hill - 5" +pumpkin_hill_upgrade = "Pumpkin Hill - Upgrade" +aquatic_mine_1 = "Aquatic Mine - 1" +aquatic_mine_2 = "Aquatic Mine - 2" +aquatic_mine_3 = "Aquatic Mine - 3" +aquatic_mine_4 = "Aquatic Mine - 4" +aquatic_mine_5 = "Aquatic Mine - 5" +aquatic_mine_upgrade = "Aquatic Mine - Upgrade" +death_chamber_1 = "Death Chamber - 1" +death_chamber_2 = "Death Chamber - 2" +death_chamber_3 = "Death Chamber - 3" +death_chamber_4 = "Death Chamber - 4" +death_chamber_5 = "Death Chamber - 5" +death_chamber_upgrade = "Death Chamber - Upgrade" +meteor_herd_1 = "Meteor Herd - 1" +meteor_herd_2 = "Meteor Herd - 2" +meteor_herd_3 = "Meteor Herd - 3" +meteor_herd_4 = "Meteor Herd - 4" +meteor_herd_5 = "Meteor Herd - 5" +meteor_herd_upgrade = "Meteor Herd - Upgrade" + + +# Shadow Mission Definitions +radical_highway_1 = "Radical Highway - 1" +radical_highway_2 = "Radical Highway - 2" +radical_highway_3 = "Radical Highway - 3" +radical_highway_4 = "Radical Highway - 4" +radical_highway_5 = "Radical Highway - 5" +radical_highway_upgrade = "Radical Highway - Upgrade" +white_jungle_1 = "White Jungle - 1" +white_jungle_2 = "White Jungle - 2" +white_jungle_3 = "White Jungle - 3" +white_jungle_4 = "White Jungle - 4" +white_jungle_5 = "White Jungle - 5" +white_jungle_upgrade = "White Jungle - Upgrade" +sky_rail_1 = "Sky Rail - 1" +sky_rail_2 = "Sky Rail - 2" +sky_rail_3 = "Sky Rail - 3" +sky_rail_4 = "Sky Rail - 4" +sky_rail_5 = "Sky Rail - 5" +sky_rail_upgrade = "Sky Rail - Upgrade" +final_chase_1 = "Final Chase - 1" +final_chase_2 = "Final Chase - 2" +final_chase_3 = "Final Chase - 3" +final_chase_4 = "Final Chase - 4" +final_chase_5 = "Final Chase - 5" +final_chase_upgrade = "Final Chase - Upgrade" + +# Eggman Mission Definitions +iron_gate_1 = "Iron Gate - 1" +iron_gate_2 = "Iron Gate - 2" +iron_gate_3 = "Iron Gate - 3" +iron_gate_4 = "Iron Gate - 4" +iron_gate_5 = "Iron Gate - 5" +iron_gate_upgrade = "Iron Gate - Upgrade" +sand_ocean_1 = "Sand Ocean - 1" +sand_ocean_2 = "Sand Ocean - 2" +sand_ocean_3 = "Sand Ocean - 3" +sand_ocean_4 = "Sand Ocean - 4" +sand_ocean_5 = "Sand Ocean - 5" +sand_ocean_upgrade = "Sand Ocean - Upgrade" +lost_colony_1 = "Lost Colony - 1" +lost_colony_2 = "Lost Colony - 2" +lost_colony_3 = "Lost Colony - 3" +lost_colony_4 = "Lost Colony - 4" +lost_colony_5 = "Lost Colony - 5" +lost_colony_upgrade = "Lost Colony - Upgrade" +weapons_bed_1 = "Weapons Bed - 1" +weapons_bed_2 = "Weapons Bed - 2" +weapons_bed_3 = "Weapons Bed - 3" +weapons_bed_4 = "Weapons Bed - 4" +weapons_bed_5 = "Weapons Bed - 5" +weapons_bed_upgrade = "Weapons Bed - Upgrade" +cosmic_wall_1 = "Cosmic Wall - 1" +cosmic_wall_2 = "Cosmic Wall - 2" +cosmic_wall_3 = "Cosmic Wall - 3" +cosmic_wall_4 = "Cosmic Wall - 4" +cosmic_wall_5 = "Cosmic Wall - 5" +cosmic_wall_upgrade = "Cosmic Wall - Upgrade" + +# Rouge Mission Definitions +dry_lagoon_1 = "Dry Lagoon - 1" +dry_lagoon_2 = "Dry Lagoon - 2" +dry_lagoon_3 = "Dry Lagoon - 3" +dry_lagoon_4 = "Dry Lagoon - 4" +dry_lagoon_5 = "Dry Lagoon - 5" +dry_lagoon_upgrade = "Dry Lagoon - Upgrade" +egg_quarters_1 = "Egg Quarters - 1" +egg_quarters_2 = "Egg Quarters - 2" +egg_quarters_3 = "Egg Quarters - 3" +egg_quarters_4 = "Egg Quarters - 4" +egg_quarters_5 = "Egg Quarters - 5" +egg_quarters_upgrade = "Egg Quarters - Upgrade" +security_hall_1 = "Security Hall - 1" +security_hall_2 = "Security Hall - 2" +security_hall_3 = "Security Hall - 3" +security_hall_4 = "Security Hall - 4" +security_hall_5 = "Security Hall - 5" +security_hall_upgrade = "Security Hall - Upgrade" +route_280_1 = "Route 280 - 1" +route_280_2 = "Route 280 - 2" +route_280_3 = "Route 280 - 3" +route_280_4 = "Route 280 - 4" +route_280_5 = "Route 280 - 5" +mad_space_1 = "Mad Space - 1" +mad_space_2 = "Mad Space - 2" +mad_space_3 = "Mad Space - 3" +mad_space_4 = "Mad Space - 4" +mad_space_5 = "Mad Space - 5" +mad_space_upgrade = "Mad Space - Upgrade" + +# Final Mission Definitions +cannon_core_1 = "Cannon Core - 1" +cannon_core_2 = "Cannon Core - 2" +cannon_core_3 = "Cannon Core - 3" +cannon_core_4 = "Cannon Core - 4" +cannon_core_5 = "Cannon Core - 5" + +# Chao Garden Definitions +chao_beginner_race = "Chao Garden - Beginner Race" +chao_jewel_race = "Chao Garden - Jewel Race" +chao_challenge_race = "Chao Garden - Challenge Race" +chao_hero_race = "Chao Garden - Hero Race" +chao_dark_race = "Chao Garden - Dark Race" +chao_beginner_karate = "Chao Garden - Beginner Karate" +chao_standard_karate = "Chao Garden - Standard Karate" +chao_expert_karate = "Chao Garden - Expert Karate" +chao_super_karate = "Chao Garden - Super Karate" + +# Other Definitions +green_hill = "Green Hill" +biolizard = "Biolizard" + + +# Region Definitions +menu_region = "Menu" +gate_0_region = "Gate 0" +gate_1_region = "Gate 1" +gate_2_region = "Gate 2" +gate_3_region = "Gate 3" +gate_4_region = "Gate 4" +gate_5_region = "Gate 5" + +city_escape_region = "City Escape" +metal_harbor_region = "Metal Harbor" +green_forest_region = "Green Forest" +pyramid_cave_region = "Pyramid Cave" +crazy_gadget_region = "Crazy Gadget" +final_rush_region = "Final Rush" + +prison_lane_region = "Prison Lane" +mission_street_region = "Mission Street" +route_101_region = "Route 101" +hidden_base_region = "Hidden Base" +eternal_engine_region = "Eternal Engine" + +wild_canyon_region = "Wild Canyon" +pumpkin_hill_region = "Pumpkin Hill" +aquatic_mine_region = "Aquatic Mine" +death_chamber_region = "Death Chamber" +meteor_herd_region = "Meteor Herd" + +radical_highway_region = "Radical Highway" +white_jungle_region = "White Jungle" +sky_rail_region = "Sky Rail" +final_chase_region = "Final Chase" + +iron_gate_region = "Iron Gate" +sand_ocean_region = "Sand Ocean" +lost_colony_region = "Lost Colony" +weapons_bed_region = "Weapons Bed" +cosmic_wall_region = "Cosmic Wall" + +dry_lagoon_region = "Dry Lagoon" +egg_quarters_region = "Egg Quarters" +security_hall_region = "Security Hall" +route_280_region = "Route 280" +mad_space_region = "Mad Space" + +cannon_core_region = "Cannon Core" + +biolizard_region = "Biolizard" + +chao_garden_region = "Chao Garden" diff --git a/worlds/sa2b/Options.py b/worlds/sa2b/Options.py new file mode 100644 index 00000000..ea1306c3 --- /dev/null +++ b/worlds/sa2b/Options.py @@ -0,0 +1,77 @@ +import typing + +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList + + +class IncludeMissions(Range): + """ + Allows logic to place items in a range of Missions for each level + Each mission setting includes lower settings + 1: Base Story Missions + 2: 100 Ring Missions + 3: Lost Chao Missions + 4: Timer Missions + 5: Hard Mode Missions + """ + display_name = "Include Missions" + range_start = 1 + range_end = 5 + default = 2 + + +class EmblemPercentageForCannonsCore(Range): + """ + Allows logic to gate the final mission behind a number of Emblems + """ + display_name = "Emblem Percentage for Cannons Core" + range_start = 0 + range_end = 75 + default = 50 + + +class NumberOfLevelGates(Range): + """ + Allows logic to gate some levels behind emblem requirements + """ + display_name = "Number of Level Gates" + range_start = 0 + range_end = 5 + default = 3 + + +class LevelGateDistribution(Choice): + """ + Determines how levels are distributed between level gate regions + Early: Earlier regions will have more levels than later regions + Even: Levels will be evenly distributed between all regions + Late: Later regions will have more levels than earlier regions + """ + display_name = "Level Gate Distribution" + option_early = 0 + option_even = 1 + option_late = 2 + default = 1 + + +class MusicShuffle(Choice): + """ + What type of Music Shuffle is used + Off: No music is shuffled. + Levels: Level music is shuffled. + Full: Level, Menu, and Additional music is shuffled. + """ + display_name = "Music Shuffle Type" + option_none = 0 + option_levels = 1 + option_full = 2 + default = 0 + + +sa2b_options: typing.Dict[str, type(Option)] = { + "DeathLink": DeathLink, + "MusicShuffle": MusicShuffle, + "IncludeMissions": IncludeMissions, + "EmblemPercentageForCannonsCore": EmblemPercentageForCannonsCore, + "NumberOfLevelGates": NumberOfLevelGates, + "LevelGateDistribution": LevelGateDistribution, +} diff --git a/worlds/sa2b/Regions.py b/worlds/sa2b/Regions.py new file mode 100644 index 00000000..aa13fd8a --- /dev/null +++ b/worlds/sa2b/Regions.py @@ -0,0 +1,579 @@ +import typing + +from BaseClasses import MultiWorld, Region, Entrance +from .Items import SA2BItem +from .Locations import SA2BLocation +from .Names import LocationName, ItemName + + +class LevelGate: + gate_levels: typing.List[int] + gate_emblem_count: int + + def __init__(self, emblems): + self.gate_emblem_count = emblems + self.gate_levels = list() + + +shuffleable_regions = [ + LocationName.city_escape_region, + LocationName.wild_canyon_region, + LocationName.prison_lane_region, + LocationName.metal_harbor_region, + LocationName.green_forest_region, + LocationName.pumpkin_hill_region, + LocationName.mission_street_region, + LocationName.aquatic_mine_region, + LocationName.route_101_region, + LocationName.hidden_base_region, + LocationName.pyramid_cave_region, + LocationName.death_chamber_region, + LocationName.eternal_engine_region, + LocationName.meteor_herd_region, + LocationName.crazy_gadget_region, + LocationName.final_rush_region, + LocationName.iron_gate_region, + LocationName.dry_lagoon_region, + LocationName.sand_ocean_region, + LocationName.radical_highway_region, + LocationName.egg_quarters_region, + LocationName.lost_colony_region, + LocationName.weapons_bed_region, + LocationName.security_hall_region, + LocationName.white_jungle_region, + LocationName.route_280_region, + LocationName.sky_rail_region, + LocationName.mad_space_region, + LocationName.cosmic_wall_region, + LocationName.final_chase_region, +] + +gate_0_blacklist_regions = [ + LocationName.hidden_base_region, + LocationName.eternal_engine_region, + LocationName.crazy_gadget_region, + LocationName.security_hall_region, + LocationName.cosmic_wall_region, +] + +gate_0_whitelist_regions = [ + LocationName.city_escape_region, + LocationName.wild_canyon_region, + LocationName.prison_lane_region, + LocationName.metal_harbor_region, + LocationName.green_forest_region, + LocationName.pumpkin_hill_region, + LocationName.mission_street_region, + LocationName.aquatic_mine_region, + LocationName.route_101_region, + LocationName.pyramid_cave_region, + LocationName.death_chamber_region, + LocationName.meteor_herd_region, + LocationName.final_rush_region, + LocationName.iron_gate_region, + LocationName.dry_lagoon_region, + LocationName.sand_ocean_region, + LocationName.radical_highway_region, + LocationName.egg_quarters_region, + LocationName.lost_colony_region, + LocationName.weapons_bed_region, + LocationName.white_jungle_region, + LocationName.route_280_region, + LocationName.sky_rail_region, + LocationName.mad_space_region, + LocationName.final_chase_region, +] + + +def create_regions(world, player: int, active_locations): + menu_region = create_region(world, player, active_locations, 'Menu', None, None) + gate_0_region = create_region(world, player, active_locations, 'Gate 0', None, None) + gate_1_region = create_region(world, player, active_locations, 'Gate 1', None, None) + gate_2_region = create_region(world, player, active_locations, 'Gate 2', None, None) + gate_3_region = create_region(world, player, active_locations, 'Gate 3', None, None) + gate_4_region = create_region(world, player, active_locations, 'Gate 4', None, None) + gate_5_region = create_region(world, player, active_locations, 'Gate 5', None, None) + + city_escape_region_locations = [ + LocationName.city_escape_1, + LocationName.city_escape_2, + LocationName.city_escape_3, + LocationName.city_escape_4, + LocationName.city_escape_5, + LocationName.city_escape_upgrade, + ] + city_escape_region = create_region(world, player, active_locations, LocationName.city_escape_region, + city_escape_region_locations, None) + + metal_harbor_region_locations = [ + LocationName.metal_harbor_1, + LocationName.metal_harbor_2, + LocationName.metal_harbor_3, + LocationName.metal_harbor_4, + LocationName.metal_harbor_5, + LocationName.metal_harbor_upgrade, + ] + metal_harbor_region = create_region(world, player, active_locations, LocationName.metal_harbor_region, + metal_harbor_region_locations, None) + + green_forest_region_locations = [ + LocationName.green_forest_1, + LocationName.green_forest_2, + LocationName.green_forest_3, + LocationName.green_forest_4, + LocationName.green_forest_5, + LocationName.green_forest_upgrade, + ] + green_forest_region = create_region(world, player, active_locations, LocationName.green_forest_region, + green_forest_region_locations, None) + + pyramid_cave_region_locations = [ + LocationName.pyramid_cave_1, + LocationName.pyramid_cave_2, + LocationName.pyramid_cave_3, + LocationName.pyramid_cave_4, + LocationName.pyramid_cave_5, + LocationName.pyramid_cave_upgrade, + ] + pyramid_cave_region = create_region(world, player, active_locations, LocationName.pyramid_cave_region, + pyramid_cave_region_locations, None) + + crazy_gadget_region_locations = [ + LocationName.crazy_gadget_1, + LocationName.crazy_gadget_2, + LocationName.crazy_gadget_3, + LocationName.crazy_gadget_4, + LocationName.crazy_gadget_5, + LocationName.crazy_gadget_upgrade, + ] + crazy_gadget_region = create_region(world, player, active_locations, LocationName.crazy_gadget_region, + crazy_gadget_region_locations, None) + + final_rush_region_locations = [ + LocationName.final_rush_1, + LocationName.final_rush_2, + LocationName.final_rush_3, + LocationName.final_rush_4, + LocationName.final_rush_5, + LocationName.final_rush_upgrade, + ] + final_rush_region = create_region(world, player, active_locations, LocationName.final_rush_region, + final_rush_region_locations, None) + + prison_lane_region_locations = [ + LocationName.prison_lane_1, + LocationName.prison_lane_2, + LocationName.prison_lane_3, + LocationName.prison_lane_4, + LocationName.prison_lane_5, + LocationName.prison_lane_upgrade, + ] + prison_lane_region = create_region(world, player, active_locations, LocationName.prison_lane_region, + prison_lane_region_locations, None) + + mission_street_region_locations = [ + LocationName.mission_street_1, + LocationName.mission_street_2, + LocationName.mission_street_3, + LocationName.mission_street_4, + LocationName.mission_street_5, + LocationName.mission_street_upgrade, + ] + mission_street_region = create_region(world, player, active_locations, LocationName.mission_street_region, + mission_street_region_locations, None) + + route_101_region_locations = [ + LocationName.route_101_1, + LocationName.route_101_2, + LocationName.route_101_3, + LocationName.route_101_4, + LocationName.route_101_5, + ] + route_101_region = create_region(world, player, active_locations, LocationName.route_101_region, + route_101_region_locations, None) + + hidden_base_region_locations = [ + LocationName.hidden_base_1, + LocationName.hidden_base_2, + LocationName.hidden_base_3, + LocationName.hidden_base_4, + LocationName.hidden_base_5, + LocationName.hidden_base_upgrade, + ] + hidden_base_region = create_region(world, player, active_locations, LocationName.hidden_base_region, + hidden_base_region_locations, None) + + eternal_engine_region_locations = [ + LocationName.eternal_engine_1, + LocationName.eternal_engine_2, + LocationName.eternal_engine_3, + LocationName.eternal_engine_4, + LocationName.eternal_engine_5, + LocationName.eternal_engine_upgrade, + ] + eternal_engine_region = create_region(world, player, active_locations, LocationName.eternal_engine_region, + eternal_engine_region_locations, None) + + wild_canyon_region_locations = [ + LocationName.wild_canyon_1, + LocationName.wild_canyon_2, + LocationName.wild_canyon_3, + LocationName.wild_canyon_4, + LocationName.wild_canyon_5, + LocationName.wild_canyon_upgrade, + ] + wild_canyon_region = create_region(world, player, active_locations, LocationName.wild_canyon_region, + wild_canyon_region_locations, None) + + pumpkin_hill_region_locations = [ + LocationName.pumpkin_hill_1, + LocationName.pumpkin_hill_2, + LocationName.pumpkin_hill_3, + LocationName.pumpkin_hill_4, + LocationName.pumpkin_hill_5, + LocationName.pumpkin_hill_upgrade, + ] + pumpkin_hill_region = create_region(world, player, active_locations, LocationName.pumpkin_hill_region, + pumpkin_hill_region_locations, None) + + aquatic_mine_region_locations = [ + LocationName.aquatic_mine_1, + LocationName.aquatic_mine_2, + LocationName.aquatic_mine_3, + LocationName.aquatic_mine_4, + LocationName.aquatic_mine_5, + LocationName.aquatic_mine_upgrade, + ] + aquatic_mine_region = create_region(world, player, active_locations, LocationName.aquatic_mine_region, + aquatic_mine_region_locations, None) + + death_chamber_region_locations = [ + LocationName.death_chamber_1, + LocationName.death_chamber_2, + LocationName.death_chamber_3, + LocationName.death_chamber_4, + LocationName.death_chamber_5, + LocationName.death_chamber_upgrade, + ] + death_chamber_region = create_region(world, player, active_locations, LocationName.death_chamber_region, + death_chamber_region_locations, None) + + meteor_herd_region_locations = [ + LocationName.meteor_herd_1, + LocationName.meteor_herd_2, + LocationName.meteor_herd_3, + LocationName.meteor_herd_4, + LocationName.meteor_herd_5, + LocationName.meteor_herd_upgrade, + ] + meteor_herd_region = create_region(world, player, active_locations, LocationName.meteor_herd_region, + meteor_herd_region_locations, None) + + radical_highway_region_locations = [ + LocationName.radical_highway_1, + LocationName.radical_highway_2, + LocationName.radical_highway_3, + LocationName.radical_highway_4, + LocationName.radical_highway_5, + LocationName.radical_highway_upgrade, + ] + radical_highway_region = create_region(world, player, active_locations, LocationName.radical_highway_region, + radical_highway_region_locations, None) + + white_jungle_region_locations = [ + LocationName.white_jungle_1, + LocationName.white_jungle_2, + LocationName.white_jungle_3, + LocationName.white_jungle_4, + LocationName.white_jungle_5, + LocationName.white_jungle_upgrade, + ] + white_jungle_region = create_region(world, player, active_locations, LocationName.white_jungle_region, + white_jungle_region_locations, None) + + sky_rail_region_locations = [ + LocationName.sky_rail_1, + LocationName.sky_rail_2, + LocationName.sky_rail_3, + LocationName.sky_rail_4, + LocationName.sky_rail_5, + LocationName.sky_rail_upgrade, + ] + sky_rail_region = create_region(world, player, active_locations, LocationName.sky_rail_region, + sky_rail_region_locations, None) + + final_chase_region_locations = [ + LocationName.final_chase_1, + LocationName.final_chase_2, + LocationName.final_chase_3, + LocationName.final_chase_4, + LocationName.final_chase_5, + LocationName.final_chase_upgrade, + ] + final_chase_region = create_region(world, player, active_locations, LocationName.final_chase_region, + final_chase_region_locations, None) + + iron_gate_region_locations = [ + LocationName.iron_gate_1, + LocationName.iron_gate_2, + LocationName.iron_gate_3, + LocationName.iron_gate_4, + LocationName.iron_gate_5, + LocationName.iron_gate_upgrade, + ] + iron_gate_region = create_region(world, player, active_locations, LocationName.iron_gate_region, + iron_gate_region_locations, None) + + sand_ocean_region_locations = [ + LocationName.sand_ocean_1, + LocationName.sand_ocean_2, + LocationName.sand_ocean_3, + LocationName.sand_ocean_4, + LocationName.sand_ocean_5, + LocationName.sand_ocean_upgrade, + ] + sand_ocean_region = create_region(world, player, active_locations, LocationName.sand_ocean_region, + sand_ocean_region_locations, None) + + lost_colony_region_locations = [ + LocationName.lost_colony_1, + LocationName.lost_colony_2, + LocationName.lost_colony_3, + LocationName.lost_colony_4, + LocationName.lost_colony_5, + LocationName.lost_colony_upgrade, + ] + lost_colony_region = create_region(world, player, active_locations, LocationName.lost_colony_region, + lost_colony_region_locations, None) + + weapons_bed_region_locations = [ + LocationName.weapons_bed_1, + LocationName.weapons_bed_2, + LocationName.weapons_bed_3, + LocationName.weapons_bed_4, + LocationName.weapons_bed_5, + LocationName.weapons_bed_upgrade, + ] + weapons_bed_region = create_region(world, player, active_locations, LocationName.weapons_bed_region, + weapons_bed_region_locations, None) + + cosmic_wall_region_locations = [ + LocationName.cosmic_wall_1, + LocationName.cosmic_wall_2, + LocationName.cosmic_wall_3, + LocationName.cosmic_wall_4, + LocationName.cosmic_wall_5, + LocationName.cosmic_wall_upgrade, + ] + cosmic_wall_region = create_region(world, player, active_locations, LocationName.cosmic_wall_region, + cosmic_wall_region_locations, None) + + dry_lagoon_region_locations = [ + LocationName.dry_lagoon_1, + LocationName.dry_lagoon_2, + LocationName.dry_lagoon_3, + LocationName.dry_lagoon_4, + LocationName.dry_lagoon_5, + LocationName.dry_lagoon_upgrade, + ] + dry_lagoon_region = create_region(world, player, active_locations, LocationName.dry_lagoon_region, + dry_lagoon_region_locations, None) + + egg_quarters_region_locations = [ + LocationName.egg_quarters_1, + LocationName.egg_quarters_2, + LocationName.egg_quarters_3, + LocationName.egg_quarters_4, + LocationName.egg_quarters_5, + LocationName.egg_quarters_upgrade, + ] + egg_quarters_region = create_region(world, player, active_locations, LocationName.egg_quarters_region, + egg_quarters_region_locations, None) + + security_hall_region_locations = [ + LocationName.security_hall_1, + LocationName.security_hall_2, + LocationName.security_hall_3, + LocationName.security_hall_4, + LocationName.security_hall_5, + LocationName.security_hall_upgrade, + ] + security_hall_region = create_region(world, player, active_locations, LocationName.security_hall_region, + security_hall_region_locations, None) + + route_280_region_locations = [ + LocationName.route_280_1, + LocationName.route_280_2, + LocationName.route_280_3, + LocationName.route_280_4, + LocationName.route_280_5, + ] + route_280_region = create_region(world, player, active_locations, LocationName.route_280_region, + route_280_region_locations, None) + + mad_space_region_locations = [ + LocationName.mad_space_1, + LocationName.mad_space_2, + LocationName.mad_space_3, + LocationName.mad_space_4, + LocationName.mad_space_5, + LocationName.mad_space_upgrade, + ] + mad_space_region = create_region(world, player, active_locations, LocationName.mad_space_region, + mad_space_region_locations, None) + + cannon_core_region_locations = [ + LocationName.cannon_core_1, + LocationName.cannon_core_2, + LocationName.cannon_core_3, + LocationName.cannon_core_4, + LocationName.cannon_core_5, + ] + cannon_core_region = create_region(world, player, active_locations, LocationName.cannon_core_region, + cannon_core_region_locations, None) + + chao_garden_region_locations = [ + LocationName.chao_beginner_race, + LocationName.chao_jewel_race, + LocationName.chao_challenge_race, + LocationName.chao_hero_race, + LocationName.chao_dark_race, + LocationName.chao_beginner_karate, + LocationName.chao_standard_karate, + LocationName.chao_expert_karate, + LocationName.chao_super_karate, + ] + chao_garden_region = create_region(world, player, active_locations, LocationName.chao_garden_region, + chao_garden_region_locations, None) + + biolizard_region_locations = [ + LocationName.biolizard, + ] + biolizard_region = create_region(world, player, active_locations, LocationName.biolizard_region, + biolizard_region_locations, None) + + # Set up the regions correctly. + world.regions += [ + menu_region, + gate_0_region, + gate_1_region, + gate_2_region, + gate_3_region, + gate_4_region, + gate_5_region, + city_escape_region, + metal_harbor_region, + green_forest_region, + pyramid_cave_region, + crazy_gadget_region, + final_rush_region, + prison_lane_region, + mission_street_region, + route_101_region, + hidden_base_region, + eternal_engine_region, + wild_canyon_region, + pumpkin_hill_region, + aquatic_mine_region, + death_chamber_region, + meteor_herd_region, + radical_highway_region, + white_jungle_region, + sky_rail_region, + final_chase_region, + iron_gate_region, + sand_ocean_region, + lost_colony_region, + weapons_bed_region, + cosmic_wall_region, + dry_lagoon_region, + egg_quarters_region, + security_hall_region, + route_280_region, + mad_space_region, + cannon_core_region, + chao_garden_region, + biolizard_region, + ] + + +def connect_regions(world, player, gates: typing.List[LevelGate], cannon_core_emblems): + names: typing.Dict[str, int] = {} + + connect(world, player, names, 'Menu', LocationName.gate_0_region) + connect(world, player, names, LocationName.gate_0_region, LocationName.cannon_core_region, + lambda state: (state.has(ItemName.emblem, player, cannon_core_emblems))) + + connect(world, player, names, LocationName.cannon_core_region, LocationName.biolizard_region, + lambda state: (state.can_reach(LocationName.cannon_core_1, "Location", player))) + + for i in range(len(gates[0].gate_levels)): + connect(world, player, names, LocationName.gate_0_region, shuffleable_regions[gates[0].gate_levels[i]]) + + if len(gates) >= 2: + connect(world, player, names, 'Menu', LocationName.gate_1_region, + lambda state: (state.has(ItemName.emblem, player, gates[1].gate_emblem_count))) + for i in range(len(gates[1].gate_levels)): + connect(world, player, names, LocationName.gate_1_region, shuffleable_regions[gates[1].gate_levels[i]]) + + if len(gates) >= 3: + connect(world, player, names, 'Menu', LocationName.gate_2_region, + lambda state: (state.has(ItemName.emblem, player, gates[2].gate_emblem_count))) + for i in range(len(gates[2].gate_levels)): + connect(world, player, names, LocationName.gate_2_region, shuffleable_regions[gates[2].gate_levels[i]]) + + if len(gates) >= 4: + connect(world, player, names, 'Menu', LocationName.gate_3_region, + lambda state: (state.has(ItemName.emblem, player, gates[3].gate_emblem_count))) + for i in range(len(gates[3].gate_levels)): + connect(world, player, names, LocationName.gate_3_region, shuffleable_regions[gates[3].gate_levels[i]]) + + if len(gates) >= 5: + connect(world, player, names, 'Menu', LocationName.gate_4_region, + lambda state: (state.has(ItemName.emblem, player, gates[4].gate_emblem_count))) + for i in range(len(gates[4].gate_levels)): + connect(world, player, names, LocationName.gate_4_region, shuffleable_regions[gates[4].gate_levels[i]]) + + if len(gates) >= 6: + connect(world, player, names, 'Menu', LocationName.gate_5_region, + lambda state: (state.has(ItemName.emblem, player, gates[5].gate_emblem_count))) + for i in range(len(gates[5].gate_levels)): + connect(world, player, names, LocationName.gate_5_region, shuffleable_regions[gates[5].gate_levels[i]]) + + +def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None): + # Shamelessly stolen from the ROR2 definition + ret = Region(name, None, name, player) + ret.world = world + if locations: + for location in locations: + loc_id = active_locations.get(location, 0) + if loc_id: + location = SA2BLocation(player, location, loc_id, ret) + ret.locations.append(location) + if exits: + for exit in exits: + ret.exits.append(Entrance(player, exit, ret)) + + return ret + + +def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, + rule: typing.Optional[typing.Callable] = None): + source_region = world.get_region(source, player) + target_region = world.get_region(target, player) + + if target not in used_names: + used_names[target] = 1 + name = target + else: + used_names[target] += 1 + name = target + (' ' * used_names[target]) + + connection = Entrance(player, name, source_region) + + if rule: + connection.access_rule = rule + + source_region.exits.append(connection) + connection.connect(target_region) diff --git a/worlds/sa2b/Rules.py b/worlds/sa2b/Rules.py new file mode 100644 index 00000000..9720e1b5 --- /dev/null +++ b/worlds/sa2b/Rules.py @@ -0,0 +1,396 @@ +from BaseClasses import MultiWorld +from .Names import LocationName, ItemName +from .Locations import first_mission_location_table, second_mission_location_table, third_mission_location_table, \ + fourth_mission_location_table, fifth_mission_location_table, \ + upgrade_location_table, chao_garden_location_table +from ..AutoWorld import LogicMixin +from ..generic.Rules import add_rule, set_rule + + +def set_mission_progress_rules(world: MultiWorld, player: int): + for (k1, v1), (k2, v2), (k3, v3), (k4, v4), (k5, v5) in \ + zip(sorted(first_mission_location_table.items()), + sorted(second_mission_location_table.items()), + sorted(third_mission_location_table.items()), + sorted(fourth_mission_location_table.items()), + sorted(fifth_mission_location_table.items())): + + if world.IncludeMissions[player].value >= 2: + set_rule(world.get_location(k2, player), lambda state, k1=k1: state.can_reach(k1, "Location", player)) + + if world.IncludeMissions[player].value >= 3: + set_rule(world.get_location(k3, player), lambda state, k2=k2: state.can_reach(k2, "Location", player)) + + if world.IncludeMissions[player].value >= 4: + set_rule(world.get_location(k4, player), lambda state, k3=k3: state.can_reach(k3, "Location", player)) + + if world.IncludeMissions[player].value >= 5: + set_rule(world.get_location(k5, player), lambda state, k4=k4: state.can_reach(k4, "Location", player)) + + +def set_mission_upgrade_rules(world: MultiWorld, player: int): + # Mission 1 Upgrade Requirements + add_rule(world.get_location(LocationName.metal_harbor_1, player), + lambda state: state.has(ItemName.sonic_light_shoes, player)) + add_rule(world.get_location(LocationName.pumpkin_hill_1, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player)) + add_rule(world.get_location(LocationName.mission_street_1, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.aquatic_mine_1, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player)) + add_rule(world.get_location(LocationName.hidden_base_1, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.pyramid_cave_1, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + add_rule(world.get_location(LocationName.death_chamber_1, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.eternal_engine_1, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.meteor_herd_1, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.crazy_gadget_1, player), + lambda state: state.has(ItemName.sonic_light_shoes, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_flame_ring, player)) + add_rule(world.get_location(LocationName.final_rush_1, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + + add_rule(world.get_location(LocationName.egg_quarters_1, player), + lambda state: state.has(ItemName.rouge_pick_nails, player)) + add_rule(world.get_location(LocationName.lost_colony_1, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + add_rule(world.get_location(LocationName.weapons_bed_1, player), + lambda state: state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.security_hall_1, player), + lambda state: state.has(ItemName.rouge_pick_nails, player)) + add_rule(world.get_location(LocationName.white_jungle_1, player), + lambda state: state.has(ItemName.shadow_air_shoes, player)) + add_rule(world.get_location(LocationName.mad_space_1, player), + lambda state: state.has(ItemName.rouge_pick_nails, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.cosmic_wall_1, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + + add_rule(world.get_location(LocationName.cannon_core_1, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_air_necklace, player) and + state.has(ItemName.sonic_bounce_bracelet, player)) + + # Mission 2 Upgrade Requirements + if world.IncludeMissions[player].value >= 2: + add_rule(world.get_location(LocationName.metal_harbor_2, player), + lambda state: state.has(ItemName.sonic_light_shoes, player)) + add_rule(world.get_location(LocationName.mission_street_2, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.hidden_base_2, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.death_chamber_2, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.eternal_engine_2, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.crazy_gadget_2, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + + add_rule(world.get_location(LocationName.lost_colony_2, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + add_rule(world.get_location(LocationName.weapons_bed_2, player), + lambda state: state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.security_hall_2, player), + lambda state: state.has(ItemName.rouge_pick_nails, player)) + add_rule(world.get_location(LocationName.mad_space_2, player), + lambda state: state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.cosmic_wall_2, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + + add_rule(world.get_location(LocationName.cannon_core_2, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.eggman_jet_engine, player)) + + # Mission 3 Upgrade Requirements + if world.IncludeMissions[player].value >= 3: + add_rule(world.get_location(LocationName.city_escape_3, player), + lambda state: state.has(ItemName.sonic_mystic_melody, player)) + add_rule(world.get_location(LocationName.wild_canyon_3, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_mystic_melody, player)) + add_rule(world.get_location(LocationName.prison_lane_3, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_mystic_melody, player)) + add_rule(world.get_location(LocationName.metal_harbor_3, player), + lambda state: state.has(ItemName.sonic_light_shoes, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_mystic_melody, player)) + add_rule(world.get_location(LocationName.green_forest_3, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_mystic_melody, player)) + add_rule(world.get_location(LocationName.pumpkin_hill_3, player), + lambda state: state.has(ItemName.knuckles_mystic_melody, player)) + add_rule(world.get_location(LocationName.mission_street_3, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_mystic_melody, player)) + add_rule(world.get_location(LocationName.aquatic_mine_3, player), + lambda state: state.has(ItemName.knuckles_mystic_melody, player)) + add_rule(world.get_location(LocationName.hidden_base_3, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_mystic_melody, player)) + add_rule(world.get_location(LocationName.pyramid_cave_3, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_mystic_melody, player)) + add_rule(world.get_location(LocationName.death_chamber_3, player), + lambda state: state.has(ItemName.knuckles_mystic_melody, player) and + state.has(ItemName.knuckles_air_necklace, player) and + state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.eternal_engine_3, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_mystic_melody, player)) + add_rule(world.get_location(LocationName.meteor_herd_3, player), + lambda state: state.has(ItemName.knuckles_mystic_melody, player)) + add_rule(world.get_location(LocationName.crazy_gadget_3, player), + lambda state: state.has(ItemName.sonic_light_shoes, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_flame_ring, player) and + state.has(ItemName.sonic_mystic_melody, player)) + add_rule(world.get_location(LocationName.final_rush_3, player), + lambda state: state.has(ItemName.sonic_light_shoes, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_mystic_melody, player)) + + add_rule(world.get_location(LocationName.iron_gate_3, player), + lambda state: state.has(ItemName.eggman_mystic_melody, player) and + state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.dry_lagoon_3, player), + lambda state: state.has(ItemName.rouge_mystic_melody, player) and + state.has(ItemName.rouge_pick_nails, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.sand_ocean_3, player), + lambda state: state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.radical_highway_3, player), + lambda state: state.has(ItemName.shadow_mystic_melody, player)) + add_rule(world.get_location(LocationName.egg_quarters_3, player), + lambda state: state.has(ItemName.rouge_mystic_melody, player) and + state.has(ItemName.rouge_pick_nails, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.lost_colony_3, player), + lambda state: state.has(ItemName.eggman_mystic_melody, player) and + state.has(ItemName.eggman_jet_engine, player)) + add_rule(world.get_location(LocationName.weapons_bed_3, player), + lambda state: state.has(ItemName.eggman_mystic_melody, player) and + state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.security_hall_3, player), + lambda state: state.has(ItemName.rouge_treasure_scope, player)) + add_rule(world.get_location(LocationName.white_jungle_3, player), + lambda state: state.has(ItemName.shadow_air_shoes, player) and + state.has(ItemName.shadow_mystic_melody, player)) + add_rule(world.get_location(LocationName.sky_rail_3, player), + lambda state: state.has(ItemName.shadow_air_shoes, player) and + state.has(ItemName.shadow_mystic_melody, player)) + add_rule(world.get_location(LocationName.mad_space_3, player), + lambda state: state.has(ItemName.rouge_mystic_melody, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.cosmic_wall_3, player), + lambda state: state.has(ItemName.eggman_mystic_melody, player) and + state.has(ItemName.eggman_jet_engine, player)) + add_rule(world.get_location(LocationName.final_chase_3, player), + lambda state: state.has(ItemName.shadow_air_shoes, player) and + state.has(ItemName.shadow_mystic_melody, player)) + + add_rule(world.get_location(LocationName.cannon_core_3, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.eggman_mystic_melody, player) and + state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player) and + state.has(ItemName.rouge_mystic_melody, player) and + state.has(ItemName.knuckles_mystic_melody, player) and + state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_air_necklace, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_light_shoes, player)) + + # Mission 4 Upgrade Requirements + if world.IncludeMissions[player].value >= 4: + add_rule(world.get_location(LocationName.metal_harbor_4, player), + lambda state: state.has(ItemName.sonic_light_shoes, player)) + add_rule(world.get_location(LocationName.pumpkin_hill_4, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player)) + add_rule(world.get_location(LocationName.mission_street_4, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.aquatic_mine_4, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player)) + add_rule(world.get_location(LocationName.hidden_base_4, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.pyramid_cave_4, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + add_rule(world.get_location(LocationName.death_chamber_4, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.eternal_engine_4, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.meteor_herd_4, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.crazy_gadget_4, player), + lambda state: state.has(ItemName.sonic_light_shoes, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_flame_ring, player)) + add_rule(world.get_location(LocationName.final_rush_4, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + + add_rule(world.get_location(LocationName.egg_quarters_4, player), + lambda state: state.has(ItemName.rouge_pick_nails, player)) + add_rule(world.get_location(LocationName.lost_colony_4, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + add_rule(world.get_location(LocationName.weapons_bed_4, player), + lambda state: state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.security_hall_4, player), + lambda state: state.has(ItemName.rouge_pick_nails, player)) + add_rule(world.get_location(LocationName.white_jungle_4, player), + lambda state: state.has(ItemName.shadow_air_shoes, player)) + add_rule(world.get_location(LocationName.mad_space_4, player), + lambda state: state.has(ItemName.rouge_pick_nails, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.cosmic_wall_4, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + + add_rule(world.get_location(LocationName.cannon_core_4, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_air_necklace, player) and + state.has(ItemName.sonic_bounce_bracelet, player)) + + # Mission 5 Upgrade Requirements + if world.IncludeMissions[player].value >= 5: + add_rule(world.get_location(LocationName.city_escape_5, player), + lambda state: state.has(ItemName.sonic_flame_ring, player)) + add_rule(world.get_location(LocationName.wild_canyon_5, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_sunglasses, player)) + add_rule(world.get_location(LocationName.metal_harbor_5, player), + lambda state: state.has(ItemName.sonic_light_shoes, player)) + add_rule(world.get_location(LocationName.pumpkin_hill_5, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_sunglasses, player)) + add_rule(world.get_location(LocationName.mission_street_5, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.aquatic_mine_5, player), + lambda state: state.has(ItemName.knuckles_mystic_melody, player) and + state.has(ItemName.knuckles_air_necklace, player) and + state.has(ItemName.knuckles_sunglasses, player)) + add_rule(world.get_location(LocationName.hidden_base_5, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.pyramid_cave_5, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + add_rule(world.get_location(LocationName.death_chamber_5, player), + lambda state: state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_shovel_claws, player) and + state.has(ItemName.knuckles_mystic_melody, player) and + state.has(ItemName.knuckles_air_necklace, player)) + add_rule(world.get_location(LocationName.eternal_engine_5, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.meteor_herd_5, player), + lambda state: state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_sunglasses, player)) + add_rule(world.get_location(LocationName.crazy_gadget_5, player), + lambda state: state.has(ItemName.sonic_light_shoes, player) and + state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_flame_ring, player)) + add_rule(world.get_location(LocationName.final_rush_5, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + + add_rule(world.get_location(LocationName.iron_gate_5, player), + lambda state: state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.dry_lagoon_5, player), + lambda state: state.has(ItemName.rouge_treasure_scope, player)) + add_rule(world.get_location(LocationName.egg_quarters_5, player), + lambda state: state.has(ItemName.rouge_treasure_scope, player)) + add_rule(world.get_location(LocationName.lost_colony_5, player), + lambda state: state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.weapons_bed_5, player), + lambda state: state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.security_hall_5, player), + lambda state: state.has(ItemName.rouge_pick_nails, player) and + state.has(ItemName.rouge_treasure_scope, player)) + add_rule(world.get_location(LocationName.white_jungle_5, player), + lambda state: state.has(ItemName.shadow_air_shoes, player) and + state.has(ItemName.shadow_flame_ring, player)) + add_rule(world.get_location(LocationName.mad_space_5, player), + lambda state: state.has(ItemName.rouge_pick_nails, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.cosmic_wall_5, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + + add_rule(world.get_location(LocationName.cannon_core_5, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.eggman_jet_engine, player) and + state.has(ItemName.knuckles_hammer_gloves, player) and + state.has(ItemName.knuckles_air_necklace, player) and + state.has(ItemName.sonic_bounce_bracelet, player)) + + add_rule(world.get_location(LocationName.city_escape_upgrade, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player) and + state.has(ItemName.sonic_flame_ring, player)) + add_rule(world.get_location(LocationName.wild_canyon_upgrade, player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player)) + add_rule(world.get_location(LocationName.prison_lane_upgrade, player), + lambda state: state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.hidden_base_upgrade, player), + lambda state: state.has(ItemName.tails_booster, player) and + state.has(ItemName.tails_bazooka, player)) + add_rule(world.get_location(LocationName.eternal_engine_upgrade, player), + lambda state: state.has(ItemName.tails_booster, player)) + add_rule(world.get_location(LocationName.meteor_herd_upgrade, player), + lambda state: state.has(ItemName.knuckles_hammer_gloves, player)) + add_rule(world.get_location(LocationName.crazy_gadget_upgrade, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + add_rule(world.get_location(LocationName.final_rush_upgrade, player), + lambda state: state.has(ItemName.sonic_bounce_bracelet, player)) + + add_rule(world.get_location(LocationName.iron_gate_upgrade, player), + lambda state: state.has(ItemName.eggman_large_cannon, player)) + add_rule(world.get_location(LocationName.dry_lagoon_upgrade, player), + lambda state: state.has(ItemName.rouge_pick_nails, player)) + add_rule(world.get_location(LocationName.sand_ocean_upgrade, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + add_rule(world.get_location(LocationName.radical_highway_upgrade, player), + lambda state: state.has(ItemName.shadow_air_shoes, player)) + add_rule(world.get_location(LocationName.security_hall_upgrade, player), + lambda state: state.has(ItemName.rouge_mystic_melody, player) and + state.has(ItemName.rouge_iron_boots, player)) + add_rule(world.get_location(LocationName.cosmic_wall_upgrade, player), + lambda state: state.has(ItemName.eggman_jet_engine, player)) + + +def set_rules(world: MultiWorld, player: int): + # Mission Progression Rules (Mission 1 begets Mission 2, etc.) + set_mission_progress_rules(world, player) + + # Upgrade Requirements for each location + set_mission_upgrade_rules(world, player) + + # TODO: Place Level Emblem Requirements Here + + # (Edge Case) + # Create some reasonable arbitrary logic for Chao Races to prevent having to grind Chaos Drives in the first level + # for loc in chao_garden_location_table: + # world.get_location(loc, player).add_item_rule(loc, lambda item: False) + + world.completion_condition[player] = lambda state: state.has(ItemName.maria, player) diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py new file mode 100644 index 00000000..70b7f32f --- /dev/null +++ b/worlds/sa2b/__init__.py @@ -0,0 +1,223 @@ +import os +import typing +import math + +from BaseClasses import Item, MultiWorld, Tutorial +from .Items import SA2BItem, ItemData, item_table, upgrades_table +from .Locations import SA2BLocation, all_locations, setup_locations +from .Options import sa2b_options +from .Regions import create_regions, shuffleable_regions, connect_regions, LevelGate, gate_0_whitelist_regions, \ + gate_0_blacklist_regions +from .Rules import set_rules +from .Names import ItemName, LocationName +from ..AutoWorld import WebWorld, World +import Patch + + +class SA2BWeb(WebWorld): + theme = "partyTime" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Sonic Adventure 2: Battle randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["RaspberrySpaceJam", "PoryGone"] + ) + + tutorials = [setup_en] + + +def check_for_impossible_shuffle(shuffled_levels: typing.List[int], gate_0_range: int, world: MultiWorld): + blacklist_level_count = 0 + + for i in range(gate_0_range): + if shuffled_levels[i] in gate_0_blacklist_regions: + blacklist_level_count += 1 + + if blacklist_level_count == gate_0_range: + index_to_swap = world.random.randint(0, gate_0_range) + for i in range(len(shuffled_levels)): + if shuffled_levels[i] in gate_0_whitelist_regions: + shuffled_levels[i], shuffled_levels[index_to_swap] = shuffled_levels[index_to_swap], shuffled_levels[i] + break + + +class SA2BWorld(World): + """ + Sonic Adventure 2 Battle is an action platforming game. Play as Sonic, Tails, Knuckles, Shadow, Rogue, and Eggman across 31 stages and prevent the destruction of the earth. + """ + game: str = "Sonic Adventure 2 Battle" + options = sa2b_options + topology_present = False + data_version = 1 + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = all_locations + + music_map: typing.Dict[int, int] + emblems_for_cannons_core: int + region_emblem_map: typing.Dict[int, int] + web = SA2BWeb() + + def _get_slot_data(self): + return { + "ModVersion": 100, + "MusicMap": self.music_map, + "MusicShuffle": self.world.MusicShuffle[self.player], + "DeathLink": self.world.DeathLink[self.player], + "IncludeMissions": self.world.IncludeMissions[self.player].value, + "EmblemPercentageForCannonsCore": self.world.EmblemPercentageForCannonsCore[self.player].value, + "NumberOfLevelGates": self.world.NumberOfLevelGates[self.player].value, + "LevelGateDistribution": self.world.LevelGateDistribution[self.player], + "EmblemsForCannonsCore": self.emblems_for_cannons_core, + "RegionEmblemMap": self.region_emblem_map, + } + + def _create_items(self, name: str): + data = item_table[name] + return [self.create_item(name)] * 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.world, 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.world.NumberOfLevelGates[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.world.LevelGateDistribution[self.player] == 0 or self.world.LevelGateDistribution[self.player] == 2: + early_distribution = self.world.LevelGateDistribution[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_basic(self): + self.world.get_location(LocationName.biolizard, self.player).place_locked_item(self.create_item(ItemName.maria)) + + itempool: typing.List[SA2BItem] = [] + + # First Missions + total_required_locations = 31 + + # Mission Locations + total_required_locations *= self.world.IncludeMissions[self.player].value + + # Upgrades + total_required_locations += 28 + + # Fill item pool with all required items + for item in {**upgrades_table}: + itempool += self._create_items(item) + + total_emblem_count = total_required_locations - len(itempool) + + # itempool += [self.create_item(ItemName.emblem)] * total_emblem_count + + self.emblems_for_cannons_core = math.floor( + total_emblem_count * (self.world.EmblemPercentageForCannonsCore[self.player].value / 100.0)) + + shuffled_region_list = list(range(30)) + emblem_requirement_list = list() + self.world.random.shuffle(shuffled_region_list) + levels_per_gate = self.get_levels_per_gate() + + check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.world) + levels_added_to_gate = 0 + total_levels_added = 0 + current_gate = 0 + current_gate_emblems = 0 + gates = list() + gates.append(LevelGate(0)) + for i in range(30): + gates[current_gate].gate_levels.append(shuffled_region_list[i]) + emblem_requirement_list.append(current_gate_emblems) + levels_added_to_gate += 1 + total_levels_added += 1 + if levels_added_to_gate >= levels_per_gate[current_gate]: + current_gate += 1 + if current_gate > self.world.NumberOfLevelGates[self.player].value: + current_gate = self.world.NumberOfLevelGates[self.player].value + else: + current_gate_emblems = max( + math.floor(total_emblem_count * math.pow(total_levels_added / 30.0, 2.0)), current_gate) + gates.append(LevelGate(current_gate_emblems)) + levels_added_to_gate = 0 + + self.region_emblem_map = dict(zip(shuffled_region_list, emblem_requirement_list)) + + connect_regions(self.world, self.player, gates, self.emblems_for_cannons_core) + + max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core) + itempool += [self.create_item(ItemName.emblem)] * max_required_emblems + itempool += [self.create_item(ItemName.emblem, True)] * (total_emblem_count - max_required_emblems) + + self.world.itempool += itempool + + if self.world.MusicShuffle[self.player] == "levels": + musiclist_o = list(range(0, 47)) + musiclist_s = musiclist_o.copy() + self.world.random.shuffle(musiclist_s) + self.music_map = dict(zip(musiclist_o, musiclist_s)) + elif self.world.MusicShuffle[self.player] == "full": + musiclist_o = list(range(0, 78)) + musiclist_s = musiclist_o.copy() + self.world.random.shuffle(musiclist_s) + self.music_map = dict(zip(musiclist_o, musiclist_s)) + else: + self.music_map = dict() + + def create_regions(self): + location_table = setup_locations(self.world, self.player) + create_regions(self.world, self.player, location_table) + + def create_item(self, name: str, force_non_progression=False) -> Item: + data = item_table[name] + created_item = SA2BItem(name, data.progression, data.code, self.player) + if name == ItemName.emblem: + created_item.skip_in_prog_balancing = True + if force_non_progression: + created_item.advancement = False + return created_item + + def set_rules(self): + set_rules(self.world, self.player) + + @classmethod + def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool, + restitempool, fill_locations): + if world.get_game_players("Sonic Adventure 2 Battle"): + progitempool.sort( + key=lambda item: 0 if (item.name != 'Emblem') else 1) diff --git a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md new file mode 100644 index 00000000..38b24c23 --- /dev/null +++ b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md @@ -0,0 +1,30 @@ +# Sonic Adventure 2: Battle + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. + +## What does randomization do to this game? + +The randomizer shuffles emblems and upgrade items into the AP item pool. The story mode is disabled, but stage select is available from the start. Levels can be locked behind gates requiring a certain number of emblems to unlock. Cannons Core will be locked behind a percentage of all available emblems, and completing Cannons Core will unlock the Biolizard boss. Progress towards unlocking Cannons Core and the next stage gate will be displayed on the Stage Select screen. + +## What is the goal of Sonic Adventure 2: Battle when randomized? + +The goal is to unlock and complete Cannons Core Mission 1, then complete the Biolizard boss fight. + +## What items and locations get shuffled? + +All 30 story stages leading up to Cannons Core will be shuffled and can be optionally placed behind emblem requirement gates. All emblems from the selected mission range and all 28 character upgrade items get shuffled. + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + +## What does another world's item look like in Sonic Adventure 2: Battle + +Emblems have no visualization in the randomizer and items all retain their original appearance. You won't know if an item belongs to another player until you collect. + +## When the player receives an emblem or item, what happens? + +When the player collects an emblem or item, text will appear on screen to indicate who the item was for and what the item was. When collecting items in a level, the orignal item collection text will display and will likely not be correct. + diff --git a/worlds/sa2b/docs/setup_en.md b/worlds/sa2b/docs/setup_en.md new file mode 100644 index 00000000..a5346e70 --- /dev/null +++ b/worlds/sa2b/docs/setup_en.md @@ -0,0 +1,88 @@ +# Sonic Adventure 2: Battle Randomizer Setup Guide + +## Required Software + +- Sonic Adventure 2: Battle from: [Sonic Adventure 2: Battle Steam Store Page](https://store.steampowered.com/app/213610/Sonic_Adventure_2/) + - Currently the DLC is not required for this mod, but it will be required in a future release. +- Sonic Adventure 2 Mod Loader from: [Sonic Retro Mod Loader Page](http://info.sonicretro.org/SA2_Mod_Loader ) +- Microsoft Visual C++ 2013 from: [Microsoft Visual C++ 2013 Redistributable Page](https://www.microsoft.com/en-us/download/details.aspx?id=40784 ) +- Archipelago Mod for Sonic Adventure 2: Battle + from: [Sonic Adventure 2: Battle Archipelago Randomizer Mod Releases Page](https://github.com/PoryGone/SA2B_Archipelago/releases/) + +Optional: +- Sonic Adventure 2: Battle Archipelago PopTracker pack from: [SA2B_AP_Tracker Releases Page](https://github.com/PoryGone/SA2B_AP_Tracker/releases/) + +## Installation Procedures + +1. Install Sonic Adventure 2: Battle from Steam. + +2. Launch the game at least once without mods. + +3. Install Sonic Adventure 2 Mod Loader as per its instructions. + +4. The folder you installed the Sonic Adventure 2 Mod Loader into will now have a `/mods` directory. + +5. Unpack the Archipelago Mod into this folder, so that `/mods/SA2B_Archipelago` is a valid path. + +6. In the SA2B_Archipelago folder, run the `CopyAPCppDLL.bat` script (a window will very quickly pop up and go away). + +7. Launch the `SA2ModManager.exe` and make sure the SA2B_Archipelago mod is listed and enabled. + +## Joining a MultiWorld Game + +1. Before launching the game, run the `SA2ModManager.exe`, select the SA2B_Archipelago mod, and hit the `Configure...` button. + +2. For the `Server IP` field under `AP Settings`, enter the address of the server, such as archipelago.gg:38281, your server host should be able to tell you this. + +3. For the `PlayerName` field under `AP Settings`, enter your "name" field from the yaml, or website config. + +4. For the `Password` field under `AP Settings`, enter the server password if one exists, otherwise leave blank. + +5. Click The `Save` button then hit `Save & Play` to launch the game. + +6. Create a new save to connect to the MultiWorld game. A "Connected to Archipelago" message will appear if you sucessfully connect. If you close the game during play, you can reconnect to the MultiWorld game by selecting the same save file slot. + +## Additional Options + +Some additional settings related to the Archipelago messages in game can be adjusted in the SA2ModManager if you select `Configure...` on the SA2B_Archipelago mod. This settings will be under a `General Settings` tab. + + - Message Display Count: This is the maximum number of Archipelago messages that can be displayed on screen at any given time. + + - Message Display Duration: This dictates how long Archipelago messages are displayed on screen (in seconds). + + - Message Font Size: The is the size of the font used to display the messages from Archipelago. + +## Troubleshooting + +- "The following mods didn't load correctly: SA2B_Archipelago: DLL error - The specified module could not be found." + - Make sure the `APCpp.dll` is in the same folder as the `sonic2app.exe`. (See Installation Procedures step 6) + +- Game is running too fast (Like Sonic). + - If using an NVidia graphics card: + 1. Open the NVIDIA Control Panel. + 2. Select `Manage 3D Settings` under `3D settings` on the left. + 3. Select the `Program Settings` tab in the main window. + 4. Click the `Add` button and select `sonic2app.exe` (or browse to the exe location), then click `Add Selected Program`. + 5. Under `Specify the settings for this program:`, find the `Max Frame Rate` feature and click the Setting column for that feature. + 6. Choose the `On` radial option and in the input box next to the slide enter a value of 60 (or 59 if 60 causes the game to crash). + +- Controller input is not working. + 1. Run the Launcher.exe which should be in the same folder as the SA2ModManager. + 2. Select the `Player` tab and reselect the controller for the player 1 input method. + 3. Click the `Save settings and launch SONIC ADVENTURE 2` button. (Any mod manager settings will apply even if the game is launched this way rather than through the mod manager) + +- Game crashes after display logos. + - This may be caused by a high monitor refresh rate. + - Change the monitor refresh rate to 60 Hz [Change display refresh rate on Windows] (https://support.microsoft.com/en-us/windows/change-your-display-refresh-rate-in-windows-c8ea729e-0678-015c-c415-f806f04aae5a) + - This may also be fixed by setting Windows 7 compatibility mode on the sonic app: + 1. Right click on the sonic2app.exe and select `Properties`. + 2. Select the `Compatibility` tab. + 3. Check the `Run this program in compatility mode for:` box and select Windows 7 in the drop down. + 4. Click the `Apply` button. + +- No resolution options in the Launcher.exe. + - In the `Graphics device` dropdown, select the device and display you plan to run the game on. The `Resolution` dropdown should populate once a graphics device is selected. + +## 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.