diff --git a/worlds/sa2b/GateBosses.py b/worlds/sa2b/GateBosses.py new file mode 100644 index 00000000..afbca993 --- /dev/null +++ b/worlds/sa2b/GateBosses.py @@ -0,0 +1,69 @@ +import typing + +speed_characters_1 = "Sonic vs Shadow 1" +speed_characters_2 = "Sonic vs Shadow 2" +mech_characters_1 = "Tails vs Eggman 1" +mech_characters_2 = "Tails vs Eggman 2" +hunt_characters_1 = "Knuckles vs Rouge 1" +big_foot = "F-6t BIG FOOT" +hot_shot = "B-3x HOT SHOT" +flying_dog = "R-1/A FLYING DOG" +egg_golem_sonic = "Egg Golem (Sonic)" +egg_golem_eggman = "Egg Golem (Eggman)" +king_boom_boo = "King Boom Boo" + +gate_bosses_no_requirements_table = { + speed_characters_1: 0, + speed_characters_2: 1, + mech_characters_1: 2, + mech_characters_2: 3, + hunt_characters_1: 4, + big_foot: 5, + hot_shot: 6, + flying_dog: 7, + egg_golem_sonic: 8, + egg_golem_eggman: 9, +} + +gate_bosses_with_requirements_table = { + king_boom_boo: 10, +} + +all_gate_bosses_table = { + **gate_bosses_no_requirements_table, + **gate_bosses_with_requirements_table, +} + + +def get_boss_name(boss: int): + for key, value in gate_bosses_no_requirements_table.items(): + if value == boss: + return key + for key, value in gate_bosses_with_requirements_table.items(): + if value == boss: + return key + + +def boss_has_requirement(boss: int): + return boss >= len(gate_bosses_no_requirements_table) + + +def get_gate_bosses(world, player: int): + 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) + 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: + available_bosses.extend(gate_bosses_with_requirements_table) + world.random.shuffle(available_bosses) + halfway = True + selected_bosses.append(all_gate_bosses_table[available_bosses[0]]) + boss_gates.append(x + 1) + available_bosses.remove(available_bosses[0]) + + bosses: typing.Dict[int, int] = dict(zip(boss_gates, selected_bosses)) + + return bosses diff --git a/worlds/sa2b/Items.py b/worlds/sa2b/Items.py index 0483c051..bebfa44c 100644 --- a/worlds/sa2b/Items.py +++ b/worlds/sa2b/Items.py @@ -7,6 +7,7 @@ from .Names import ItemName class ItemData(typing.NamedTuple): code: typing.Optional[int] progression: bool + trap: bool = False quantity: int = 1 event: bool = False @@ -62,6 +63,23 @@ upgrades_table = { ItemName.rouge_iron_boots: ItemData(0xFF001C, True), } +junk_table = { + ItemName.five_rings: ItemData(0xFF0020, False), + ItemName.ten_rings: ItemData(0xFF0021, False), + ItemName.twenty_rings: ItemData(0xFF0022, False), + ItemName.extra_life: ItemData(0xFF0023, False), + ItemName.shield: ItemData(0xFF0024, False), + ItemName.magnetic_shield: ItemData(0xFF0025, False), + ItemName.invincibility: ItemData(0xFF0026, False), +} + +trap_table = { + ItemName.omochao_trap: ItemData(0xFF0030, False, True), + ItemName.timestop_trap: ItemData(0xFF0031, False, True), + ItemName.confuse_trap: ItemData(0xFF0032, False, True), + ItemName.tiny_trap: ItemData(0xFF0033, False, True), +} + event_table = { ItemName.maria: ItemData(0xFF001D, True), } @@ -70,6 +88,8 @@ event_table = { item_table = { **emblems_table, **upgrades_table, + **junk_table, + **trap_table, **event_table, } diff --git a/worlds/sa2b/Locations.py b/worlds/sa2b/Locations.py index 14967837..f5f4c79f 100644 --- a/worlds/sa2b/Locations.py +++ b/worlds/sa2b/Locations.py @@ -220,17 +220,92 @@ upgrade_location_table = { 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, +boss_gate_location_table = { + LocationName.gate_1_boss: 0xFF0100, + LocationName.gate_2_boss: 0xFF0101, + LocationName.gate_3_boss: 0xFF0102, + LocationName.gate_4_boss: 0xFF0103, + LocationName.gate_5_boss: 0xFF0104, +} - LocationName.chao_beginner_karate: 0xFF00C5, - LocationName.chao_standard_karate: 0xFF00C6, - LocationName.chao_expert_karate: 0xFF00C7, - LocationName.chao_super_karate: 0xFF00C8, +chao_garden_beginner_location_table = { + LocationName.chao_race_crab_pool_1: 0xFF0200, + LocationName.chao_race_crab_pool_2: 0xFF0201, + LocationName.chao_race_crab_pool_3: 0xFF0202, + LocationName.chao_race_stump_valley_1: 0xFF0203, + LocationName.chao_race_stump_valley_2: 0xFF0204, + LocationName.chao_race_stump_valley_3: 0xFF0205, + LocationName.chao_race_mushroom_forest_1: 0xFF0206, + LocationName.chao_race_mushroom_forest_2: 0xFF0207, + LocationName.chao_race_mushroom_forest_3: 0xFF0208, + 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 = { + LocationName.chao_race_aquamarine_1: 0xFF020C, + LocationName.chao_race_aquamarine_2: 0xFF020D, + LocationName.chao_race_aquamarine_3: 0xFF020E, + LocationName.chao_race_aquamarine_4: 0xFF020F, + LocationName.chao_race_aquamarine_5: 0xFF0210, + LocationName.chao_race_topaz_1: 0xFF0211, + LocationName.chao_race_topaz_2: 0xFF0212, + LocationName.chao_race_topaz_3: 0xFF0213, + LocationName.chao_race_topaz_4: 0xFF0214, + LocationName.chao_race_topaz_5: 0xFF0215, + LocationName.chao_race_peridot_1: 0xFF0216, + LocationName.chao_race_peridot_2: 0xFF0217, + LocationName.chao_race_peridot_3: 0xFF0218, + LocationName.chao_race_peridot_4: 0xFF0219, + LocationName.chao_race_peridot_5: 0xFF021A, + LocationName.chao_race_garnet_1: 0xFF021B, + LocationName.chao_race_garnet_2: 0xFF021C, + LocationName.chao_race_garnet_3: 0xFF021D, + LocationName.chao_race_garnet_4: 0xFF021E, + LocationName.chao_race_garnet_5: 0xFF021F, + LocationName.chao_race_onyx_1: 0xFF0220, + LocationName.chao_race_onyx_2: 0xFF0221, + LocationName.chao_race_onyx_3: 0xFF0222, + LocationName.chao_race_onyx_4: 0xFF0223, + LocationName.chao_race_onyx_5: 0xFF0224, + LocationName.chao_race_diamond_1: 0xFF0225, + LocationName.chao_race_diamond_2: 0xFF0226, + LocationName.chao_race_diamond_3: 0xFF0227, + LocationName.chao_race_diamond_4: 0xFF0228, + LocationName.chao_race_diamond_5: 0xFF0229, + + LocationName.chao_standard_karate: 0xFF0301, +} + +chao_garden_expert_location_table = { + LocationName.chao_race_challenge_1: 0xFF022A, + LocationName.chao_race_challenge_2: 0xFF022B, + LocationName.chao_race_challenge_3: 0xFF022C, + LocationName.chao_race_challenge_4: 0xFF022D, + LocationName.chao_race_challenge_5: 0xFF022E, + LocationName.chao_race_challenge_6: 0xFF022F, + LocationName.chao_race_challenge_7: 0xFF0230, + LocationName.chao_race_challenge_8: 0xFF0231, + LocationName.chao_race_challenge_9: 0xFF0232, + LocationName.chao_race_challenge_10: 0xFF0233, + LocationName.chao_race_challenge_11: 0xFF0234, + LocationName.chao_race_challenge_12: 0xFF0235, + + LocationName.chao_race_hero_1: 0xFF0236, + LocationName.chao_race_hero_2: 0xFF0237, + LocationName.chao_race_hero_3: 0xFF0238, + LocationName.chao_race_hero_4: 0xFF0239, + + LocationName.chao_race_dark_1: 0xFF023A, + LocationName.chao_race_dark_2: 0xFF023B, + LocationName.chao_race_dark_3: 0xFF023C, + LocationName.chao_race_dark_4: 0xFF023D, + + LocationName.chao_expert_karate: 0xFF0302, + LocationName.chao_super_karate: 0xFF0303, } other_location_table = { @@ -245,15 +320,57 @@ all_locations = { **fourth_mission_location_table, **fifth_mission_location_table, **upgrade_location_table, - **chao_garden_location_table, + **boss_gate_location_table, + **chao_garden_beginner_location_table, + **chao_garden_intermediate_location_table, + **chao_garden_expert_location_table, **other_location_table, } -location_table = {} +boss_gate_set = [ + LocationName.gate_1_boss, + LocationName.gate_2_boss, + LocationName.gate_3_boss, + LocationName.gate_4_boss, + 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, + LocationName.chao_race_mushroom_forest_3, + LocationName.chao_race_block_canyon_3, + + LocationName.chao_race_aquamarine_5, + LocationName.chao_race_topaz_5, + LocationName.chao_race_peridot_5, + LocationName.chao_race_garnet_5, + LocationName.chao_race_onyx_5, + LocationName.chao_race_diamond_5, + + LocationName.chao_race_challenge_4, + LocationName.chao_race_challenge_8, + LocationName.chao_race_challenge_12, + + LocationName.chao_race_hero_2, + LocationName.chao_race_hero_4, + + LocationName.chao_race_dark_2, + LocationName.chao_race_dark_4, +] def setup_locations(world, player: int): - location_table = {**first_mission_location_table} + location_table = {} + chao_location_table = {} + location_table.update({**first_mission_location_table}) if world.include_missions[player].value >= 2: location_table.update({**second_mission_location_table}) @@ -268,9 +385,30 @@ def setup_locations(world, player: int): 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}) + 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}) + + 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": + location_table[key] = value + else: + location_table[key] = value + + for x in range(len(boss_gate_set)): + if x < world.number_of_level_gates[player].value: + location_table[boss_gate_set[x]] = boss_gate_location_table[boss_gate_set[x]] + return location_table diff --git a/worlds/sa2b/Names/ItemName.py b/worlds/sa2b/Names/ItemName.py index db6cc304..32eaf5ef 100644 --- a/worlds/sa2b/Names/ItemName.py +++ b/worlds/sa2b/Names/ItemName.py @@ -36,4 +36,17 @@ rouge_pick_nails = "Rouge - Pick Nails" rouge_treasure_scope = "Rouge - Treasure Scope" rouge_iron_boots = "Rouge - Iron Boots" -maria = "What Maria Wanted" +five_rings = "Five Rings" +ten_rings = "Ten Rings" +twenty_rings = "Twenty Rings" +extra_life = "Extra Life" +shield = "Shield" +magnetic_shield = "Magnetic Shield" +invincibility = "Invincibility" + +omochao_trap = "OmoTrap" +timestop_trap = "Chaos Control Trap" +confuse_trap = "Confusion Trap" +tiny_trap = "Tiny Trap" + +maria = "What Maria Wanted" diff --git a/worlds/sa2b/Names/LocationName.py b/worlds/sa2b/Names/LocationName.py index cf9721d3..8d55d5e4 100644 --- a/worlds/sa2b/Names/LocationName.py +++ b/worlds/sa2b/Names/LocationName.py @@ -196,16 +196,85 @@ cannon_core_3 = "Cannon Core - 3" cannon_core_4 = "Cannon Core - 4" cannon_core_5 = "Cannon Core - 5" +# Boss Definitions +gate_1_boss = "Gate 1 Boss" +gate_2_boss = "Gate 2 Boss" +gate_3_boss = "Gate 3 Boss" +gate_4_boss = "Gate 4 Boss" +gate_5_boss = "Gate 5 Boss" + # 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" +chao_race_crab_pool_1 = "Chao Race - Beginner - Crab Pool 1" +chao_race_crab_pool_2 = "Chao Race - Beginner - Crab Pool 2" +chao_race_crab_pool_3 = "Chao Race - Beginner - Crab Pool 3" +chao_race_stump_valley_1 = "Chao Race - Beginner - Stump Valley 1" +chao_race_stump_valley_2 = "Chao Race - Beginner - Stump Valley 2" +chao_race_stump_valley_3 = "Chao Race - Beginner - Stump Valley 3" +chao_race_mushroom_forest_1 = "Chao Race - Beginner - Mushroom Forest 1" +chao_race_mushroom_forest_2 = "Chao Race - Beginner - Mushroom Forest 2" +chao_race_mushroom_forest_3 = "Chao Race - Beginner - Mushroom Forest 3" +chao_race_block_canyon_1 = "Chao Race - Beginner - Block Canyon 1" +chao_race_block_canyon_2 = "Chao Race - Beginner - Block Canyon 2" +chao_race_block_canyon_3 = "Chao Race - Beginner - Block Canyon 3" + +chao_race_aquamarine_1 = "Chao Race - Jewel - Aquamarine 1" +chao_race_aquamarine_2 = "Chao Race - Jewel - Aquamarine 2" +chao_race_aquamarine_3 = "Chao Race - Jewel - Aquamarine 3" +chao_race_aquamarine_4 = "Chao Race - Jewel - Aquamarine 4" +chao_race_aquamarine_5 = "Chao Race - Jewel - Aquamarine 5" +chao_race_topaz_1 = "Chao Race - Jewel - Topaz 1" +chao_race_topaz_2 = "Chao Race - Jewel - Topaz 2" +chao_race_topaz_3 = "Chao Race - Jewel - Topaz 3" +chao_race_topaz_4 = "Chao Race - Jewel - Topaz 4" +chao_race_topaz_5 = "Chao Race - Jewel - Topaz 5" +chao_race_peridot_1 = "Chao Race - Jewel - Peridot 1" +chao_race_peridot_2 = "Chao Race - Jewel - Peridot 2" +chao_race_peridot_3 = "Chao Race - Jewel - Peridot 3" +chao_race_peridot_4 = "Chao Race - Jewel - Peridot 4" +chao_race_peridot_5 = "Chao Race - Jewel - Peridot 5" +chao_race_garnet_1 = "Chao Race - Jewel - Garnet 1" +chao_race_garnet_2 = "Chao Race - Jewel - Garnet 2" +chao_race_garnet_3 = "Chao Race - Jewel - Garnet 3" +chao_race_garnet_4 = "Chao Race - Jewel - Garnet 4" +chao_race_garnet_5 = "Chao Race - Jewel - Garnet 5" +chao_race_onyx_1 = "Chao Race - Jewel - Onyx 1" +chao_race_onyx_2 = "Chao Race - Jewel - Onyx 2" +chao_race_onyx_3 = "Chao Race - Jewel - Onyx 3" +chao_race_onyx_4 = "Chao Race - Jewel - Onyx 4" +chao_race_onyx_5 = "Chao Race - Jewel - Onyx 5" +chao_race_diamond_1 = "Chao Race - Jewel - Diamond 1" +chao_race_diamond_2 = "Chao Race - Jewel - Diamond 2" +chao_race_diamond_3 = "Chao Race - Jewel - Diamond 3" +chao_race_diamond_4 = "Chao Race - Jewel - Diamond 4" +chao_race_diamond_5 = "Chao Race - Jewel - Diamond 5" + +chao_race_challenge_1 = "Chao Race - Challenge 1" +chao_race_challenge_2 = "Chao Race - Challenge 2" +chao_race_challenge_3 = "Chao Race - Challenge 3" +chao_race_challenge_4 = "Chao Race - Challenge 4" +chao_race_challenge_5 = "Chao Race - Challenge 5" +chao_race_challenge_6 = "Chao Race - Challenge 6" +chao_race_challenge_7 = "Chao Race - Challenge 7" +chao_race_challenge_8 = "Chao Race - Challenge 8" +chao_race_challenge_9 = "Chao Race - Challenge 9" +chao_race_challenge_10 = "Chao Race - Challenge 10" +chao_race_challenge_11 = "Chao Race - Challenge 11" +chao_race_challenge_12 = "Chao Race - Challenge 12" + +chao_race_hero_1 = "Chao Race - Hero 1" +chao_race_hero_2 = "Chao Race - Hero 2" +chao_race_hero_3 = "Chao Race - Hero 3" +chao_race_hero_4 = "Chao Race - Hero 4" + +chao_race_dark_1 = "Chao Race - Dark 1" +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" # Other Definitions green_hill = "Green Hill" @@ -221,6 +290,12 @@ gate_3_region = "Gate 3" gate_4_region = "Gate 4" gate_5_region = "Gate 5" +gate_1_boss_region = "Gate 1 Boss" +gate_2_boss_region = "Gate 2 Boss" +gate_3_boss_region = "Gate 3 Boss" +gate_4_boss_region = "Gate 4 Boss" +gate_5_boss_region = "Gate 5 Boss" + city_escape_region = "City Escape" metal_harbor_region = "Metal Harbor" green_forest_region = "Green Forest" @@ -261,4 +336,6 @@ cannon_core_region = "Cannon Core" biolizard_region = "Biolizard" -chao_garden_region = "Chao Garden" +chao_garden_beginner_region = "Chao Garden - Beginner" +chao_garden_intermediate_region = "Chao Garden - Intermediate" +chao_garden_expert_region = "Chao Garden - Expert" diff --git a/worlds/sa2b/Options.py b/worlds/sa2b/Options.py index b6b9f170..7d44776c 100644 --- a/worlds/sa2b/Options.py +++ b/worlds/sa2b/Options.py @@ -3,6 +3,65 @@ import typing from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList +class BaseTrapWeight(Choice): + """ + Base Class for Trap Weights + """ + option_none = 0 + option_low = 1 + option_medium = 2 + option_high = 4 + default = 2 + + +class OmochaoTrapWeight(BaseTrapWeight): + """ + Likelihood of a 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 + """ + 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 + """ + display_name = "Confusion Trap Weight" + + +class TinyTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the player to become tiny + """ + display_name = "Tiny Trap Weight" + + +class JunkFillPercentage(Range): + """ + Replace a percentage of non-required emblems in the item pool with random junk items + """ + display_name = "Junk Fill Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class TrapFillPercentage(Range): + """ + Replace a percentage of junk items in the item pool with random traps + """ + display_name = "Trap Fill Percentage" + range_start = 0 + range_end = 100 + default = 0 + + class IncludeMissions(Range): """ Allows logic to place items in a range of Missions for each level @@ -31,7 +90,7 @@ class EmblemPercentageForCannonsCore(Range): class NumberOfLevelGates(Range): """ - Allows logic to gate some levels behind emblem requirements + The number emblem-locked gates which lock sets of levels """ display_name = "Number of Level Gates" range_start = 0 @@ -53,6 +112,65 @@ class LevelGateDistribution(Choice): default = 1 +class LevelGateCosts(Choice): + """ + Determines how many emblems are required to unlock level gates + """ + display_name = "Level Gate Costs" + option_low = 0 + option_medium = 1 + option_high = 2 + default = 2 + + +class RequiredRank(Choice): + """ + Determines what minimum Rank is required to send a check for a mission + """ + display_name = "Required Rank" + option_e = 0 + option_d = 1 + option_c = 2 + option_b = 3 + option_a = 4 + default = 0 + + +class ChaoGardenDifficulty(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 + Beginner: Beginner Races + Intermediate: Beginner and Jewel Races + Expert: Beginner, Jewel, Challenge, Hero, and Dark Races + """ + display_name = "Chao Garden Difficulty" + option_none = 0 + option_beginner = 1 + option_intermediate = 2 + option_expert = 3 + default = 0 + + +class IncludeChaoKarate(Toggle): + """ + Determines whether the Chao Karate should be included as checks (Note: This setting requires purchase of the "Battle" DLC) + """ + display_name = "Include Chao Karate" + + +class ChaoRaceChecks(Choice): + """ + Determines which Chao Races grant checks + All: Each individual race 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) + """ + display_name = "Chao Race Checks" + option_all = 0 + option_prize = 1 + default = 0 + + class MusicShuffle(Choice): """ What type of Music Shuffle is used @@ -68,10 +186,21 @@ class MusicShuffle(Choice): sa2b_options: typing.Dict[str, type(Option)] = { - "death_link": DeathLink, - "music_shuffle": MusicShuffle, "include_missions": IncludeMissions, + "required_rank": RequiredRank, "emblem_percentage_for_cannons_core": EmblemPercentageForCannonsCore, "number_of_level_gates": NumberOfLevelGates, "level_gate_distribution": LevelGateDistribution, + "level_gate_costs": LevelGateCosts, + "chao_garden_difficulty": ChaoGardenDifficulty, + "include_chao_karate": IncludeChaoKarate, + "chao_race_checks": ChaoRaceChecks, + "junk_fill_percentage": JunkFillPercentage, + "trap_fill_percentage": TrapFillPercentage, + "omochao_trap_weight": OmochaoTrapWeight, + "timestop_trap_weight": TimestopTrapWeight, + "confusion_trap_weight": ConfusionTrapWeight, + "tiny_trap_weight": TinyTrapWeight, + "music_shuffle": MusicShuffle, + "death_link": DeathLink, } diff --git a/worlds/sa2b/Regions.py b/worlds/sa2b/Regions.py index aa13fd8a..b39b13c1 100644 --- a/worlds/sa2b/Regions.py +++ b/worlds/sa2b/Regions.py @@ -2,8 +2,9 @@ import typing from BaseClasses import MultiWorld, Region, Entrance from .Items import SA2BItem -from .Locations import SA2BLocation +from .Locations import SA2BLocation, boss_gate_location_table, boss_gate_set from .Names import LocationName, ItemName +from .GateBosses import get_boss_name, all_gate_bosses_table, king_boom_boo class LevelGate: @@ -93,6 +94,11 @@ def create_regions(world, player: int, active_locations): 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) + gate_1_boss_region = create_region(world, player, active_locations, 'Gate 1 Boss', [LocationName.gate_1_boss], None) + gate_2_boss_region = create_region(world, player, active_locations, 'Gate 2 Boss', [LocationName.gate_2_boss], None) + gate_3_boss_region = create_region(world, player, active_locations, 'Gate 3 Boss', [LocationName.gate_3_boss], None) + gate_4_boss_region = create_region(world, player, active_locations, 'Gate 4 Boss', [LocationName.gate_4_boss], None) + gate_5_boss_region = create_region(world, player, active_locations, 'Gate 5 Boss', [LocationName.gate_5_boss], None) city_escape_region_locations = [ LocationName.city_escape_1, @@ -432,19 +438,91 @@ def create_regions(world, player: int, active_locations): 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, + chao_garden_beginner_region_locations = [ + LocationName.chao_race_crab_pool_1, + LocationName.chao_race_crab_pool_2, + LocationName.chao_race_crab_pool_3, + LocationName.chao_race_stump_valley_1, + LocationName.chao_race_stump_valley_2, + LocationName.chao_race_stump_valley_3, + LocationName.chao_race_mushroom_forest_1, + LocationName.chao_race_mushroom_forest_2, + LocationName.chao_race_mushroom_forest_3, + LocationName.chao_race_block_canyon_1, + LocationName.chao_race_block_canyon_2, + LocationName.chao_race_block_canyon_3, + LocationName.chao_beginner_karate, + ] + chao_garden_beginner_region = create_region(world, player, active_locations, LocationName.chao_garden_beginner_region, + chao_garden_beginner_region_locations, None) + + chao_garden_intermediate_region_locations = [ + LocationName.chao_race_aquamarine_1, + LocationName.chao_race_aquamarine_2, + LocationName.chao_race_aquamarine_3, + LocationName.chao_race_aquamarine_4, + LocationName.chao_race_aquamarine_5, + LocationName.chao_race_topaz_1, + LocationName.chao_race_topaz_2, + LocationName.chao_race_topaz_3, + LocationName.chao_race_topaz_4, + LocationName.chao_race_topaz_5, + LocationName.chao_race_peridot_1, + LocationName.chao_race_peridot_2, + LocationName.chao_race_peridot_3, + LocationName.chao_race_peridot_4, + LocationName.chao_race_peridot_5, + LocationName.chao_race_garnet_1, + LocationName.chao_race_garnet_2, + LocationName.chao_race_garnet_3, + LocationName.chao_race_garnet_4, + LocationName.chao_race_garnet_5, + LocationName.chao_race_onyx_1, + LocationName.chao_race_onyx_2, + LocationName.chao_race_onyx_3, + LocationName.chao_race_onyx_4, + LocationName.chao_race_onyx_5, + LocationName.chao_race_diamond_1, + LocationName.chao_race_diamond_2, + LocationName.chao_race_diamond_3, + LocationName.chao_race_diamond_4, + LocationName.chao_race_diamond_5, + LocationName.chao_standard_karate, + ] + chao_garden_intermediate_region = create_region(world, player, active_locations, LocationName.chao_garden_intermediate_region, + chao_garden_intermediate_region_locations, None) + + chao_garden_expert_region_locations = [ + LocationName.chao_race_challenge_1, + LocationName.chao_race_challenge_2, + LocationName.chao_race_challenge_3, + LocationName.chao_race_challenge_4, + LocationName.chao_race_challenge_5, + LocationName.chao_race_challenge_6, + LocationName.chao_race_challenge_7, + LocationName.chao_race_challenge_8, + LocationName.chao_race_challenge_9, + LocationName.chao_race_challenge_10, + LocationName.chao_race_challenge_11, + LocationName.chao_race_challenge_12, + + LocationName.chao_race_hero_1, + LocationName.chao_race_hero_2, + LocationName.chao_race_hero_3, + LocationName.chao_race_hero_4, + + LocationName.chao_race_dark_1, + LocationName.chao_race_dark_2, + LocationName.chao_race_dark_3, + LocationName.chao_race_dark_4, + 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) + chao_garden_expert_region = create_region(world, player, active_locations, LocationName.chao_garden_expert_region, + chao_garden_expert_region_locations, None) biolizard_region_locations = [ LocationName.biolizard, @@ -461,6 +539,11 @@ def create_regions(world, player: int, active_locations): gate_3_region, gate_4_region, gate_5_region, + gate_1_boss_region, + gate_2_boss_region, + gate_3_boss_region, + gate_4_boss_region, + gate_5_boss_region, city_escape_region, metal_harbor_region, green_forest_region, @@ -492,12 +575,14 @@ def create_regions(world, player: int, active_locations): route_280_region, mad_space_region, cannon_core_region, - chao_garden_region, + chao_garden_beginner_region, + chao_garden_intermediate_region, + chao_garden_expert_region, biolizard_region, ] -def connect_regions(world, player, gates: typing.List[LevelGate], cannon_core_emblems): +def connect_regions(world, player, gates: typing.List[LevelGate], cannon_core_emblems, gate_bosses): names: typing.Dict[str, int] = {} connect(world, player, names, 'Menu', LocationName.gate_0_region) @@ -510,36 +595,97 @@ def connect_regions(world, player, gates: typing.List[LevelGate], cannon_core_em 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, + gates_len = len(gates) + if gates_len >= 2: + connect(world, player, names, 'Menu', LocationName.gate_1_boss_region, lambda state: (state.has(ItemName.emblem, player, gates[1].gate_emblem_count))) + + if gate_bosses[1] == all_gate_bosses_table[king_boom_boo]: + connect(world, player, names, LocationName.gate_1_boss_region, LocationName.gate_1_region, + lambda state: (state.has(ItemName.knuckles_shovel_claws, player))) + else: + connect(world, player, names, LocationName.gate_1_boss_region, LocationName.gate_1_region) + 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, + if gates_len >= 3: + connect(world, player, names, 'Menu', LocationName.gate_2_boss_region, lambda state: (state.has(ItemName.emblem, player, gates[2].gate_emblem_count))) + + if gate_bosses[2] == all_gate_bosses_table[king_boom_boo]: + connect(world, player, names, LocationName.gate_2_boss_region, LocationName.gate_2_region, + lambda state: (state.has(ItemName.knuckles_shovel_claws, player))) + else: + connect(world, player, names, LocationName.gate_2_boss_region, LocationName.gate_2_region) + 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, + if gates_len >= 4: + connect(world, player, names, 'Menu', LocationName.gate_3_boss_region, lambda state: (state.has(ItemName.emblem, player, gates[3].gate_emblem_count))) + + if gate_bosses[3] == all_gate_bosses_table[king_boom_boo]: + connect(world, player, names, LocationName.gate_3_boss_region, LocationName.gate_3_region, + lambda state: (state.has(ItemName.knuckles_shovel_claws, player))) + else: + connect(world, player, names, LocationName.gate_3_boss_region, LocationName.gate_3_region) + 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, + if gates_len >= 5: + connect(world, player, names, 'Menu', LocationName.gate_4_boss_region, lambda state: (state.has(ItemName.emblem, player, gates[4].gate_emblem_count))) + + if gate_bosses[4] == all_gate_bosses_table[king_boom_boo]: + connect(world, player, names, LocationName.gate_4_boss_region, LocationName.gate_4_region, + lambda state: (state.has(ItemName.knuckles_shovel_claws, player))) + else: + connect(world, player, names, LocationName.gate_4_boss_region, LocationName.gate_4_region) + 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, + if gates_len >= 6: + connect(world, player, names, 'Menu', LocationName.gate_5_boss_region, lambda state: (state.has(ItemName.emblem, player, gates[5].gate_emblem_count))) + + if gate_bosses[5] == all_gate_bosses_table[king_boom_boo]: + connect(world, player, names, LocationName.gate_5_boss_region, LocationName.gate_5_region, + lambda state: (state.has(ItemName.knuckles_shovel_claws, player))) + else: + connect(world, player, names, LocationName.gate_5_boss_region, LocationName.gate_5_region) + for i in range(len(gates[5].gate_levels)): connect(world, player, names, LocationName.gate_5_region, shuffleable_regions[gates[5].gate_levels[i]]) + if gates_len == 1: + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_beginner_region) + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_intermediate_region) + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_expert_region) + elif gates_len == 2: + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_beginner_region) + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_intermediate_region) + connect(world, player, names, LocationName.gate_1_region, LocationName.chao_garden_expert_region) + elif gates_len == 3: + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_beginner_region) + connect(world, player, names, LocationName.gate_1_region, LocationName.chao_garden_intermediate_region) + connect(world, player, names, LocationName.gate_2_region, LocationName.chao_garden_expert_region) + elif gates_len == 4: + connect(world, player, names, LocationName.gate_0_region, LocationName.chao_garden_beginner_region) + connect(world, player, names, LocationName.gate_1_region, LocationName.chao_garden_intermediate_region) + connect(world, player, names, LocationName.gate_3_region, LocationName.chao_garden_expert_region) + elif gates_len == 5: + connect(world, player, names, LocationName.gate_1_region, LocationName.chao_garden_beginner_region) + connect(world, player, names, LocationName.gate_2_region, LocationName.chao_garden_intermediate_region) + connect(world, player, names, LocationName.gate_3_region, LocationName.chao_garden_expert_region) + elif gates_len >= 6: + connect(world, player, names, LocationName.gate_1_region, LocationName.chao_garden_beginner_region) + connect(world, player, names, LocationName.gate_2_region, LocationName.chao_garden_intermediate_region) + connect(world, player, names, LocationName.gate_4_region, LocationName.chao_garden_expert_region) + def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None): # Shamelessly stolen from the ROR2 definition diff --git a/worlds/sa2b/Rules.py b/worlds/sa2b/Rules.py index 467840a5..3b09712e 100644 --- a/worlds/sa2b/Rules.py +++ b/worlds/sa2b/Rules.py @@ -1,10 +1,13 @@ +import typing + 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 + upgrade_location_table, boss_gate_set from ..AutoWorld import LogicMixin from ..generic.Rules import add_rule, set_rule +from .GateBosses import boss_has_requirement def set_mission_progress_rules(world: MultiWorld, player: int): @@ -276,7 +279,8 @@ def set_mission_upgrade_rules(world: MultiWorld, player: int): # Mission 5 Upgrade Requirements if world.include_missions[player].value >= 5: add_rule(world.get_location(LocationName.city_escape_5, player), - lambda state: state.has(ItemName.sonic_flame_ring, player)) + lambda state: state.has(ItemName.sonic_flame_ring, player) and + state.has(ItemName.sonic_light_shoes, 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)) @@ -328,13 +332,11 @@ def set_mission_upgrade_rules(world: MultiWorld, player: int): 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)) + state.has(ItemName.rouge_treasure_scope, player) and + state.has(ItemName.rouge_iron_boots, 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)) @@ -380,18 +382,21 @@ def set_mission_upgrade_rules(world: MultiWorld, player: int): lambda state: state.has(ItemName.eggman_jet_engine, player)) -def set_rules(world: MultiWorld, player: int): +def set_boss_gate_rules(world: MultiWorld, player: int, gate_bosses: typing.Dict[int, int]): + for x in range(len(gate_bosses)): + if boss_has_requirement(gate_bosses[x + 1]): + add_rule(world.get_location(boss_gate_set[x], player), + lambda state: state.has(ItemName.knuckles_shovel_claws, player)) + + +def set_rules(world: MultiWorld, player: int, gate_bosses: typing.Dict[int, int]): # Mission Progression Rules (Mission 1 begets Mission 2, etc.) set_mission_progress_rules(world, player) - # Upgrade Requirements for each location + # Upgrade Requirements for each mission 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) + # Upgrade Requirements for each boss gate + set_boss_gate_rules(world, player, gate_bosses) world.completion_condition[player] = lambda state: state.has(ItemName.maria, player) diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py index 9913a1d1..ffff2a93 100644 --- a/worlds/sa2b/__init__.py +++ b/worlds/sa2b/__init__.py @@ -2,7 +2,7 @@ import typing import math from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification -from .Items import SA2BItem, ItemData, item_table, upgrades_table +from .Items import SA2BItem, ItemData, item_table, upgrades_table, junk_table, trap_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, \ @@ -10,6 +10,8 @@ from .Regions import create_regions, shuffleable_regions, connect_regions, Level from .Rules import set_rules from .Names import ItemName, LocationName from ..AutoWorld import WebWorld, World +from .GateBosses import get_gate_bosses, get_boss_name +import Patch class SA2BWeb(WebWorld): @@ -49,21 +51,28 @@ class SA2BWorld(World): game: str = "Sonic Adventure 2 Battle" options = sa2b_options topology_present = False - data_version = 1 + data_version = 2 item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = all_locations + location_table: typing.Dict[str, int] + music_map: typing.Dict[int, int] emblems_for_cannons_core: int region_emblem_map: typing.Dict[int, int] + gate_costs: typing.Dict[int, int] + gate_bosses: typing.Dict[int, int] web = SA2BWeb() def _get_slot_data(self): return { - "ModVersion": 100, + "ModVersion": 101, "MusicMap": self.music_map, "MusicShuffle": self.world.music_shuffle[self.player].value, + "RequiredRank": self.world.required_rank[self.player].value, + "ChaoRaceChecks": self.world.chao_race_checks[self.player].value, + "ChaoGardenDifficulty": self.world.chao_garden_difficulty[self.player].value, "DeathLink": self.world.death_link[self.player].value, "IncludeMissions": self.world.include_missions[self.player].value, "EmblemPercentageForCannonsCore": self.world.emblem_percentage_for_cannons_core[self.player].value, @@ -71,6 +80,8 @@ class SA2BWorld(World): "LevelGateDistribution": self.world.level_gate_distribution[self.player].value, "EmblemsForCannonsCore": self.emblems_for_cannons_core, "RegionEmblemMap": self.region_emblem_map, + "GateCosts": self.gate_costs, + "GateBosses": self.gate_bosses, } def _create_items(self, name: str): @@ -122,31 +133,36 @@ class SA2BWorld(World): return levels_per_gate + def generate_early(self): + self.gate_bosses = get_gate_bosses(self.world, self.player) + 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.include_missions[self.player].value - - # Upgrades - total_required_locations += 28 + total_required_locations = len(self.location_table) + total_required_locations -= 1; # Locked Victory Location # 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 + # Cap at 180 Emblems + raw_emblem_count = total_required_locations - len(itempool) + total_emblem_count = min(raw_emblem_count, 180) + extra_junk_count = raw_emblem_count - total_emblem_count self.emblems_for_cannons_core = math.floor( total_emblem_count * (self.world.emblem_percentage_for_cannons_core[self.player].value / 100.0)) + gate_cost_mult = 1.0 + if self.world.level_gate_costs[self.player].value == 0: + gate_cost_mult = 0.6 + elif self.world.level_gate_costs[self.player].value == 1: + gate_cost_mult = 0.8 + shuffled_region_list = list(range(30)) emblem_requirement_list = list() self.world.random.shuffle(shuffled_region_list) @@ -157,6 +173,8 @@ class SA2BWorld(World): total_levels_added = 0 current_gate = 0 current_gate_emblems = 0 + self.gate_costs = dict() + self.gate_costs[0] = 0 gates = list() gates.append(LevelGate(0)) for i in range(30): @@ -170,20 +188,51 @@ class SA2BWorld(World): current_gate = self.world.number_of_level_gates[self.player].value else: current_gate_emblems = max( - math.floor(total_emblem_count * math.pow(total_levels_added / 30.0, 2.0)), current_gate) + math.floor(total_emblem_count * math.pow(total_levels_added / 30.0, 2.0) * gate_cost_mult), current_gate) gates.append(LevelGate(current_gate_emblems)) + self.gate_costs[current_gate] = current_gate_emblems levels_added_to_gate = 0 self.region_emblem_map = dict(zip(shuffled_region_list, emblem_requirement_list)) - connect_regions(self.world, self.player, gates, self.emblems_for_cannons_core) + connect_regions(self.world, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses) 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) + + non_required_emblems = (total_emblem_count - max_required_emblems) + junk_count = math.floor(non_required_emblems * (self.world.junk_fill_percentage[self.player].value / 100.0)) + itempool += [self.create_item(ItemName.emblem, True)] * (non_required_emblems - junk_count) + + # Carve Traps out of junk_count + trap_weights = [] + trap_weights += ([ItemName.omochao_trap] * self.world.omochao_trap_weight[self.player].value) + trap_weights += ([ItemName.timestop_trap] * self.world.timestop_trap_weight[self.player].value) + trap_weights += ([ItemName.confuse_trap] * self.world.confusion_trap_weight[self.player].value) + trap_weights += ([ItemName.tiny_trap] * self.world.tiny_trap_weight[self.player].value) + + junk_count += extra_junk_count + trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0)) + junk_count -= trap_count + + junk_pool = [] + junk_keys = list(junk_table.keys()) + for i in range(junk_count): + junk_item = self.world.random.choice(junk_keys) + junk_pool += [self.create_item(junk_item)] + + itempool += junk_pool + + trap_pool = [] + for i in range(trap_count): + trap_item = self.world.random.choice(trap_weights) + trap_pool += [self.create_item(trap_item)] + + itempool += trap_pool self.world.itempool += itempool + # Music Shuffle if self.world.music_shuffle[self.player] == "levels": musiclist_o = list(range(0, 47)) musiclist_s = musiclist_o.copy() @@ -198,18 +247,20 @@ class SA2BWorld(World): self.music_map = dict() def create_regions(self): - location_table = setup_locations(self.world, self.player) - create_regions(self.world, self.player, location_table) + self.location_table = setup_locations(self.world, self.player) + create_regions(self.world, self.player, self.location_table) def create_item(self, name: str, force_non_progression=False) -> Item: data = item_table[name] - if name == ItemName.emblem: - classification = ItemClassification.progression_skip_balancing - elif force_non_progression: + if force_non_progression: classification = ItemClassification.filler + elif name == ItemName.emblem: + classification = ItemClassification.progression_skip_balancing elif data.progression: classification = ItemClassification.progression + elif data.trap: + classification = ItemClassification.trap else: classification = ItemClassification.filler @@ -218,7 +269,17 @@ class SA2BWorld(World): return created_item def set_rules(self): - set_rules(self.world, self.player) + set_rules(self.world, self.player, self.gate_bosses) + + def write_spoiler(self, spoiler_handle: typing.TextIO): + spoiler_handle.write("\n") + header_text = "Sonic Adventure 2 Bosses for {}:\n" + header_text = header_text.format(self.world.player_name[self.player]) + spoiler_handle.write(header_text) + for x in range(len(self.gate_bosses.values())): + text = "Gate {0} Boss: {1}\n" + text = text.format((x + 1), get_boss_name(self.gate_bosses[x + 1])) + spoiler_handle.writelines(text) @classmethod def stage_fill_hook(cls, world, progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool, diff --git a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md index 38b24c23..b70037a9 100644 --- a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md +++ b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md @@ -6,7 +6,7 @@ The [player settings page for this game](../player-settings) contains all the op ## 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. +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 and a boss fight 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? @@ -14,7 +14,7 @@ The goal is to unlock and complete Cannons Core Mission 1, then complete the Bio ## 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. +All 30 story stages leading up to Cannons Core will be shuffled and can be optionally placed behind emblem requirement gates. Chao Garden emblems can optionally be added to the randomizer. All emblems from the selected mission range and all 28 character upgrade items get shuffled. At most 180 emblems will be added to the item pool, after which remaining items added will be random collectables (rings, shields, etc). Traps can also be optionally included. ## Which items can be in another player's world? @@ -28,3 +28,6 @@ Emblems have no visualization in the randomizer and items all retain their origi 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. +## How can I get started? + +To get started playing Sonic Adventure 2: Battle in Archipelago, [go to the setup guide for this game](../../../tutorial/Sonic%20Adventure%202%20Battle/setup/en) \ No newline at end of file diff --git a/worlds/sa2b/docs/setup_en.md b/worlds/sa2b/docs/setup_en.md index 9e6c10df..1fa45dd7 100644 --- a/worlds/sa2b/docs/setup_en.md +++ b/worlds/sa2b/docs/setup_en.md @@ -3,7 +3,7 @@ ## 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. + - The Battle DLC is required if you choose to add Chao Karate locations to the randomizer - 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 @@ -13,6 +13,8 @@ - Sonic Adventure 2 Tracker - PopTracker from: [PopTracker Releases Page](https://github.com/black-sliver/PopTracker/releases/) - Sonic Adventure 2: Battle Archipelago PopTracker pack from: [SA2B AP Tracker Releases Page](https://github.com/PoryGone/SA2B_AP_Tracker/releases/) +- Quality of life mods + - SA2 Volume Controls from: [SA2 Volume Controls Release Page] (https://gamebanana.com/mods/381193) ## Installation Procedures @@ -48,16 +50,24 @@ 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. +- 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) + +- "sonic2app.exe - Entry Point Not Found" + - Make sure the `APCpp.dll` is up to date. Follow Installation Procedures step 6 to update the dll. - Game is running too fast (Like Sonic). + - Limit framerate using the mod manager: + 1. Launch `SA2ModManager.exe`. + 2. Select the `Graphics` tab. + 3. Check the `Lock framerate` box under the Visuals section. + 4. Press the `Save` button. - If using an NVidia graphics card: 1. Open the NVIDIA Control Panel. 2. Select `Manage 3D Settings` under `3D settings` on the left.