mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
418 lines
24 KiB
Python
418 lines
24 KiB
Python
import math
|
|
from typing import Mapping, Any
|
|
|
|
from Options import OptionError
|
|
from .data.strings import OTHER, ITEMS, CATEGORY, LOCATIONS, SLOTDATA, GOALS, OPTIONS
|
|
from .items import item_descriptions, item_table, ShapezItem, \
|
|
buildings_routing, buildings_processing, buildings_other, \
|
|
buildings_top_row, buildings_wires, gameplay_unlocks, upgrades, \
|
|
big_upgrades, filler, trap, bundles, belt_and_extractor, standard_traps, random_draining_trap, split_draining_traps, \
|
|
whacky_upgrade_traps
|
|
from .locations import ShapezLocation, addlevels, addupgrades, addachievements, location_description, \
|
|
addshapesanity, addshapesanity_ut, shapesanity_simple, init_shapesanity_pool, achievement_locations, \
|
|
level_locations, upgrade_locations, shapesanity_locations, categories
|
|
from .presets import options_presets
|
|
from .options import ShapezOptions
|
|
from worlds.AutoWorld import World, WebWorld
|
|
from BaseClasses import Item, Tutorial, LocationProgressType, MultiWorld
|
|
from .regions import create_shapez_regions, has_x_belt_multiplier
|
|
from ..generic.Rules import add_rule
|
|
|
|
|
|
class ShapezWeb(WebWorld):
|
|
options_presets = options_presets
|
|
rich_text_options_doc = True
|
|
theme = "stone"
|
|
game_info_languages = ['en', 'de']
|
|
setup_en = Tutorial(
|
|
"Multiworld Setup Guide",
|
|
"A guide to playing shapez with Archipelago:",
|
|
"English",
|
|
"setup_en.md",
|
|
"setup/en",
|
|
["BlastSlimey"]
|
|
)
|
|
setup_de = Tutorial(
|
|
setup_en.tutorial_name,
|
|
setup_en.description,
|
|
"Deutsch",
|
|
"setup_de.md",
|
|
"setup/de",
|
|
["BlastSlimey"]
|
|
)
|
|
datapackage_settings_en = Tutorial(
|
|
"Changing datapackage settings",
|
|
"3000 locations are too many or not enough? Here's how you can change that:",
|
|
"English",
|
|
"datapackage_settings_en.md",
|
|
"datapackage_settings/en",
|
|
["BlastSlimey"]
|
|
)
|
|
datapackage_settings_de = Tutorial(
|
|
datapackage_settings_en.tutorial_name,
|
|
datapackage_settings_en.description,
|
|
"Deutsch",
|
|
"datapackage_settings_de.md",
|
|
"datapackage_settings/de",
|
|
["BlastSlimey"]
|
|
)
|
|
tutorials = [setup_en, setup_de, datapackage_settings_en, datapackage_settings_de]
|
|
item_descriptions = item_descriptions
|
|
location_descriptions = location_description
|
|
|
|
|
|
class ShapezWorld(World):
|
|
"""
|
|
shapez is an automation game about cutting, rotating, stacking, and painting shapes, that you extract from randomly
|
|
generated patches on an infinite canvas, without the need to manage your infinite resources or to pay for building
|
|
your factories.
|
|
"""
|
|
game = OTHER.game_name
|
|
options_dataclass = ShapezOptions
|
|
options: ShapezOptions
|
|
topology_present = True
|
|
web = ShapezWeb()
|
|
base_id = 20010707
|
|
item_name_to_id = {name: id for id, name in enumerate(item_table.keys(), base_id)}
|
|
location_name_to_id = {name: id for id, name in enumerate(level_locations + upgrade_locations
|
|
+ achievement_locations + shapesanity_locations, base_id)}
|
|
item_name_groups = {
|
|
"Main Buildings": {ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker},
|
|
"Processing Buildings": {*buildings_processing},
|
|
"Goal Buildings": {ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.rotator_ccw, ITEMS.color_mixer,
|
|
ITEMS.stacker, ITEMS.cutter_quad, ITEMS.painter_double, ITEMS.painter_quad, ITEMS.wires,
|
|
ITEMS.switch, ITEMS.const_signal},
|
|
"Most Useful Buildings": {ITEMS.balancer, ITEMS.tunnel, ITEMS.tunnel_tier_ii, ITEMS.comp_merger,
|
|
ITEMS.comp_splitter, ITEMS.trash, ITEMS.extractor_chain},
|
|
"Most Important Buildings": {*belt_and_extractor},
|
|
"Top Row Buildings": {*buildings_top_row},
|
|
"Wires Layer Buildings": {*buildings_wires},
|
|
"Gameplay Mechanics": {ITEMS.blueprints, ITEMS.wires},
|
|
"Upgrades": {*{ITEMS.upgrade(size, cat)
|
|
for size in {CATEGORY.big, CATEGORY.small, CATEGORY.gigantic, CATEGORY.rising}
|
|
for cat in {CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting}},
|
|
*{ITEMS.trap_upgrade(cat, size)
|
|
for cat in {CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting}
|
|
for size in {"", CATEGORY.demonic}},
|
|
*{ITEMS.upgrade(size, CATEGORY.random)
|
|
for size in {CATEGORY.big, CATEGORY.small}}},
|
|
**{f"{cat} Upgrades": {*{ITEMS.upgrade(size, cat)
|
|
for size in {CATEGORY.big, CATEGORY.small, CATEGORY.gigantic, CATEGORY.rising}},
|
|
*{ITEMS.trap_upgrade(cat, size)
|
|
for size in {"", CATEGORY.demonic}}}
|
|
for cat in {CATEGORY.belt, CATEGORY.miner, CATEGORY.processors, CATEGORY.painting}},
|
|
"Bundles": {*bundles},
|
|
"Traps": {*standard_traps, *random_draining_trap, *split_draining_traps, *whacky_upgrade_traps},
|
|
}
|
|
location_name_groups = {
|
|
"Levels": {*level_locations},
|
|
"Upgrades": {*upgrade_locations},
|
|
"Achievements": {*achievement_locations},
|
|
"Shapesanity": {*shapesanity_locations},
|
|
**{f"{cat} Upgrades": {loc for loc in upgrade_locations if loc.startswith(cat)} for cat in categories},
|
|
"Only Belt and Extractor": {LOCATIONS.level(1), LOCATIONS.level(1, 1),
|
|
LOCATIONS.my_eyes, LOCATIONS.its_a_mess, LOCATIONS.getting_into_it,
|
|
LOCATIONS.perfectionist, LOCATIONS.oops, LOCATIONS.i_need_trains, LOCATIONS.gps,
|
|
LOCATIONS.a_long_time, LOCATIONS.addicted,
|
|
LOCATIONS.shapesanity(1), LOCATIONS.shapesanity(2), LOCATIONS.shapesanity(3)},
|
|
}
|
|
|
|
def __init__(self, multiworld: MultiWorld, player: int):
|
|
super().__init__(multiworld, player)
|
|
|
|
# Defining instance attributes for each shapez world
|
|
# These are set to default values that should fail unit tests if not replaced with correct values
|
|
self.location_count: int = 0
|
|
self.level_logic: list[str] = []
|
|
self.upgrade_logic: list[str] = []
|
|
self.level_logic_type: str = ""
|
|
self.upgrade_logic_type: str = ""
|
|
self.random_logic_phase_length: list[int] = []
|
|
self.category_random_logic_amounts: dict[str, int] = {}
|
|
self.maxlevel: int = 0
|
|
self.finaltier: int = 0
|
|
self.included_locations: dict[str, tuple[str, LocationProgressType]] = {}
|
|
self.client_seed: int = 0
|
|
self.shapesanity_names: list[str] = []
|
|
self.upgrade_traps_allowed: bool = False
|
|
|
|
# Universal Tracker support
|
|
self.ut_active: bool = False
|
|
self.passthrough: dict[str, Any] = {}
|
|
self.location_id_to_alias: dict[int, str] = {}
|
|
|
|
@classmethod
|
|
def stage_generate_early(cls, multiworld: MultiWorld) -> None:
|
|
# Import the 75800 entries long shapesanity pool only once and only if it's actually needed
|
|
if len(shapesanity_simple) == 0:
|
|
init_shapesanity_pool()
|
|
|
|
def generate_early(self) -> None:
|
|
# Calculate all the important values used for generating a shapez world, with some of them being random
|
|
self.upgrade_traps_allowed: bool = (self.options.include_whacky_upgrades and
|
|
(not self.options.goal == GOALS.efficiency_iii) and
|
|
self.options.throughput_levels_ratio == 0)
|
|
|
|
# Load values from UT if this is a regenerated world
|
|
if hasattr(self.multiworld, "re_gen_passthrough"):
|
|
if OTHER.game_name in self.multiworld.re_gen_passthrough:
|
|
self.ut_active = True
|
|
self.passthrough = self.multiworld.re_gen_passthrough[OTHER.game_name]
|
|
self.maxlevel = self.passthrough[SLOTDATA.maxlevel]
|
|
self.finaltier = self.passthrough[SLOTDATA.finaltier]
|
|
self.client_seed = self.passthrough[SLOTDATA.seed]
|
|
self.level_logic = [self.passthrough[SLOTDATA.level_building(i+1)] for i in range(5)]
|
|
self.upgrade_logic = [self.passthrough[SLOTDATA.upgrade_building(i+1)] for i in range(5)]
|
|
self.level_logic_type = self.passthrough[SLOTDATA.rand_level_logic]
|
|
self.upgrade_logic_type = self.passthrough[SLOTDATA.rand_upgrade_logic]
|
|
self.random_logic_phase_length = [self.passthrough[SLOTDATA.phase_length(i)] for i in range(5)]
|
|
self.category_random_logic_amounts = {cat: self.passthrough[SLOTDATA.cat_buildings_amount(cat)]
|
|
for cat in [CATEGORY.belt_low, CATEGORY.miner_low,
|
|
CATEGORY.processors_low, CATEGORY.painting_low]}
|
|
# Forces balancers, tunnel, and trash to not appear in regen to make UT more accurate
|
|
self.options.early_balancer_tunnel_and_trash.value = 0
|
|
return
|
|
|
|
# "MAM" goal is supposed to be longer than vanilla, but to not have more options than necessary,
|
|
# both goal amounts for "MAM" and "Even fasterer" are set in a single option.
|
|
if self.options.goal == GOALS.mam and self.options.goal_amount < 27:
|
|
raise OptionError(self.player_name
|
|
+ ": When setting goal to 1 ('mam'), goal_amount must be at least 27 and not "
|
|
+ str(self.options.goal_amount.value))
|
|
|
|
# If lock_belt_and_extractor is true, the only sphere 1 locations will be achievements
|
|
if self.options.lock_belt_and_extractor and not self.options.include_achievements:
|
|
raise OptionError(self.player_name + ": Achievements must be included when belt and extractor are locked")
|
|
|
|
# Determines maxlevel and finaltier, which are needed for location and item generation
|
|
if self.options.goal == GOALS.vanilla:
|
|
self.maxlevel = 25
|
|
self.finaltier = 8
|
|
elif self.options.goal == GOALS.mam:
|
|
self.maxlevel = self.options.goal_amount - 1
|
|
self.finaltier = 8
|
|
elif self.options.goal == GOALS.even_fasterer:
|
|
self.maxlevel = 26
|
|
self.finaltier = self.options.goal_amount.value
|
|
else: # goal == efficiency_iii
|
|
self.maxlevel = 26
|
|
self.finaltier = 8
|
|
|
|
# Setting the seed for the game before any other randomization call is done
|
|
self.client_seed = self.random.randint(0, 100000)
|
|
|
|
# Determines the order of buildings for levels logic
|
|
if self.options.randomize_level_requirements:
|
|
self.level_logic_type = self.options.randomize_level_logic.current_key
|
|
if self.level_logic_type.endswith(OPTIONS.logic_shuffled) or self.level_logic_type == OPTIONS.logic_dopamine:
|
|
vanilla_list = [ITEMS.cutter, ITEMS.painter, ITEMS.stacker]
|
|
while len(vanilla_list) > 0:
|
|
index = self.random.randint(0, len(vanilla_list)-1)
|
|
next_building = vanilla_list.pop(index)
|
|
if next_building == ITEMS.cutter:
|
|
vanilla_list.append(ITEMS.rotator)
|
|
if next_building == ITEMS.painter:
|
|
vanilla_list.append(ITEMS.color_mixer)
|
|
self.level_logic.append(next_building)
|
|
else:
|
|
self.level_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker]
|
|
else:
|
|
self.level_logic_type = OPTIONS.logic_vanilla
|
|
self.level_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker]
|
|
|
|
# Determines the order of buildings for upgrades logic
|
|
if self.options.randomize_upgrade_requirements:
|
|
self.upgrade_logic_type = self.options.randomize_upgrade_logic.current_key
|
|
if self.upgrade_logic_type == OPTIONS.logic_hardcore:
|
|
self.upgrade_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker]
|
|
elif self.upgrade_logic_type == OPTIONS.logic_category:
|
|
self.upgrade_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.stacker, ITEMS.painter, ITEMS.color_mixer]
|
|
else:
|
|
vanilla_list = [ITEMS.cutter, ITEMS.painter, ITEMS.stacker]
|
|
while len(vanilla_list) > 0:
|
|
index = self.random.randint(0, len(vanilla_list)-1)
|
|
next_building = vanilla_list.pop(index)
|
|
if next_building == ITEMS.cutter:
|
|
vanilla_list.append(ITEMS.rotator)
|
|
if next_building == ITEMS.painter:
|
|
vanilla_list.append(ITEMS.color_mixer)
|
|
self.upgrade_logic.append(next_building)
|
|
else:
|
|
self.upgrade_logic_type = OPTIONS.logic_vanilla_like
|
|
self.upgrade_logic = [ITEMS.cutter, ITEMS.rotator, ITEMS.painter, ITEMS.color_mixer, ITEMS.stacker]
|
|
|
|
# Determine lenghts of phases in level logic type "random"
|
|
self.random_logic_phase_length = [1, 1, 1, 1, 1]
|
|
if self.level_logic_type.startswith(OPTIONS.logic_random_steps):
|
|
remaininglength = self.maxlevel - 1
|
|
for phase in range(0, 5):
|
|
if self.random.random() < 0.1: # Make sure that longer phases are less frequent
|
|
self.random_logic_phase_length[phase] = self.random.randint(0, remaininglength)
|
|
else:
|
|
self.random_logic_phase_length[phase] = self.random.randint(0, remaininglength // (6 - phase))
|
|
remaininglength -= self.random_logic_phase_length[phase]
|
|
|
|
# Determine amount of needed buildings for each category in upgrade logic type "category_random"
|
|
self.category_random_logic_amounts = {CATEGORY.belt_low: 0, CATEGORY.miner_low: 1,
|
|
CATEGORY.processors_low: 2, CATEGORY.painting_low: 3}
|
|
if self.upgrade_logic_type == OPTIONS.logic_category_random:
|
|
cats = [CATEGORY.belt_low, CATEGORY.miner_low, CATEGORY.processors_low, CATEGORY.painting_low]
|
|
nextcat = self.random.choice(cats)
|
|
self.category_random_logic_amounts[nextcat] = 0
|
|
cats.remove(nextcat)
|
|
for cat in cats:
|
|
self.category_random_logic_amounts[cat] = self.random.randint(0, 5)
|
|
|
|
def create_item(self, name: str) -> Item:
|
|
return ShapezItem(name, item_table[name](self.options), self.item_name_to_id[name], self.player)
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
return filler(self.random.random(), bool(self.options.include_whacky_upgrades))
|
|
|
|
def append_shapesanity(self, name: str) -> None:
|
|
"""This method is given as a parameter when creating the locations for shapesanity."""
|
|
self.shapesanity_names.append(name)
|
|
|
|
def add_alias(self, location_name: str, alias: str):
|
|
"""This method is given as a parameter when locations with helpful aliases for UT are created."""
|
|
if self.ut_active:
|
|
self.location_id_to_alias[self.location_name_to_id[location_name]] = alias
|
|
|
|
def create_regions(self) -> None:
|
|
# Create list of all included level and upgrade locations based on player options
|
|
# This already includes the region to be placed in and the LocationProgressType
|
|
self.included_locations = {**addlevels(self.maxlevel, self.level_logic_type,
|
|
self.random_logic_phase_length),
|
|
**addupgrades(self.finaltier, self.upgrade_logic_type,
|
|
self.category_random_logic_amounts)}
|
|
|
|
# Add shapesanity to included location and creates the corresponding list based on player options
|
|
if self.ut_active:
|
|
self.shapesanity_names = self.passthrough[SLOTDATA.shapesanity]
|
|
self.included_locations.update(addshapesanity_ut(self.shapesanity_names, self.add_alias))
|
|
else:
|
|
self.included_locations.update(addshapesanity(self.options.shapesanity_amount.value, self.random,
|
|
self.append_shapesanity, self.add_alias))
|
|
|
|
# Add achievements to included locations based on player options
|
|
if self.options.include_achievements:
|
|
self.included_locations.update(addachievements(
|
|
bool(self.options.exclude_softlock_achievements), bool(self.options.exclude_long_playtime_achievements),
|
|
bool(self.options.exclude_progression_unreasonable), self.maxlevel, self.upgrade_logic_type,
|
|
self.category_random_logic_amounts, self.options.goal.current_key, self.included_locations,
|
|
self.add_alias, self.upgrade_traps_allowed))
|
|
|
|
# Save the final amount of to-be-filled locations
|
|
self.location_count = len(self.included_locations)
|
|
|
|
# Create regions and entrances based on included locations and player options
|
|
self.multiworld.regions.extend(create_shapez_regions(self.player, self.multiworld,
|
|
bool(self.options.allow_floating_layers.value),
|
|
self.included_locations, self.location_name_to_id,
|
|
self.level_logic, self.upgrade_logic,
|
|
self.options.early_balancer_tunnel_and_trash.current_key,
|
|
self.options.goal.current_key))
|
|
|
|
def create_items(self) -> None:
|
|
# Include guaranteed items (game mechanic unlocks and 7x4 big upgrades)
|
|
included_items: list[Item] = ([self.create_item(name) for name in buildings_processing.keys()]
|
|
+ [self.create_item(name) for name in buildings_routing.keys()]
|
|
+ [self.create_item(name) for name in buildings_other.keys()]
|
|
+ [self.create_item(name) for name in buildings_top_row.keys()]
|
|
+ [self.create_item(name) for name in buildings_wires.keys()]
|
|
+ [self.create_item(name) for name in gameplay_unlocks.keys()]
|
|
+ [self.create_item(name) for name in big_upgrades for _ in range(7)])
|
|
|
|
if not self.options.lock_belt_and_extractor:
|
|
for name in belt_and_extractor:
|
|
self.multiworld.push_precollected(self.create_item(name))
|
|
else: # This also requires self.options.include_achievements to be true
|
|
included_items.extend([self.create_item(name) for name in belt_and_extractor.keys()])
|
|
|
|
# Give a detailed error message if there are already more items than available locations.
|
|
# At the moment, this won't happen, but it's better for debugging in case a future update breaks things.
|
|
if len(included_items) > self.location_count:
|
|
raise RuntimeError(self.player_name + ": There are more guaranteed items than available locations")
|
|
|
|
# Get value from traps probability option and convert to float
|
|
traps_probability = self.options.traps_percentage/100
|
|
split_draining = bool(self.options.split_inventory_draining_trap)
|
|
# Fill remaining locations with fillers
|
|
for x in range(self.location_count - len(included_items)):
|
|
if self.random.random() < traps_probability:
|
|
# Fill with trap
|
|
included_items.append(self.create_item(trap(self.random.random(), split_draining,
|
|
self.upgrade_traps_allowed)))
|
|
else:
|
|
# Fil with random filler item
|
|
included_items.append(self.create_item(self.get_filler_item_name()))
|
|
|
|
# Add correct number of items to itempool
|
|
self.multiworld.itempool += included_items
|
|
|
|
# Add balancer, tunnel, and trash to early items if player options say so
|
|
if self.options.early_balancer_tunnel_and_trash == OPTIONS.sphere_1:
|
|
self.multiworld.early_items[self.player][ITEMS.balancer] = 1
|
|
self.multiworld.early_items[self.player][ITEMS.tunnel] = 1
|
|
self.multiworld.early_items[self.player][ITEMS.trash] = 1
|
|
|
|
def set_rules(self) -> None:
|
|
# Levels might need more belt speed if they require throughput per second. As the randomization of what levels
|
|
# need throughput happens in the client mod, this logic needs to be applied to all levels. This is applied to
|
|
# every individual level instead of regions, because they would need a much more complex calculation to prevent
|
|
# softlocks.
|
|
|
|
def f(x: int, name: str):
|
|
# These calculations are taken from the client mod
|
|
if x < 26:
|
|
throughput = math.ceil((2.999+x*0.333)*self.options.required_shapes_multiplier/10)
|
|
else:
|
|
throughput = min((4+(x-26)*0.25)*self.options.required_shapes_multiplier/10, 200)
|
|
if throughput/32 >= 1:
|
|
add_rule(self.get_location(name),
|
|
lambda state: has_x_belt_multiplier(state, self.player, throughput/32))
|
|
|
|
if not self.options.throughput_levels_ratio == 0:
|
|
f(0, LOCATIONS.level(1, 1))
|
|
f(19, LOCATIONS.level(20, 1))
|
|
f(19, LOCATIONS.level(20, 2))
|
|
for _x in range(self.maxlevel):
|
|
f(_x, LOCATIONS.level(_x+1))
|
|
if self.options.goal.current_key in [GOALS.vanilla, GOALS.mam]:
|
|
f(self.maxlevel, LOCATIONS.goal)
|
|
|
|
def fill_slot_data(self) -> Mapping[str, Any]:
|
|
# Buildings logic; all buildings as individual parameters
|
|
level_logic_data = {SLOTDATA.level_building(x+1): self.level_logic[x] for x in range(5)}
|
|
upgrade_logic_data = {SLOTDATA.upgrade_building(x+1): self.upgrade_logic[x] for x in range(5)}
|
|
# Randomized values for certain logic types
|
|
logic_type_random_data = {SLOTDATA.phase_length(x): self.random_logic_phase_length[x] for x in range(0, 5)}
|
|
logic_type_cat_random_data = {SLOTDATA.cat_buildings_amount(cat): self.category_random_logic_amounts[cat]
|
|
for cat in [CATEGORY.belt_low, CATEGORY.miner_low,
|
|
CATEGORY.processors_low, CATEGORY.painting_low]}
|
|
|
|
# Options that are relevant to the mod
|
|
option_data = {
|
|
SLOTDATA.goal: self.options.goal.current_key,
|
|
SLOTDATA.maxlevel: self.maxlevel,
|
|
SLOTDATA.finaltier: self.finaltier,
|
|
SLOTDATA.req_shapes_mult: self.options.required_shapes_multiplier.value,
|
|
SLOTDATA.allow_float_layers: bool(self.options.allow_floating_layers),
|
|
SLOTDATA.rand_level_req: bool(self.options.randomize_level_requirements),
|
|
SLOTDATA.rand_upgrade_req: bool(self.options.randomize_upgrade_requirements),
|
|
SLOTDATA.rand_level_logic: self.level_logic_type,
|
|
SLOTDATA.rand_upgrade_logic: self.upgrade_logic_type,
|
|
SLOTDATA.throughput_levels_ratio: self.options.throughput_levels_ratio.value,
|
|
SLOTDATA.comp_growth_gradient: self.options.complexity_growth_gradient.value,
|
|
SLOTDATA.same_late: bool(self.options.same_late_upgrade_requirements),
|
|
SLOTDATA.toolbar_shuffling: bool(self.options.toolbar_shuffling),
|
|
}
|
|
|
|
return {**level_logic_data, **upgrade_logic_data, **option_data, **logic_type_random_data,
|
|
**logic_type_cat_random_data, SLOTDATA.seed: self.client_seed,
|
|
SLOTDATA.shapesanity: self.shapesanity_names}
|
|
|
|
def interpret_slot_data(self, slot_data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Helper function for Universal Tracker"""
|
|
return slot_data
|