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:
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user