mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-24 05:53:19 -07:00
291 lines
13 KiB
Python
291 lines
13 KiB
Python
from BaseClasses import Item, ItemClassification, Region, LocationProgressType
|
|
|
|
from .Items import HatInTimeItem, item_table, item_frequencies, item_dlc_enabled, junk_weights,\
|
|
create_item, create_multiple_items, create_junk_items, relic_groups, act_contracts, alps_hooks, \
|
|
get_total_time_pieces
|
|
|
|
from .Regions import create_region, create_regions, connect_regions, randomize_act_entrances, chapter_act_info, \
|
|
create_events, chapter_regions, act_chapters
|
|
|
|
from .Locations import HatInTimeLocation, location_table, get_total_locations, contract_locations, is_location_valid, \
|
|
get_location_names
|
|
|
|
from .Types import HatDLC, HatType, ChapterIndex
|
|
from .Options import ahit_options, slot_data_options, adjust_options
|
|
from worlds.AutoWorld import World
|
|
from .Rules import set_rules
|
|
import typing
|
|
|
|
hat_craft_order: typing.Dict[int, typing.List[HatType]] = {}
|
|
hat_yarn_costs: typing.Dict[int, typing.Dict[HatType, int]] = {}
|
|
chapter_timepiece_costs: typing.Dict[int, typing.Dict[ChapterIndex, int]] = {}
|
|
|
|
|
|
class HatInTimeWorld(World):
|
|
"""
|
|
A Hat in Time is a cute-as-heck 3D platformer featuring a little girl who stitches hats for wicked powers!
|
|
Freely explore giant worlds and recover Time Pieces to travel to new heights!
|
|
"""
|
|
|
|
game = "A Hat in Time"
|
|
data_version = 1
|
|
|
|
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
|
location_name_to_id = get_location_names()
|
|
|
|
option_definitions = ahit_options
|
|
act_connections: typing.Dict[str, str] = {}
|
|
nyakuza_thug_items: typing.Dict[str, int] = {}
|
|
shop_locs: typing.List[str] = []
|
|
item_name_groups = relic_groups
|
|
badge_seller_count: int = 0
|
|
|
|
def generate_early(self):
|
|
adjust_options(self)
|
|
|
|
# If our starting chapter is 4 and act rando isn't on, force hookshot into inventory
|
|
# If starting chapter is 3 and painting shuffle is enabled, and act rando isn't, give one free painting unlock
|
|
start_chapter: int = self.multiworld.StartingChapter[self.player].value
|
|
|
|
if start_chapter == 4 or start_chapter == 3:
|
|
if self.multiworld.ActRandomizer[self.player].value == 0 \
|
|
or self.multiworld.VanillaAlpine[self.player].value > 0:
|
|
if start_chapter == 4:
|
|
self.multiworld.push_precollected(self.create_item("Hookshot Badge"))
|
|
|
|
if start_chapter == 3 and self.multiworld.ShuffleSubconPaintings[self.player].value > 0:
|
|
self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock"))
|
|
|
|
if self.multiworld.StartWithCompassBadge[self.player].value > 0:
|
|
self.multiworld.push_precollected(self.create_item("Compass Badge"))
|
|
|
|
def create_regions(self):
|
|
self.nyakuza_thug_items = {}
|
|
self.shop_locs = []
|
|
self.badge_seller_count = 0
|
|
create_regions(self)
|
|
|
|
# place default contract locations if contract shuffle is off so logic can still utilize them
|
|
if self.multiworld.ShuffleActContracts[self.player].value == 0:
|
|
for name in contract_locations.keys():
|
|
self.multiworld.get_location(name, self.player).place_locked_item(create_item(self, name))
|
|
else:
|
|
# The bag trap contract check needs to be excluded, because if the player has the Subcon Well contract,
|
|
# the trap will not activate, locking the player out of the check permanently
|
|
self.multiworld.get_location("Snatcher's Contract - The Subcon Well",
|
|
self.player).progress_type = LocationProgressType.EXCLUDED
|
|
|
|
def create_items(self):
|
|
hat_yarn_costs[self.player] = {HatType.SPRINT: -1, HatType.BREWING: -1, HatType.ICE: -1,
|
|
HatType.DWELLER: -1, HatType.TIME_STOP: -1}
|
|
|
|
hat_craft_order[self.player] = [HatType.SPRINT, HatType.BREWING, HatType.ICE,
|
|
HatType.DWELLER, HatType.TIME_STOP]
|
|
|
|
self.topology_present = self.multiworld.ActRandomizer[self.player].value
|
|
|
|
# Item Pool
|
|
itempool: typing.List[Item] = []
|
|
self.calculate_yarn_costs()
|
|
yarn_pool: typing.List[Item] = create_multiple_items(self, "Yarn", self.multiworld.YarnAvailable[self.player].value)
|
|
|
|
# 1/5 is progression balanced
|
|
for i in range(len(yarn_pool) // 5):
|
|
yarn_pool[i].classification = ItemClassification.progression
|
|
|
|
itempool += yarn_pool
|
|
|
|
if self.multiworld.RandomizeHatOrder[self.player].value > 0:
|
|
self.multiworld.random.shuffle(hat_craft_order[self.player])
|
|
|
|
for name in item_table.keys():
|
|
if name == "Yarn":
|
|
continue
|
|
|
|
if not item_dlc_enabled(self, name):
|
|
continue
|
|
|
|
item_type: ItemClassification = item_table.get(name).classification
|
|
if item_type is ItemClassification.filler or item_type is ItemClassification.trap:
|
|
continue
|
|
|
|
if name in act_contracts.keys() and self.multiworld.ShuffleActContracts[self.player].value == 0:
|
|
continue
|
|
|
|
if name in alps_hooks.keys() and self.multiworld.ShuffleAlpineZiplines[self.player].value == 0:
|
|
continue
|
|
|
|
if name == "Progressive Painting Unlock" \
|
|
and self.multiworld.ShuffleSubconPaintings[self.player].value == 0:
|
|
continue
|
|
|
|
if self.multiworld.StartWithCompassBadge[self.player].value > 0 and name == "Compass Badge":
|
|
continue
|
|
|
|
if name == "Time Piece":
|
|
tp_count: int = 40
|
|
max_extra: int = 0
|
|
if self.multiworld.EnableDLC1[self.player].value > 0:
|
|
max_extra += 6
|
|
|
|
if self.multiworld.EnableDLC2[self.player].value > 0:
|
|
max_extra += 10
|
|
|
|
tp_count += min(max_extra, self.multiworld.MaxExtraTimePieces[self.player].value)
|
|
tp_list: typing.List[Item] = create_multiple_items(self, name, tp_count)
|
|
|
|
# 1/5 is progression balanced
|
|
for i in range(len(tp_list) // 5):
|
|
tp_list[i].classification = ItemClassification.progression
|
|
|
|
itempool += tp_list
|
|
continue
|
|
|
|
itempool += create_multiple_items(self, name, item_frequencies.get(name, 1))
|
|
|
|
create_events(self)
|
|
total_locations: int = get_total_locations(self)
|
|
itempool += create_junk_items(self, total_locations-len(itempool))
|
|
self.multiworld.itempool += itempool
|
|
|
|
def set_rules(self):
|
|
self.act_connections = {}
|
|
chapter_timepiece_costs[self.player] = {ChapterIndex.MAFIA: -1,
|
|
ChapterIndex.BIRDS: -1,
|
|
ChapterIndex.SUBCON: -1,
|
|
ChapterIndex.ALPINE: -1,
|
|
ChapterIndex.FINALE: -1,
|
|
ChapterIndex.CRUISE: -1,
|
|
ChapterIndex.METRO: -1}
|
|
|
|
if self.multiworld.ActRandomizer[self.player].value > 0:
|
|
randomize_act_entrances(self)
|
|
|
|
set_rules(self)
|
|
|
|
def create_item(self, name: str) -> Item:
|
|
return create_item(self, name)
|
|
|
|
def fill_slot_data(self) -> dict:
|
|
slot_data: dict = {"SprintYarnCost": hat_yarn_costs[self.player][HatType.SPRINT],
|
|
"BrewingYarnCost": hat_yarn_costs[self.player][HatType.BREWING],
|
|
"IceYarnCost": hat_yarn_costs[self.player][HatType.ICE],
|
|
"DwellerYarnCost": hat_yarn_costs[self.player][HatType.DWELLER],
|
|
"TimeStopYarnCost": hat_yarn_costs[self.player][HatType.TIME_STOP],
|
|
"Chapter1Cost": chapter_timepiece_costs[self.player][ChapterIndex.MAFIA],
|
|
"Chapter2Cost": chapter_timepiece_costs[self.player][ChapterIndex.BIRDS],
|
|
"Chapter3Cost": chapter_timepiece_costs[self.player][ChapterIndex.SUBCON],
|
|
"Chapter4Cost": chapter_timepiece_costs[self.player][ChapterIndex.ALPINE],
|
|
"Chapter5Cost": chapter_timepiece_costs[self.player][ChapterIndex.FINALE],
|
|
"Chapter6Cost": chapter_timepiece_costs[self.player][ChapterIndex.CRUISE],
|
|
"Chapter7Cost": chapter_timepiece_costs[self.player][ChapterIndex.METRO],
|
|
"Hat1": int(hat_craft_order[self.player][0]),
|
|
"Hat2": int(hat_craft_order[self.player][1]),
|
|
"Hat3": int(hat_craft_order[self.player][2]),
|
|
"Hat4": int(hat_craft_order[self.player][3]),
|
|
"Hat5": int(hat_craft_order[self.player][4]),
|
|
"BadgeSellerItemCount": self.badge_seller_count,
|
|
"SeedNumber": self.multiworld.seed} # For shop prices
|
|
|
|
if self.multiworld.ActRandomizer[self.player].value > 0:
|
|
for name in self.act_connections.keys():
|
|
slot_data[name] = self.act_connections[name]
|
|
|
|
if self.multiworld.EnableDLC2[self.player].value > 0:
|
|
for name in self.nyakuza_thug_items.keys():
|
|
slot_data[name] = self.nyakuza_thug_items[name]
|
|
|
|
for option_name in slot_data_options:
|
|
option = getattr(self.multiworld, option_name)[self.player]
|
|
slot_data[option_name] = option.value
|
|
|
|
return slot_data
|
|
|
|
def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
|
|
new_hint_data = {}
|
|
alpine_regions = ["The Birdhouse", "The Lava Cake", "The Windmill", "The Twilight Bell"]
|
|
metro_regions = ["Yellow Overpass Station", "Green Clean Station", "Bluefin Tunnel", "Pink Paw Station"]
|
|
|
|
for key, data in location_table.items():
|
|
if not is_location_valid(self, key):
|
|
continue
|
|
|
|
location = self.multiworld.get_location(key, self.player)
|
|
region_name: str
|
|
|
|
if data.region in alpine_regions:
|
|
region_name = "Alpine Free Roam"
|
|
elif data.region in metro_regions:
|
|
region_name = "Nyakuza Free Roam"
|
|
elif data.region in chapter_act_info.keys():
|
|
region_name = location.parent_region.name
|
|
else:
|
|
continue
|
|
|
|
new_hint_data[location.address] = self.get_shuffled_region(region_name)
|
|
|
|
if self.multiworld.EnableDLC1[self.player].value > 0 and self.multiworld.Tasksanity[self.player].value > 0:
|
|
ship_shape_region = self.get_shuffled_region("Ship Shape")
|
|
id_start: int = 300204
|
|
for i in range(self.multiworld.TasksanityCheckCount[self.player].value):
|
|
new_hint_data[id_start+i] = ship_shape_region
|
|
|
|
hint_data[self.player] = new_hint_data
|
|
|
|
def write_spoiler_header(self, spoiler_handle: typing.TextIO):
|
|
for i in self.get_chapter_costs():
|
|
spoiler_handle.write("Chapter %i Cost: %i\n" % (i, self.get_chapter_costs()[ChapterIndex(i)]))
|
|
|
|
for hat in hat_craft_order[self.player]:
|
|
spoiler_handle.write("Hat Cost: %s: %i\n" % (hat, hat_yarn_costs[self.player][hat]))
|
|
|
|
def calculate_yarn_costs(self):
|
|
mw = self.multiworld
|
|
p = self.player
|
|
min_yarn_cost = int(min(mw.YarnCostMin[p].value, mw.YarnCostMax[p].value))
|
|
max_yarn_cost = int(max(mw.YarnCostMin[p].value, mw.YarnCostMax[p].value))
|
|
|
|
max_cost: int = 0
|
|
for i in range(5):
|
|
cost = mw.random.randint(min(min_yarn_cost, max_yarn_cost), max(max_yarn_cost, min_yarn_cost))
|
|
hat_yarn_costs[self.player][HatType(i)] = cost
|
|
max_cost += cost
|
|
|
|
available_yarn = mw.YarnAvailable[p].value
|
|
if max_cost > available_yarn:
|
|
mw.YarnAvailable[p].value = max_cost
|
|
available_yarn = max_cost
|
|
|
|
# make sure we always have at least 8 extra
|
|
if max_cost + 8 > available_yarn:
|
|
mw.YarnAvailable[p].value += (max_cost + 8) - available_yarn
|
|
|
|
def set_chapter_cost(self, chapter: ChapterIndex, cost: int):
|
|
chapter_timepiece_costs[self.player][chapter] = cost
|
|
|
|
def get_chapter_cost(self, chapter: ChapterIndex) -> int:
|
|
return chapter_timepiece_costs[self.player].get(chapter)
|
|
|
|
# Sets an act entrance in slot data by specifying the Hat_ChapterActInfo, to be used in-game
|
|
def update_chapter_act_info(self, original_region: Region, new_region: Region):
|
|
original_act_info = chapter_act_info[original_region.name]
|
|
new_act_info = chapter_act_info[new_region.name]
|
|
self.act_connections[original_act_info] = new_act_info
|
|
|
|
def get_shuffled_region(self, region: str) -> str:
|
|
ci: str = chapter_act_info[region]
|
|
for key, val in self.act_connections.items():
|
|
if val == ci:
|
|
for name in chapter_act_info.keys():
|
|
if chapter_act_info[name] == key:
|
|
return name
|
|
|
|
def get_hat_craft_order(self):
|
|
return hat_craft_order[self.player]
|
|
|
|
def get_hat_yarn_costs(self):
|
|
return hat_yarn_costs[self.player]
|
|
|
|
def get_chapter_costs(self):
|
|
return chapter_timepiece_costs[self.player]
|