SA2B: v2.2 Content Update (#1904)

* Ice Trap Support

* Support Animalsanity

* Add option for controlling number of emblems in pool

* Support Slow Trap

* Support Cutscene Traps

* Support Voice Shuffle

* Handle Boss Rush goals

* Fix create item reference to self.multiworld

* Support Ringlink

* Reduce beep frequency to 20

* Add Boss Rush Chaos Emerald Hunt Goal

* Fix Eternal Engine - Pipe 1 logic

* Add Chao voice shuffle

* Remove unused option

* Adjust wording of Required Cannon's Core Missions

* Fix incorrect region assignment

* Fix incorrect animal logics

* Fix Chao Race tooltip

* Remove Green Hill Animal Location

* Add Location Count info to tooltips

* Don't allow M4 first if animalsanity is active

* Add Iron Boots to Standard Logic Egg Quarters 5

* Make Vanilla Boss Rush actually Vanilla

* Increment Mod Version

* Increment Data Package Version

---------

Co-authored-by: RaspberrySpaceJam <tyler.summers@gmail.com>
This commit is contained in:
PoryGone
2023-06-27 17:38:58 -04:00
committed by GitHub
parent d51e0ec0ab
commit 1ced726d31
10 changed files with 3067 additions and 585 deletions

View File

@@ -10,7 +10,7 @@ from .Regions import create_regions, shuffleable_regions, connect_regions, Level
from .Rules import set_rules
from .Names import ItemName, LocationName
from worlds.AutoWorld import WebWorld, World
from .GateBosses import get_gate_bosses, get_boss_name
from .GateBosses import get_gate_bosses, get_boss_rush_bosses, get_boss_name
from .Missions import get_mission_table, get_mission_count_table, get_first_and_last_cannons_core_missions
import Patch
@@ -52,7 +52,7 @@ class SA2BWorld(World):
game: str = "Sonic Adventure 2 Battle"
option_definitions = sa2b_options
topology_present = False
data_version = 5
data_version = 6
item_name_groups = item_groups
item_name_to_id = {name: data.code for name, data in item_table.items()}
@@ -61,30 +61,35 @@ class SA2BWorld(World):
location_table: typing.Dict[str, int]
music_map: typing.Dict[int, int]
voice_map: typing.Dict[int, int]
mission_map: typing.Dict[int, int]
mission_count_map: typing.Dict[int, int]
emblems_for_cannons_core: int
region_emblem_map: typing.Dict[int, int]
gate_costs: typing.Dict[int, int]
gate_bosses: typing.Dict[int, int]
boss_rush_map: typing.Dict[int, int]
web = SA2BWeb()
def _get_slot_data(self):
return {
"ModVersion": 201,
"ModVersion": 202,
"Goal": self.multiworld.goal[self.player].value,
"MusicMap": self.music_map,
"VoiceMap": self.voice_map,
"MissionMap": self.mission_map,
"MissionCountMap": self.mission_count_map,
"MusicShuffle": self.multiworld.music_shuffle[self.player].value,
"Narrator": self.multiworld.narrator[self.player].value,
"MinigameTrapDifficulty": self.multiworld.minigame_trap_difficulty[self.player].value,
"RingLoss": self.multiworld.ring_loss[self.player].value,
"RingLink": self.multiworld.ring_link[self.player].value,
"RequiredRank": self.multiworld.required_rank[self.player].value,
"ChaoKeys": self.multiworld.keysanity[self.player].value,
"Whistlesanity": self.multiworld.whistlesanity[self.player].value,
"GoldBeetles": self.multiworld.beetlesanity[self.player].value,
"OmochaoChecks": self.multiworld.omosanity[self.player].value,
"AnimalChecks": self.multiworld.animalsanity[self.player].value,
"KartRaceChecks": self.multiworld.kart_race_checks[self.player].value,
"ChaoRaceChecks": self.multiworld.chao_race_checks[self.player].value,
"ChaoGardenDifficulty": self.multiworld.chao_garden_difficulty[self.player].value,
@@ -97,6 +102,7 @@ class SA2BWorld(World):
"RegionEmblemMap": self.region_emblem_map,
"GateCosts": self.gate_costs,
"GateBosses": self.gate_bosses,
"BossRushMap": self.boss_rush_map,
}
def _create_items(self, name: str):
@@ -160,19 +166,26 @@ class SA2BWorld(World):
self.multiworld.confusion_trap_weight[self.player].value = 0
self.multiworld.tiny_trap_weight[self.player].value = 0
self.multiworld.gravity_trap_weight[self.player].value = 0
self.multiworld.ice_trap_weight[self.player].value = 0
self.multiworld.slow_trap_weight[self.player].value = 0
valid_trap_weights = self.multiworld.exposition_trap_weight[self.player].value + self.multiworld.pong_trap_weight[self.player].value
valid_trap_weights = self.multiworld.exposition_trap_weight[self.player].value + \
self.multiworld.cutscene_trap_weight[self.player].value + \
self.multiworld.pong_trap_weight[self.player].value
if valid_trap_weights == 0:
self.multiworld.exposition_trap_weight[self.player].value = 4
self.multiworld.cutscene_trap_weight[self.player].value = 4
self.multiworld.pong_trap_weight[self.player].value = 4
if self.multiworld.kart_race_checks[self.player].value == 0:
self.multiworld.kart_race_checks[self.player].value = 2
self.gate_bosses = {}
self.boss_rush_map = {}
else:
self.gate_bosses = get_gate_bosses(self.multiworld, self.player)
self.gate_bosses = get_gate_bosses(self.multiworld, self.player)
self.boss_rush_map = get_boss_rush_bosses(self.multiworld, self.player)
def create_regions(self):
self.mission_map = get_mission_table(self.multiworld, self.player)
@@ -182,7 +195,7 @@ class SA2BWorld(World):
create_regions(self.multiworld, self.player, self.location_table)
# Not Generate Basic
if self.multiworld.goal[self.player].value == 0 or self.multiworld.goal[self.player].value == 2:
if self.multiworld.goal[self.player].value in [0, 2, 4, 5, 6]:
self.multiworld.get_location(LocationName.finalhazard, self.player).place_locked_item(self.create_item(ItemName.maria))
elif self.multiworld.goal[self.player].value == 1:
self.multiworld.get_location(LocationName.green_hill, self.player).place_locked_item(self.create_item(ItemName.maria))
@@ -198,16 +211,16 @@ class SA2BWorld(World):
if self.multiworld.goal[self.player].value != 3:
# Fill item pool with all required items
for item in {**upgrades_table}:
itempool += self._create_items(item)
itempool += [self.create_item(item, False, self.multiworld.goal[self.player].value)]
if self.multiworld.goal[self.player].value == 1 or self.multiworld.goal[self.player].value == 2:
if self.multiworld.goal[self.player].value in [1, 2, 6]:
# Some flavor of Chaos Emerald Hunt
for item in {**emeralds_table}:
itempool += self._create_items(item)
# Cap at 250 Emblems
# Cap at player-specified Emblem count
raw_emblem_count = total_required_locations - len(itempool)
total_emblem_count = min(raw_emblem_count, 250)
total_emblem_count = min(raw_emblem_count, self.multiworld.max_emblem_cap[self.player].value)
extra_junk_count = raw_emblem_count - total_emblem_count
self.emblems_for_cannons_core = math.floor(
@@ -253,7 +266,7 @@ class SA2BWorld(World):
first_cannons_core_mission, final_cannons_core_mission = get_first_and_last_cannons_core_missions(self.mission_map, self.mission_count_map)
connect_regions(self.multiworld, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses, first_cannons_core_mission, final_cannons_core_mission)
connect_regions(self.multiworld, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses, self.boss_rush_map, first_cannons_core_mission, final_cannons_core_mission)
max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core)
itempool += [self.create_item(ItemName.emblem) for _ in range(max_required_emblems)]
@@ -271,6 +284,9 @@ class SA2BWorld(World):
trap_weights += ([ItemName.gravity_trap] * self.multiworld.gravity_trap_weight[self.player].value)
trap_weights += ([ItemName.exposition_trap] * self.multiworld.exposition_trap_weight[self.player].value)
#trap_weights += ([ItemName.darkness_trap] * self.multiworld.darkness_trap_weight[self.player].value)
trap_weights += ([ItemName.ice_trap] * self.multiworld.ice_trap_weight[self.player].value)
trap_weights += ([ItemName.slow_trap] * self.multiworld.slow_trap_weight[self.player].value)
trap_weights += ([ItemName.cutscene_trap] * self.multiworld.cutscene_trap_weight[self.player].value)
trap_weights += ([ItemName.pong_trap] * self.multiworld.pong_trap_weight[self.player].value)
junk_count += extra_junk_count
@@ -347,12 +363,52 @@ class SA2BWorld(World):
self.music_map = dict(zip(musiclist_o, musiclist_s))
def create_item(self, name: str, force_non_progression=False) -> Item:
# Voice Shuffle
if self.multiworld.voice_shuffle[self.player] == "shuffled":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.multiworld.random.shuffle(voicelist_s)
self.voice_map = dict(zip(voicelist_o, voicelist_s))
elif self.multiworld.voice_shuffle[self.player] == "rude":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.multiworld.random.shuffle(voicelist_s)
for i in range(len(voicelist_s)):
if self.multiworld.random.randint(1,100) > 80:
voicelist_s[i] = 17
self.voice_map = dict(zip(voicelist_o, voicelist_s))
elif self.multiworld.voice_shuffle[self.player] == "chao":
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.multiworld.random.shuffle(voicelist_s)
for i in range(len(voicelist_s)):
voicelist_s[i] = self.multiworld.random.choice(range(2586, 2608))
self.voice_map = dict(zip(voicelist_o, voicelist_s))
elif self.multiworld.voice_shuffle[self.player] == "singularity":
voicelist_o = list(range(0, 2623))
voicelist_s = [self.multiworld.random.choice(voicelist_o)] * len(voicelist_o)
self.voice_map = dict(zip(voicelist_o, voicelist_s))
else:
voicelist_o = list(range(0, 2623))
voicelist_s = voicelist_o.copy()
self.voice_map = dict(zip(voicelist_o, voicelist_s))
def create_item(self, name: str, force_non_progression=False, goal=0) -> Item:
data = item_table[name]
if force_non_progression:
classification = ItemClassification.filler
elif name == ItemName.emblem:
elif name == ItemName.emblem or \
name in emeralds_table.keys() or \
(name == ItemName.knuckles_shovel_claws and goal in [4, 5]):
classification = ItemClassification.progression_skip_balancing
elif data.progression:
classification = ItemClassification.progression
@@ -369,18 +425,28 @@ class SA2BWorld(World):
self.multiworld.random.choice(junk_table.keys())
def set_rules(self):
set_rules(self.multiworld, self.player, self.gate_bosses, self.mission_map, self.mission_count_map)
set_rules(self.multiworld, self.player, self.gate_bosses, self.boss_rush_map, self.mission_map, self.mission_count_map)
def write_spoiler(self, spoiler_handle: typing.TextIO):
if self.multiworld.number_of_level_gates[self.player].value > 0:
if self.multiworld.number_of_level_gates[self.player].value > 0 or self.multiworld.goal[self.player].value in [4, 5, 6]:
spoiler_handle.write("\n")
header_text = "Sonic Adventure 2 Bosses for {}:\n"
header_text = header_text.format(self.multiworld.player_name[self.player])
spoiler_handle.write(header_text)
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)
if self.multiworld.number_of_level_gates[self.player].value > 0:
for x in range(len(self.gate_bosses.values())):
text = "Gate {0} Boss: {1}\n"
text = text.format((x + 1), get_boss_name(self.gate_bosses[x + 1]))
spoiler_handle.writelines(text)
spoiler_handle.write("\n")
if self.multiworld.goal[self.player].value in [4, 5, 6]:
for x in range(len(self.boss_rush_map.values())):
text = "Boss Rush Boss {0}: {1}\n"
text = text.format((x + 1), get_boss_name(self.boss_rush_map[x]))
spoiler_handle.writelines(text)
spoiler_handle.write("\n")
def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
gate_names = [