Files
Archipelago/worlds/ahit/Regions.py
2023-10-26 23:02:34 -04:00

901 lines
39 KiB
Python

from worlds.AutoWorld import World
from BaseClasses import Region, Entrance, ItemClassification, Location
from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem
from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \
shop_locations, get_tasksanity_start_id, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard
import typing
from .Rules import set_rift_rules, get_difficulty
# ChapterIndex: region
chapter_regions = {
ChapterIndex.SPACESHIP: "Spaceship",
ChapterIndex.MAFIA: "Mafia Town",
ChapterIndex.BIRDS: "Battle of the Birds",
ChapterIndex.SUBCON: "Subcon Forest",
ChapterIndex.ALPINE: "Alpine Skyline",
ChapterIndex.FINALE: "Time's End",
ChapterIndex.CRUISE: "The Arctic Cruise",
ChapterIndex.METRO: "Nyakuza Metro",
}
# entrance: region
act_entrances = {
"Welcome to Mafia Town": "Mafia Town - Act 1",
"Barrel Battle": "Mafia Town - Act 2",
"She Came from Outer Space": "Mafia Town - Act 3",
"Down with the Mafia!": "Mafia Town - Act 4",
"Cheating the Race": "Mafia Town - Act 5",
"Heating Up Mafia Town": "Mafia Town - Act 6",
"The Golden Vault": "Mafia Town - Act 7",
"Dead Bird Studio": "Battle of the Birds - Act 1",
"Murder on the Owl Express": "Battle of the Birds - Act 2",
"Picture Perfect": "Battle of the Birds - Act 3",
"Train Rush": "Battle of the Birds - Act 4",
"The Big Parade": "Battle of the Birds - Act 5",
"Award Ceremony": "Battle of the Birds - Finale A",
"Dead Bird Studio Basement": "Battle of the Birds - Finale B",
"Contractual Obligations": "Subcon Forest - Act 1",
"The Subcon Well": "Subcon Forest - Act 2",
"Toilet of Doom": "Subcon Forest - Act 3",
"Queen Vanessa's Manor": "Subcon Forest - Act 4",
"Mail Delivery Service": "Subcon Forest - Act 5",
"Your Contract has Expired": "Subcon Forest - Finale",
"Alpine Free Roam": "Alpine Skyline - Free Roam",
"The Illness has Spread": "Alpine Skyline - Finale",
"The Finale": "Time's End - Act 1",
"Bon Voyage!": "The Arctic Cruise - Act 1",
"Ship Shape": "The Arctic Cruise - Act 2",
"Rock the Boat": "The Arctic Cruise - Finale",
"Nyakuza Free Roam": "Nyakuza Metro - Free Roam",
"Rush Hour": "Nyakuza Metro - Finale",
}
act_chapters = {
"Time Rift - Gallery": "Spaceship",
"Time Rift - The Lab": "Spaceship",
"Welcome to Mafia Town": "Mafia Town",
"Barrel Battle": "Mafia Town",
"She Came from Outer Space": "Mafia Town",
"Down with the Mafia!": "Mafia Town",
"Cheating the Race": "Mafia Town",
"Heating Up Mafia Town": "Mafia Town",
"The Golden Vault": "Mafia Town",
"Time Rift - Mafia of Cooks": "Mafia Town",
"Time Rift - Sewers": "Mafia Town",
"Time Rift - Bazaar": "Mafia Town",
"Dead Bird Studio": "Battle of the Birds",
"Murder on the Owl Express": "Battle of the Birds",
"Picture Perfect": "Battle of the Birds",
"Train Rush": "Battle of the Birds",
"The Big Parade": "Battle of the Birds",
"Award Ceremony": "Battle of the Birds",
"Dead Bird Studio Basement": "Battle of the Birds",
"Time Rift - Dead Bird Studio": "Battle of the Birds",
"Time Rift - The Owl Express": "Battle of the Birds",
"Time Rift - The Moon": "Battle of the Birds",
"Contractual Obligations": "Subcon Forest",
"The Subcon Well": "Subcon Forest",
"Toilet of Doom": "Subcon Forest",
"Queen Vanessa's Manor": "Subcon Forest",
"Mail Delivery Service": "Subcon Forest",
"Your Contract has Expired": "Subcon Forest",
"Time Rift - Sleepy Subcon": "Subcon Forest",
"Time Rift - Pipe": "Subcon Forest",
"Time Rift - Village": "Subcon Forest",
"Alpine Free Roam": "Alpine Skyline",
"The Illness has Spread": "Alpine Skyline",
"Time Rift - Alpine Skyline": "Alpine Skyline",
"Time Rift - The Twilight Bell": "Alpine Skyline",
"Time Rift - Curly Tail Trail": "Alpine Skyline",
"The Finale": "Time's End",
"Time Rift - Tour": "Time's End",
"Bon Voyage!": "The Arctic Cruise",
"Ship Shape": "The Arctic Cruise",
"Rock the Boat": "The Arctic Cruise",
"Time Rift - Balcony": "The Arctic Cruise",
"Time Rift - Deep Sea": "The Arctic Cruise",
"Nyakuza Free Roam": "Nyakuza Metro",
"Rush Hour": "Nyakuza Metro",
"Time Rift - Rumbi Factory": "Nyakuza Metro",
}
# region: list[Region]
rift_access_regions = {
"Time Rift - Gallery": ["Spaceship"],
"Time Rift - The Lab": ["Spaceship"],
"Time Rift - Sewers": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space",
"Down with the Mafia!", "Cheating the Race", "Heating Up Mafia Town",
"The Golden Vault"],
"Time Rift - Bazaar": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space",
"Down with the Mafia!", "Cheating the Race", "Heating Up Mafia Town",
"The Golden Vault"],
"Time Rift - Mafia of Cooks": ["Welcome to Mafia Town", "Barrel Battle", "She Came from Outer Space",
"Down with the Mafia!", "Cheating the Race", "The Golden Vault"],
"Time Rift - The Owl Express": ["Murder on the Owl Express"],
"Time Rift - The Moon": ["Picture Perfect", "The Big Parade"],
"Time Rift - Dead Bird Studio": ["Dead Bird Studio", "Dead Bird Studio Basement"],
"Time Rift - Pipe": ["Contractual Obligations", "The Subcon Well",
"Toilet of Doom", "Queen Vanessa's Manor",
"Mail Delivery Service"],
"Time Rift - Village": ["Contractual Obligations", "The Subcon Well",
"Toilet of Doom", "Queen Vanessa's Manor",
"Mail Delivery Service"],
"Time Rift - Sleepy Subcon": ["Contractual Obligations", "The Subcon Well",
"Toilet of Doom", "Queen Vanessa's Manor",
"Mail Delivery Service"],
"Time Rift - The Twilight Bell": ["Alpine Free Roam"],
"Time Rift - Curly Tail Trail": ["Alpine Free Roam"],
"Time Rift - Alpine Skyline": ["Alpine Free Roam", "The Illness has Spread"],
"Time Rift - Tour": ["Time's End"],
"Time Rift - Balcony": ["Cruise Ship"],
"Time Rift - Deep Sea": ["Bon Voyage!"],
"Time Rift - Rumbi Factory": ["Nyakuza Free Roam"],
}
# Time piece identifiers to be used in act shuffle
chapter_act_info = {
"Time Rift - Gallery": "Spaceship_WaterRift_Gallery",
"Time Rift - The Lab": "Spaceship_WaterRift_MailRoom",
"Welcome to Mafia Town": "chapter1_tutorial",
"Barrel Battle": "chapter1_barrelboss",
"She Came from Outer Space": "chapter1_cannon_repair",
"Down with the Mafia!": "chapter1_boss",
"Cheating the Race": "harbor_impossible_race",
"Heating Up Mafia Town": "mafiatown_lava",
"The Golden Vault": "mafiatown_goldenvault",
"Time Rift - Mafia of Cooks": "TimeRift_Cave_Mafia",
"Time Rift - Sewers": "TimeRift_Water_Mafia_Easy",
"Time Rift - Bazaar": "TimeRift_Water_Mafia_Hard",
"Dead Bird Studio": "DeadBirdStudio",
"Murder on the Owl Express": "chapter3_murder",
"Picture Perfect": "moon_camerasnap",
"Train Rush": "trainwreck_selfdestruct",
"The Big Parade": "moon_parade",
"Award Ceremony": "award_ceremony",
"Dead Bird Studio Basement": "chapter3_secret_finale",
"Time Rift - Dead Bird Studio": "TimeRift_Cave_BirdBasement",
"Time Rift - The Owl Express": "TimeRift_Water_TWreck_Panels",
"Time Rift - The Moon": "TimeRift_Water_TWreck_Parade",
"Contractual Obligations": "subcon_village_icewall",
"The Subcon Well": "subcon_cave",
"Toilet of Doom": "chapter2_toiletboss",
"Queen Vanessa's Manor": "vanessa_manor_attic",
"Mail Delivery Service": "subcon_maildelivery",
"Your Contract has Expired": "snatcher_boss",
"Time Rift - Sleepy Subcon": "TimeRift_Cave_Raccoon",
"Time Rift - Pipe": "TimeRift_Water_Subcon_Hookshot",
"Time Rift - Village": "TimeRift_Water_Subcon_Dwellers",
"Alpine Free Roam": "AlpineFreeRoam", # not an actual Time Piece
"The Illness has Spread": "AlpineSkyline_Finale",
"Time Rift - Alpine Skyline": "TimeRift_Cave_Alps",
"Time Rift - The Twilight Bell": "TimeRift_Water_Alp_Goats",
"Time Rift - Curly Tail Trail": "TimeRift_Water_AlpineSkyline_Cats",
"The Finale": "TheFinale_FinalBoss",
"Time Rift - Tour": "TimeRift_Cave_Tour",
"Bon Voyage!": "Cruise_Boarding",
"Ship Shape": "Cruise_Working",
"Rock the Boat": "Cruise_Sinking",
"Time Rift - Balcony": "Cruise_WaterRift_Slide",
"Time Rift - Deep Sea": "Cruise_CaveRift_Aquarium",
"Nyakuza Free Roam": "MetroFreeRoam", # not an actual Time Piece
"Rush Hour": "Metro_Escape",
"Time Rift - Rumbi Factory": "Metro_CaveRift_RumbiFactory"
}
# Guarantee that the first level a player can access is a location dense area beatable with no items
guaranteed_first_acts = [
"Welcome to Mafia Town",
"Barrel Battle",
"She Came from Outer Space",
"Down with the Mafia!",
"Heating Up Mafia Town", # Removed in umbrella logic
"The Golden Vault",
"Contractual Obligations", # Removed in painting logic
"Queen Vanessa's Manor", # Removed in umbrella/painting logic
]
purple_time_rifts = [
"Time Rift - Mafia of Cooks",
"Time Rift - Dead Bird Studio",
"Time Rift - Sleepy Subcon",
"Time Rift - Alpine Skyline",
"Time Rift - Deep Sea",
"Time Rift - Tour",
"Time Rift - Rumbi Factory",
]
chapter_finales = [
"Dead Bird Studio Basement",
"Your Contract has Expired",
"The Illness has Spread",
"Rock the Boat",
"Rush Hour",
]
# Acts blacklisted in act shuffle
# entrance: region
blacklisted_acts = {
"Battle of the Birds - Finale A": "Award Ceremony",
}
# Blacklisted act shuffle combinations to help prevent impossible layouts. Mostly for free roam acts.
blacklisted_combos = {
"The Illness has Spread": ["Nyakuza Free Roam", "Alpine Free Roam", "Contractual Obligations"],
"Rush Hour": ["Nyakuza Free Roam", "Alpine Free Roam", "Contractual Obligations"],
"Time Rift - The Owl Express": ["Alpine Free Roam", "Nyakuza Free Roam", "Bon Voyage!",
"Contractual Obligations"],
"Time Rift - The Moon": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"],
"Time Rift - Dead Bird Studio": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"],
"Time Rift - Curly Tail Trail": ["Nyakuza Free Roam", "Contractual Obligations"],
"Time Rift - The Twilight Bell": ["Nyakuza Free Roam", "Contractual Obligations"],
"Time Rift - Alpine Skyline": ["Nyakuza Free Roam", "Contractual Obligations"],
"Time Rift - Rumbi Factory": ["Alpine Free Roam", "Contractual Obligations"],
"Time Rift - Deep Sea": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations"],
}
def create_regions(world: World):
w = world
p = world.player
# ------------------------------------------- HUB -------------------------------------------------- #
menu = create_region(w, "Menu")
spaceship = create_region_and_connect(w, "Spaceship", "Save File -> Spaceship", menu)
# we only need the menu and the spaceship regions
if world.is_dw_only():
return
create_rift_connections(w, create_region(w, "Time Rift - Gallery"))
create_rift_connections(w, create_region(w, "Time Rift - The Lab"))
# ------------------------------------------- MAFIA TOWN ------------------------------------------- #
mafia_town = create_region_and_connect(w, "Mafia Town", "Telescope -> Mafia Town", spaceship)
mt_act1 = create_region_and_connect(w, "Welcome to Mafia Town", "Mafia Town - Act 1", mafia_town)
mt_act2 = create_region_and_connect(w, "Barrel Battle", "Mafia Town - Act 2", mafia_town)
mt_act3 = create_region_and_connect(w, "She Came from Outer Space", "Mafia Town - Act 3", mafia_town)
mt_act4 = create_region_and_connect(w, "Down with the Mafia!", "Mafia Town - Act 4", mafia_town)
mt_act6 = create_region_and_connect(w, "Heating Up Mafia Town", "Mafia Town - Act 6", mafia_town)
mt_act5 = create_region_and_connect(w, "Cheating the Race", "Mafia Town - Act 5", mafia_town)
mt_act7 = create_region_and_connect(w, "The Golden Vault", "Mafia Town - Act 7", mafia_town)
# ------------------------------------------- BOTB ------------------------------------------------- #
botb = create_region_and_connect(w, "Battle of the Birds", "Telescope -> Battle of the Birds", spaceship)
dbs = create_region_and_connect(w, "Dead Bird Studio", "Battle of the Birds - Act 1", botb)
create_region_and_connect(w, "Murder on the Owl Express", "Battle of the Birds - Act 2", botb)
pp = create_region_and_connect(w, "Picture Perfect", "Battle of the Birds - Act 3", botb)
tr = create_region_and_connect(w, "Train Rush", "Battle of the Birds - Act 4", botb)
create_region_and_connect(w, "The Big Parade", "Battle of the Birds - Act 5", botb)
create_region_and_connect(w, "Award Ceremony", "Battle of the Birds - Finale A", botb)
basement = create_region_and_connect(w, "Dead Bird Studio Basement", "Battle of the Birds - Finale B", botb)
create_rift_connections(w, create_region(w, "Time Rift - Dead Bird Studio"))
create_rift_connections(w, create_region(w, "Time Rift - The Owl Express"))
create_rift_connections(w, create_region(w, "Time Rift - The Moon"))
# Items near the Dead Bird Studio elevator can be reached from the basement act, and beyond in Expert
ev_area = create_region_and_connect(w, "Dead Bird Studio - Elevator Area", "DBS -> Elevator Area", dbs)
post_ev_area = create_region_and_connect(w, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs)
connect_regions(basement, ev_area, "DBS Basement -> Elevator Area", p)
if world.options.LogicDifficulty.value >= int(Difficulty.EXPERT):
connect_regions(basement, post_ev_area, "DBS Basement -> Post Elevator Area", p)
# ------------------------------------------- SUBCON FOREST --------------------------------------- #
subcon_forest = create_region_and_connect(w, "Subcon Forest", "Telescope -> Subcon Forest", spaceship)
sf_act1 = create_region_and_connect(w, "Contractual Obligations", "Subcon Forest - Act 1", subcon_forest)
sf_act2 = create_region_and_connect(w, "The Subcon Well", "Subcon Forest - Act 2", subcon_forest)
sf_act3 = create_region_and_connect(w, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest)
sf_act4 = create_region_and_connect(w, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest)
sf_act5 = create_region_and_connect(w, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest)
create_region_and_connect(w, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
# ------------------------------------------- ALPINE SKYLINE ------------------------------------------ #
alpine_skyline = create_region_and_connect(w, "Alpine Skyline", "Telescope -> Alpine Skyline", spaceship)
alpine_freeroam = create_region_and_connect(w, "Alpine Free Roam", "Alpine Skyline - Free Roam", alpine_skyline)
alpine_area = create_region_and_connect(w, "Alpine Skyline Area", "AFR -> Alpine Skyline Area", alpine_freeroam)
# Needs to be separate because there are a lot of locations in Alpine that can't be accessed from Illness
alpine_area_tihs = create_region_and_connect(w, "Alpine Skyline Area (TIHS)", "-> Alpine Skyline Area (TIHS)",
alpine_area)
create_region_and_connect(w, "The Birdhouse", "-> The Birdhouse", alpine_area)
create_region_and_connect(w, "The Lava Cake", "-> The Lava Cake", alpine_area)
create_region_and_connect(w, "The Windmill", "-> The Windmill", alpine_area)
create_region_and_connect(w, "The Twilight Bell", "-> The Twilight Bell", alpine_area)
illness = create_region_and_connect(w, "The Illness has Spread", "Alpine Skyline - Finale", alpine_skyline)
connect_regions(illness, alpine_area_tihs, "TIHS -> Alpine Skyline Area (TIHS)", p)
create_rift_connections(w, create_region(w, "Time Rift - Alpine Skyline"))
create_rift_connections(w, create_region(w, "Time Rift - The Twilight Bell"))
create_rift_connections(w, create_region(w, "Time Rift - Curly Tail Trail"))
# ------------------------------------------- OTHER -------------------------------------------------- #
mt_area: Region = create_region(w, "Mafia Town Area")
mt_area_humt: Region = create_region(w, "Mafia Town Area (HUMT)")
connect_regions(mt_area, mt_area_humt, "MT Area -> MT Area (HUMT)", p)
connect_regions(mt_act1, mt_area, "Mafia Town Entrance WTMT", p)
connect_regions(mt_act2, mt_area, "Mafia Town Entrance BB", p)
connect_regions(mt_act3, mt_area, "Mafia Town Entrance SCFOS", p)
connect_regions(mt_act4, mt_area, "Mafia Town Entrance DWTM", p)
connect_regions(mt_act5, mt_area, "Mafia Town Entrance CTR", p)
connect_regions(mt_act6, mt_area_humt, "Mafia Town Entrance HUMT", p)
connect_regions(mt_act7, mt_area, "Mafia Town Entrance TGV", p)
create_rift_connections(w, create_region(w, "Time Rift - Mafia of Cooks"))
create_rift_connections(w, create_region(w, "Time Rift - Sewers"))
create_rift_connections(w, create_region(w, "Time Rift - Bazaar"))
sf_area: Region = create_region(w, "Subcon Forest Area")
connect_regions(sf_act1, sf_area, "Subcon Forest Entrance CO", p)
connect_regions(sf_act2, sf_area, "Subcon Forest Entrance SW", p)
connect_regions(sf_act3, sf_area, "Subcon Forest Entrance TOD", p)
connect_regions(sf_act4, sf_area, "Subcon Forest Entrance QVM", p)
connect_regions(sf_act5, sf_area, "Subcon Forest Entrance MDS", p)
create_rift_connections(w, create_region(w, "Time Rift - Sleepy Subcon"))
create_rift_connections(w, create_region(w, "Time Rift - Pipe"))
create_rift_connections(w, create_region(w, "Time Rift - Village"))
badge_seller = create_badge_seller(w)
connect_regions(mt_area, badge_seller, "MT Area -> Badge Seller", p)
connect_regions(mt_area_humt, badge_seller, "MT Area (HUMT) -> Badge Seller", p)
connect_regions(sf_area, badge_seller, "SF Area -> Badge Seller", p)
connect_regions(dbs, badge_seller, "DBS -> Badge Seller", p)
connect_regions(pp, badge_seller, "PP -> Badge Seller", p)
connect_regions(tr, badge_seller, "TR -> Badge Seller", p)
connect_regions(alpine_area_tihs, badge_seller, "ASA -> Badge Seller", p)
times_end = create_region_and_connect(w, "Time's End", "Telescope -> Time's End", spaceship)
create_region_and_connect(w, "The Finale", "Time's End - Act 1", times_end)
# ------------------------------------------- DLC1 ------------------------------------------------- #
if w.is_dlc1():
arctic_cruise = create_region_and_connect(w, "The Arctic Cruise", "Telescope -> The Arctic Cruise", spaceship)
cruise_ship = create_region(w, "Cruise Ship")
ac_act1 = create_region_and_connect(w, "Bon Voyage!", "The Arctic Cruise - Act 1", arctic_cruise)
ac_act2 = create_region_and_connect(w, "Ship Shape", "The Arctic Cruise - Act 2", arctic_cruise)
ac_act3 = create_region_and_connect(w, "Rock the Boat", "The Arctic Cruise - Finale", arctic_cruise)
connect_regions(ac_act1, cruise_ship, "Cruise Ship Entrance BV", p)
connect_regions(ac_act2, cruise_ship, "Cruise Ship Entrance SS", p)
connect_regions(ac_act3, cruise_ship, "Cruise Ship Entrance RTB", p)
create_rift_connections(w, create_region(w, "Time Rift - Balcony"))
create_rift_connections(w, create_region(w, "Time Rift - Deep Sea"))
if w.options.ExcludeTour.value == 0:
create_rift_connections(w, create_region(w, "Time Rift - Tour"))
if w.options.Tasksanity.value > 0:
create_tasksanity_locations(w)
connect_regions(cruise_ship, badge_seller, "CS -> Badge Seller", p)
if w.is_dlc2():
nyakuza_metro = create_region_and_connect(w, "Nyakuza Metro", "Telescope -> Nyakuza Metro", spaceship)
metro_freeroam = create_region_and_connect(w, "Nyakuza Free Roam", "Nyakuza Metro - Free Roam", nyakuza_metro)
create_region_and_connect(w, "Rush Hour", "Nyakuza Metro - Finale", nyakuza_metro)
yellow = create_region_and_connect(w, "Yellow Overpass Station", "-> Yellow Overpass Station", metro_freeroam)
green = create_region_and_connect(w, "Green Clean Station", "-> Green Clean Station", metro_freeroam)
pink = create_region_and_connect(w, "Pink Paw Station", "-> Pink Paw Station", metro_freeroam)
create_region_and_connect(w, "Bluefin Tunnel", "-> Bluefin Tunnel", metro_freeroam) # No manhole
create_region_and_connect(w, "Yellow Overpass Manhole", "-> Yellow Overpass Manhole", yellow)
create_region_and_connect(w, "Green Clean Manhole", "-> Green Clean Manhole", green)
create_region_and_connect(w, "Pink Paw Manhole", "-> Pink Paw Manhole", pink)
create_rift_connections(w, create_region(w, "Time Rift - Rumbi Factory"))
create_thug_shops(w)
def create_rift_connections(world: World, region: Region):
i = 1
for name in rift_access_regions[region.name]:
act_region = world.multiworld.get_region(name, world.player)
entrance_name = f"{region.name} Portal - Entrance {i}"
connect_regions(act_region, region, entrance_name, world.player)
i += 1
# fix for some weird keyerror from tests
if region.name == "Time Rift - Rumbi Factory":
for entrance in region.entrances:
world.multiworld.get_entrance(entrance.name, world.player)
def create_tasksanity_locations(world: World):
ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player)
id_start: int = get_tasksanity_start_id()
for i in range(world.options.TasksanityCheckCount.value):
location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape)
ship_shape.locations.append(location)
def is_valid_plando(world: World, region: str) -> bool:
if region in blacklisted_acts.values():
return False
if region not in world.options.ActPlando.keys():
return False
act = world.options.ActPlando.get(region)
if act in blacklisted_acts.values():
return False
# Don't allow plando-ing things onto the first act that aren't completable with nothing
is_first_act: bool = act_chapters[region] == get_first_chapter_region(world).name \
and region in act_entrances.keys() and ("Act 1" in act_entrances[region] or "Free Roam" in act_entrances[region])
if is_first_act:
if act_chapters[act] == "Subcon Forest" and world.options.ShuffleSubconPaintings.value > 0:
return False
if world.options.UmbrellaLogic.value > 0 \
and (act == "Heating Up Mafia Town" or act == "Queen Vanessa's Manor"):
return False
if act not in guaranteed_first_acts:
return False
# Don't allow straight up impossible mappings
if region == "The Illness has Spread" and act == "Alpine Free Roam":
return False
if region == "Rush Hour" and act == "Nyakuza Free Roam":
return False
if region == "Time Rift - Rumbi Factory" and act == "Nyakuza Free Roam":
return False
if region == "Time Rift - The Owl Express" and act == "Murder on the Owl Express":
return False
return any(a.name == world.options.ActPlando.get(region) for a in
world.multiworld.get_regions(world.player))
def randomize_act_entrances(world: World):
region_list: typing.List[Region] = get_act_regions(world)
world.random.shuffle(region_list)
separate_rifts: bool = bool(world.options.ActRandomizer.value == 1)
for region in region_list.copy():
if (act_chapters[region.name] == "Alpine Skyline" or act_chapters[region.name] == "Nyakuza Metro") \
and "Time Rift" not in region.name:
region_list.remove(region)
region_list.append(region)
for region in region_list.copy():
if "Time Rift" in region.name:
region_list.remove(region)
region_list.append(region)
for region in region_list.copy():
if region.name in chapter_finales:
region_list.remove(region)
region_list.append(region)
for region in region_list.copy():
if region.name in world.options.ActPlando.keys():
if is_valid_plando(world, region.name):
region_list.remove(region)
region_list.append(region)
else:
print("Disallowing act plando for",
world.multiworld.player_name[world.player],
"-", region.name, ":", world.options.ActPlando.get(region.name))
# Reverse the list, so we can do what we want to do first
region_list.reverse()
shuffled_list: typing.List[Region] = []
mapped_list: typing.List[Region] = []
rift_dict: typing.Dict[str, Region] = {}
first_chapter: Region = get_first_chapter_region(world)
has_guaranteed: bool = False
i: int = 0
while i < len(region_list):
region = region_list[i]
i += 1
# Get the first accessible act, so we can map that to something first
if not has_guaranteed:
if act_chapters[region.name] != first_chapter.name:
continue
if region.name not in act_entrances.keys() or "Act 1" not in act_entrances[region.name] \
and "Free Roam" not in act_entrances[region.name]:
continue
if region.name in world.options.ActPlando.keys() and is_valid_plando(world, region.name):
has_guaranteed = True
i = 0
# Already mapped to something else
if region in mapped_list:
continue
mapped_list.append(region)
# Look for candidates to map this act to
candidate_list: typing.List[Region] = []
for candidate in region_list:
# We're mapping something to the first act, make sure it is valid
if not has_guaranteed:
if candidate.name not in guaranteed_first_acts:
continue
if candidate.name in world.options.ActPlando.values():
continue
# Not completable without Umbrella
if world.options.UmbrellaLogic.value > 0 \
and (candidate.name == "Heating Up Mafia Town" or candidate.name == "Queen Vanessa's Manor"):
continue
# Subcon sphere 1 is too small without painting unlocks, and no acts are completable either
if world.options.ShuffleSubconPaintings.value > 0 \
and "Subcon Forest" in act_entrances[candidate.name]:
continue
candidate_list.append(candidate)
has_guaranteed = True
break
if region.name in world.options.ActPlando.keys() and is_valid_plando(world, region.name):
candidate_list.clear()
candidate_list.append(
world.multiworld.get_region(world.options.ActPlando.get(region.name), world.player))
break
# Already mapped onto something else
if candidate in shuffled_list:
continue
if separate_rifts:
# Don't map Time Rifts to normal acts
if "Time Rift" in region.name and "Time Rift" not in candidate.name:
continue
# Don't map normal acts to Time Rifts
if "Time Rift" not in region.name and "Time Rift" in candidate.name:
continue
# Separate purple rifts
if region.name in purple_time_rifts and candidate.name not in purple_time_rifts \
or region.name not in purple_time_rifts and candidate.name in purple_time_rifts:
continue
if region.name in blacklisted_combos.keys() and candidate.name in blacklisted_combos[region.name]:
continue
# Prevent Contractual Obligations from being inaccessible if contracts are not shuffled
if world.options.ShuffleActContracts.value == 0:
if (region.name == "Your Contract has Expired" or region.name == "The Subcon Well") \
and candidate.name == "Contractual Obligations":
continue
if world.options.FinaleShuffle.value > 0 and region.name in chapter_finales:
if candidate.name not in chapter_finales:
continue
if region.name in rift_access_regions and candidate.name in rift_access_regions[region.name]:
continue
candidate_list.append(candidate)
candidate: Region
if len(candidate_list) > 0:
candidate = candidate_list[world.random.randint(0, len(candidate_list)-1)]
else:
# plando can still break certain rules, so acts may not always end up shuffled.
for c in region_list:
if c not in shuffled_list:
candidate = c
break
shuffled_list.append(candidate)
# print(region, candidate)
# Vanilla
if candidate.name == region.name:
if region.name in rift_access_regions.keys():
rift_dict.setdefault(region.name, candidate)
update_chapter_act_info(world, region, candidate)
continue
if region.name in rift_access_regions.keys():
connect_time_rift(world, region, candidate)
rift_dict.setdefault(region.name, candidate)
else:
if candidate.name in rift_access_regions.keys():
for e in candidate.entrances.copy():
e.parent_region.exits.remove(e)
e.connected_region.entrances.remove(e)
entrance = world.multiworld.get_entrance(act_entrances[region.name], world.player)
reconnect_regions(entrance, world.multiworld.get_region(act_chapters[region.name], world.player), candidate)
update_chapter_act_info(world, region, candidate)
for name in blacklisted_acts.values():
if not is_act_blacklisted(world, name):
continue
region: Region = world.multiworld.get_region(name, world.player)
update_chapter_act_info(world, region, region)
set_rift_rules(world, rift_dict)
def connect_time_rift(world: World, time_rift: Region, exit_region: Region):
count: int = len(rift_access_regions[time_rift.name])
i: int = 1
while i <= count:
name = f"{time_rift.name} Portal - Entrance {i}"
entrance: Entrance = world.multiworld.get_entrance(name, world.player)
reconnect_regions(entrance, entrance.parent_region, exit_region)
i += 1
def get_act_regions(world: World) -> typing.List[Region]:
act_list: typing.List[Region] = []
for region in world.multiworld.get_regions(world.player):
if region.name in chapter_act_info.keys():
if not is_act_blacklisted(world, region.name):
act_list.append(region)
return act_list
def is_act_blacklisted(world: World, name: str) -> bool:
plando: bool = name in world.options.ActPlando.keys() \
or name in world.options.ActPlando.values()
if name == "The Finale":
return not plando and world.options.EndGoal.value == 1
if name == "Rush Hour":
return not plando and world.options.EndGoal.value == 2
if name == "Time Rift - Tour":
return world.options.ExcludeTour.value > 0
return name in blacklisted_acts.values()
def create_region(world: World, name: str) -> Region:
reg = Region(name, world.player, world.multiworld)
for (key, data) in location_table.items():
if world.is_dw_only():
break
if data.nyakuza_thug != "":
continue
if data.region == name:
if key in storybook_pages.keys() \
and world.options.ShuffleStorybookPages.value == 0:
continue
location = HatInTimeLocation(world.player, key, data.id, reg)
reg.locations.append(location)
if location.name in shop_locations:
world.shop_locs.append(location.name)
world.multiworld.regions.append(reg)
return reg
def create_badge_seller(world: World) -> Region:
badge_seller = Region("Badge Seller", world.player, world.multiworld)
world.multiworld.regions.append(badge_seller)
count: int = 0
max_items: int = 0
if world.options.BadgeSellerMaxItems.value > 0:
max_items = world.random.randint(world.options.BadgeSellerMinItems.value,
world.options.BadgeSellerMaxItems.value)
if max_items <= 0:
world.set_badge_seller_count(0)
return badge_seller
for (key, data) in shop_locations.items():
if "Badge Seller" not in key:
continue
location = HatInTimeLocation(world.player, key, data.id, badge_seller)
badge_seller.locations.append(location)
world.shop_locs.append(location.name)
count += 1
if count >= max_items:
break
world.set_badge_seller_count(max_items)
return badge_seller
def connect_regions(start_region: Region, exit_region: Region, entrancename: str, player: int) -> Entrance:
entrance = Entrance(player, entrancename, start_region)
start_region.exits.append(entrance)
entrance.connect(exit_region)
return entrance
# Takes an entrance, removes its old connections, and reconnects it between the two regions specified.
def reconnect_regions(entrance: Entrance, start_region: Region, exit_region: Region):
if entrance in entrance.connected_region.entrances:
entrance.connected_region.entrances.remove(entrance)
if entrance in entrance.parent_region.exits:
entrance.parent_region.exits.remove(entrance)
if entrance in start_region.exits:
start_region.exits.remove(entrance)
if entrance in exit_region.entrances:
exit_region.entrances.remove(entrance)
entrance.parent_region = start_region
start_region.exits.append(entrance)
entrance.connect(exit_region)
def create_region_and_connect(world: World,
name: str, entrancename: str, connected_region: Region, is_exit: bool = True) -> Region:
reg: Region = create_region(world, name)
entrance_region: Region
exit_region: Region
if is_exit:
entrance_region = connected_region
exit_region = reg
else:
entrance_region = reg
exit_region = connected_region
connect_regions(entrance_region, exit_region, entrancename, world.player)
return reg
def get_first_chapter_region(world: World) -> Region:
start_chapter: ChapterIndex = world.options.StartingChapter.value
return world.multiworld.get_region(chapter_regions.get(start_chapter), world.player)
def get_act_original_chapter(world: World, act_name: str) -> Region:
return world.multiworld.get_region(act_chapters[act_name], world.player)
# Sets an act entrance in slot data by specifying the Hat_ChapterActInfo, to be used in-game
def update_chapter_act_info(world: World, original_region: Region, new_region: Region):
original_act_info = chapter_act_info[original_region.name]
new_act_info = chapter_act_info[new_region.name]
world.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 create_thug_shops(world: World):
min_items: int = min(world.options.NyakuzaThugMinShopItems.value,
world.options.NyakuzaThugMaxShopItems.value)
max_items: int = max(world.options.NyakuzaThugMaxShopItems.value,
world.options.NyakuzaThugMinShopItems.value)
count: int = -1
step: int = 0
old_name: str = ""
thug_items = world.get_nyakuza_thug_items()
for key, data in shop_locations.items():
if data.nyakuza_thug == "":
continue
if old_name != "" and old_name == data.nyakuza_thug:
continue
try:
if thug_items[data.nyakuza_thug] <= 0:
continue
except KeyError:
pass
if count == -1:
count = world.random.randint(min_items, max_items)
thug_items.setdefault(data.nyakuza_thug, count)
if count <= 0:
continue
if count >= 1:
region = world.multiworld.get_region(data.region, world.player)
loc = HatInTimeLocation(world.player, key, data.id, region)
region.locations.append(loc)
world.shop_locs.append(loc.name)
step += 1
if step >= count:
old_name = data.nyakuza_thug
step = 0
count = -1
world.set_nyakuza_thug_items(thug_items)
def create_events(world: World) -> int:
count: int = 0
for (name, data) in event_locs.items():
if not is_location_valid(world, name):
continue
if world.is_dw():
if name in snatcher_coins.keys():
name = f"{name} ({data.region})"
elif name in zero_jumps:
if get_difficulty(world) < Difficulty.HARD and name in zero_jumps_hard:
continue
if get_difficulty(world) < Difficulty.EXPERT and name in zero_jumps_expert:
continue
event: Location = create_event(name, world.multiworld.get_region(data.region, world.player), world)
event.show_in_spoiler = False
count += 1
return count
def create_event(name: str, region: Region, world: World) -> Location:
event = HatInTimeLocation(world.player, name, None, region)
region.locations.append(event)
event.place_locked_item(HatInTimeItem(name, ItemClassification.progression, None, world.player))
return event