SA2B v1.1.0 (#673)

Co-authored-by: RaspberrySpaceJam <tyler.summers@gmail.com>
This commit is contained in:
PoryGone
2022-06-20 15:12:13 -04:00
committed by GitHub
parent 03e9034a98
commit a9e530721d
11 changed files with 762 additions and 91 deletions

View File

@@ -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,