mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-23 20:43:18 -07:00
1.0 Preparations
This commit is contained in:
@@ -1,52 +1,240 @@
|
||||
from .Locations import HatInTimeLocation, death_wishes
|
||||
from .Regions import connect_regions, create_region
|
||||
from BaseClasses import Region, LocationProgressType
|
||||
from worlds.generic.Rules import add_rule
|
||||
from worlds.AutoWorld import World
|
||||
from typing import List
|
||||
|
||||
death_wishes = [
|
||||
"Beat the Heat",
|
||||
"So You're Back From Outer Space",
|
||||
"Mafia's Jumps",
|
||||
"Collect-a-thon",
|
||||
"She Speedran from Outerspace",
|
||||
"Vault Codes in the Wind",
|
||||
"Encore! Encore!",
|
||||
"Rift Collapse: Mafia of Cooks",
|
||||
dw_prereqs = {
|
||||
"So You're Back From Outer Space": ["Beat the Heat"],
|
||||
"Snatcher's Hit List": ["Beat the Heat"],
|
||||
"Snatcher Coins in Mafia Town": ["So You're Back From Outer Space"],
|
||||
"Rift Collapse: Mafia of Cooks": ["So You're Back From Outer Space"],
|
||||
"Collect-a-thon": ["So You're Back From Outer Space"],
|
||||
"She Speedran from Outer Space": ["Rift Collapse: Mafia of Cooks"],
|
||||
"Mafia's Jumps": ["She Speedran from Outer Space"],
|
||||
"Vault Codes in the Wind": ["Collect-a-thon", "She Speedran from Outer Space"],
|
||||
"Encore! Encore!": ["Collect-a-thon"],
|
||||
|
||||
"Security Breach": ["Beat the Heat"],
|
||||
"Rift Collapse: Dead Bird Studio": ["Security Breach"],
|
||||
"The Great Big Hootenanny": ["Security Breach"],
|
||||
"10 Seconds until Self-Destruct": ["The Great Big Hootenanny"],
|
||||
"Killing Two Birds": ["Rift Collapse: Dead Bird Studio", "10 Seconds until Self-Destruct"],
|
||||
"Community Rift: Rhythm Jump Studio": ["10 Seconds until Self-Destruct"],
|
||||
"Snatcher Coins in Battle of the Birds": ["The Great Big Hootenanny"],
|
||||
"Zero Jumps": ["Rift Collapse: Dead Bird Studio"],
|
||||
"Snatcher Coins in Nyakuza Metro": ["Killing Two Birds"],
|
||||
|
||||
"Speedrun Well": ["Beat the Heat"],
|
||||
"Rift Collapse: Sleepy Subcon": ["Speedrun Well"],
|
||||
"Boss Rush": ["Speedrun Well"],
|
||||
"Quality Time with Snatcher": ["Rift Collapse: Sleepy Subcon"],
|
||||
"Breaching the Contract": ["Boss Rush", "Quality Time with Snatcher"],
|
||||
"Community Rift: Twilight Travels": ["Quality Time with Snatcher"],
|
||||
"Snatcher Coins in Subcon Forest": ["Rift Collapse: Sleepy Subcon"],
|
||||
|
||||
"Bird Sanctuary": ["Beat the Heat"],
|
||||
"Snatcher Coins in Alpine Skyline": ["Bird Sanctuary"],
|
||||
"Wound-Up Windmill": ["Bird Sanctuary"],
|
||||
"Rift Collapse: Alpine Skyline": ["Bird Sanctuary"],
|
||||
"Camera Tourist": ["Rift Collapse: Alpine Skyline"],
|
||||
"Community Rift: The Mountain Rift": ["Rift Collapse: Alpine Skyline"],
|
||||
"The Illness has Speedrun": ["Rift Collapse: Alpine Skyline", "Wound-Up Windmill"],
|
||||
|
||||
"The Mustache Gauntlet": ["Wound-Up Windmill"],
|
||||
"No More Bad Guys": ["The Mustache Gauntlet"],
|
||||
"Seal the Deal": ["Encore! Encore!", "Killing Two Birds",
|
||||
"Breaching the Contract", "No More Bad Guys"],
|
||||
|
||||
"Rift Collapse: Deep Sea": ["Rift Collapse: Mafia of Cooks", "Rift Collapse: Dead Bird Studio",
|
||||
"Rift Collapse: Sleepy Subcon", "Rift Collapse: Alpine Skyline"],
|
||||
|
||||
"Cruisin' for a Bruisin'": ["Rift Collapse: Deep Sea"],
|
||||
}
|
||||
|
||||
dw_candles = [
|
||||
"Snatcher's Hit List",
|
||||
"Snatcher Coins in Mafia Town"
|
||||
|
||||
"Security Breach",
|
||||
"10 Seconds until Self-Destruct",
|
||||
"The Great Big Hootenanny",
|
||||
"Killing Two Birds",
|
||||
"Rift Collapse: Dead Bird Studio",
|
||||
"Snatcher Coins in Battle of the Birds",
|
||||
"Zero Jumps",
|
||||
|
||||
"Speedrun Well",
|
||||
"Boss Rush",
|
||||
"Quality Time with Snatcher",
|
||||
"Breaching the Contract",
|
||||
"Rift Collapse: Sleepy Subcon",
|
||||
"Snatcher Coins in Subcon Forest",
|
||||
|
||||
"Bird Sanctuary",
|
||||
"Wound-Up Windmill",
|
||||
"The Illness has Speedrun",
|
||||
"Rift Collapse: Alpine Skyline",
|
||||
"Snatcher Coins in Alpine Skyline",
|
||||
"Camera Tourist",
|
||||
|
||||
"The Mustache Gauntlet",
|
||||
"No More Bad Guys",
|
||||
|
||||
"Seal the Deal",
|
||||
"Rift Collapse: Deep Sea",
|
||||
"Cruisin' for a Bruisin'",
|
||||
|
||||
"Community Map: Rhythm Jump Studio",
|
||||
"Community Map: Twilight Travels",
|
||||
"Community Map: The Mountain Rift",
|
||||
"Snatcher Coins in Mafia Town",
|
||||
"Snatcher Coins in Battle of the Birds",
|
||||
"Snatcher Coins in Subcon Forest",
|
||||
"Snatcher Coins in Alpine Skyline",
|
||||
"Snatcher Coins in Nyakuza Metro",
|
||||
]
|
||||
|
||||
dw_prereqs = {
|
||||
"Snatcher's Hit List": ["Beat the Heat"],
|
||||
annoying_dws = [
|
||||
"Vault Codes in the Wind",
|
||||
"Boss Rush",
|
||||
"Camera Tourist",
|
||||
"The Mustache Gauntlet",
|
||||
"Rift Collapse: Deep Sea",
|
||||
"Cruisin' for a Bruisin'",
|
||||
"Seal the Deal", # Non-excluded if goal
|
||||
]
|
||||
|
||||
}
|
||||
# includes the above as well
|
||||
annoying_bonuses = [
|
||||
"So You're Back From Outer Space",
|
||||
"Encore! Encore!",
|
||||
"Snatcher's Hit List",
|
||||
"10 Seconds until Self-Destruct",
|
||||
"Killing Two Birds",
|
||||
"Snatcher Coins in Battle of the Birds",
|
||||
"Zero Jumps",
|
||||
"Bird Sanctuary",
|
||||
"Wound-Up Windmill",
|
||||
"Snatcher Coins in Alpine Skyline",
|
||||
"Seal the Deal",
|
||||
]
|
||||
|
||||
dw_classes = {
|
||||
"Beat the Heat": "Hat_SnatcherContract_DeathWish_HeatingUpHarder",
|
||||
"So You're Back From Outer Space": "Hat_SnatcherContract_DeathWish_BackFromSpace",
|
||||
"Snatcher's Hit List": "Hat_SnatcherContract_DeathWish_KillEverybody",
|
||||
"Collect-a-thon": "Hat_SnatcherContract_DeathWish_PonFrenzy",
|
||||
"Rift Collapse: Mafia of Cooks": "Hat_SnatcherContract_DeathWish_RiftCollapse_MafiaTown",
|
||||
"Encore! Encore!": "Hat_SnatcherContract_DeathWish_MafiaBossEX",
|
||||
"She Speedran from Outer Space": "Hat_SnatcherContract_DeathWish_Speedrun_MafiaAlien",
|
||||
"Mafia's Jumps": "Hat_SnatcherContract_DeathWish_NoAPresses_MafiaAlien",
|
||||
"Vault Codes in the Wind": "Hat_SnatcherContract_DeathWish_MovingVault",
|
||||
"Snatcher Coins in Mafia Town": "Hat_SnatcherContract_DeathWish_Tokens_MafiaTown",
|
||||
|
||||
"Security Breach": "Hat_SnatcherContract_DeathWish_DeadBirdStudioMoreGuards",
|
||||
"The Great Big Hootenanny": "Hat_SnatcherContract_DeathWish_DifficultParade",
|
||||
"Rift Collapse: Dead Bird Studio": "Hat_SnatcherContract_DeathWish_RiftCollapse_Birds",
|
||||
"10 Seconds until Self-Destruct": "Hat_SnatcherContract_DeathWish_TrainRushShortTime",
|
||||
"Killing Two Birds": "Hat_SnatcherContract_DeathWish_BirdBossEX",
|
||||
"Snatcher Coins in Battle of the Birds": "Hat_SnatcherContract_DeathWish_Tokens_Birds",
|
||||
"Zero Jumps": "Hat_SnatcherContract_DeathWish_NoAPresses",
|
||||
|
||||
"Speedrun Well": "Hat_SnatcherContract_DeathWish_Speedrun_SubWell",
|
||||
"Rift Collapse: Sleepy Subcon": "Hat_SnatcherContract_DeathWish_RiftCollapse_Subcon",
|
||||
"Boss Rush": "Hat_SnatcherContract_DeathWish_BossRush",
|
||||
"Quality Time with Snatcher": "Hat_SnatcherContract_DeathWish_SurvivalOfTheFittest",
|
||||
"Breaching the Contract": "Hat_SnatcherContract_DeathWish_SnatcherEX",
|
||||
"Snatcher Coins in Subcon Forest": "Hat_SnatcherContract_DeathWish_Tokens_Subcon",
|
||||
|
||||
"Bird Sanctuary": "Hat_SnatcherContract_DeathWish_NiceBirdhouse",
|
||||
"Rift Collapse: Alpine Skyline": "Hat_SnatcherContract_DeathWish_RiftCollapse_Alps",
|
||||
"Wound-Up Windmill": "Hat_SnatcherContract_DeathWish_FastWindmill",
|
||||
"The Illness has Speedrun": "Hat_SnatcherContract_DeathWish_Speedrun_Illness",
|
||||
"Snatcher Coins in Alpine Skyline": "Hat_SnatcherContract_DeathWish_Tokens_Alps",
|
||||
"Camera Tourist": "Hat_SnatcherContract_DeathWish_CameraTourist_1",
|
||||
|
||||
"The Mustache Gauntlet": "Hat_SnatcherContract_DeathWish_HardCastle",
|
||||
"No More Bad Guys": "Hat_SnatcherContract_DeathWish_MuGirlEX",
|
||||
|
||||
"Seal the Deal": "Hat_SnatcherContract_DeathWish_BossRushEX",
|
||||
"Rift Collapse: Deep Sea": "Hat_SnatcherContract_DeathWish_RiftCollapse_Cruise",
|
||||
"Cruisin' for a Bruisin'": "Hat_SnatcherContract_DeathWish_EndlessTasks",
|
||||
|
||||
"Community Rift: Rhythm Jump Studio": "Hat_SnatcherContract_DeathWish_CommunityRift_RhythmJump",
|
||||
"Community Rift: Twilight Travels": "Hat_SnatcherContract_DeathWish_CommunityRift_TwilightTravels",
|
||||
"Community Rift: The Mountain Rift": "Hat_SnatcherContract_DeathWish_CommunityRift_MountainRift",
|
||||
|
||||
"Snatcher Coins in Nyakuza Metro": "Hat_SnatcherContract_DeathWish_Tokens_Metro",
|
||||
}
|
||||
|
||||
|
||||
def create_dw_regions(world: World):
|
||||
if world.multiworld.DWExcludeAnnoyingContracts[world.player].value > 0:
|
||||
for name in annoying_dws:
|
||||
world.get_excluded_dws().append(name)
|
||||
|
||||
if world.multiworld.DWEnableBonus[world.player].value == 0 \
|
||||
or world.multiworld.DWAutoCompleteBonuses[world.player].value > 0:
|
||||
for name in death_wishes:
|
||||
world.get_excluded_bonuses().append(name)
|
||||
elif world.multiworld.DWExcludeAnnoyingBonuses[world.player].value > 0:
|
||||
for name in annoying_bonuses:
|
||||
world.get_excluded_bonuses().append(name)
|
||||
|
||||
if world.multiworld.DWExcludeCandles[world.player].value > 0:
|
||||
for name in dw_candles:
|
||||
if name in world.get_excluded_dws():
|
||||
continue
|
||||
world.get_excluded_dws().append(name)
|
||||
|
||||
spaceship = world.multiworld.get_region("Spaceship", world.player)
|
||||
dw_map: Region = create_region(world, "Death Wish Map")
|
||||
entrance = connect_regions(spaceship, dw_map, "-> Death Wish Map", world.player)
|
||||
|
||||
add_rule(entrance, lambda state: state.has("Time Piece", world.player,
|
||||
world.multiworld.DWTimePieceRequirement[world.player].value))
|
||||
|
||||
if world.multiworld.DWShuffle[world.player].value > 0:
|
||||
dw_list: List[str] = []
|
||||
for name in death_wishes.keys():
|
||||
if not world.is_dlc2() and name == "Snatcher Coins in Nyakuza Metro" or world.is_dw_excluded(name):
|
||||
continue
|
||||
|
||||
dw_list.append(name)
|
||||
|
||||
world.random.shuffle(dw_list)
|
||||
count = world.random.randint(world.multiworld.DWShuffleCountMin[world.player].value,
|
||||
world.multiworld.DWShuffleCountMax[world.player].value)
|
||||
|
||||
dw_shuffle: List[str] = []
|
||||
total = min(len(dw_list), count)
|
||||
for i in range(total):
|
||||
dw_shuffle.append(dw_list[i])
|
||||
|
||||
# Seal the Deal is always last if it's the goal
|
||||
if world.multiworld.EndGoal[world.player].value == 3:
|
||||
if "Seal the Deal" in dw_shuffle:
|
||||
dw_shuffle.remove("Seal the Deal")
|
||||
|
||||
dw_shuffle.append("Seal the Deal")
|
||||
|
||||
world.set_dw_shuffle(dw_shuffle)
|
||||
prev_dw: Region
|
||||
for i in range(len(dw_shuffle)):
|
||||
name = dw_shuffle[i]
|
||||
dw = create_region(world, name)
|
||||
|
||||
if i == 0:
|
||||
connect_regions(dw_map, dw, f"-> {name}", world.player)
|
||||
else:
|
||||
connect_regions(prev_dw, dw, f"{prev_dw.name} -> {name}", world.player)
|
||||
|
||||
loc_id = death_wishes[name]
|
||||
main_objective = HatInTimeLocation(world.player, f"{name} - Main Objective", loc_id, dw)
|
||||
full_clear = HatInTimeLocation(world.player, f"{name} - All Clear", loc_id + 1, dw)
|
||||
|
||||
if name in world.get_excluded_bonuses():
|
||||
main_objective.progress_type = LocationProgressType.EXCLUDED
|
||||
full_clear.progress_type = LocationProgressType.EXCLUDED
|
||||
elif world.is_bonus_excluded(name):
|
||||
full_clear.progress_type = LocationProgressType.EXCLUDED
|
||||
|
||||
dw.locations.append(main_objective)
|
||||
dw.locations.append(full_clear)
|
||||
prev_dw = dw
|
||||
else:
|
||||
for key, loc_id in death_wishes.items():
|
||||
if key == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2():
|
||||
world.get_excluded_dws().append(key)
|
||||
continue
|
||||
|
||||
dw = create_region(world, key)
|
||||
|
||||
if key == "Beat the Heat":
|
||||
connect_regions(dw_map, dw, "-> Beat the Heat", world.player)
|
||||
elif key in dw_prereqs.keys():
|
||||
for name in dw_prereqs[key]:
|
||||
parent = world.multiworld.get_region(name, world.player)
|
||||
connect_regions(parent, dw, f"{parent.name} -> {key}", world.player)
|
||||
|
||||
main_objective = HatInTimeLocation(world.player, f"{key} - Main Objective", loc_id, dw)
|
||||
full_clear = HatInTimeLocation(world.player, f"{key} - All Clear", loc_id+1, dw)
|
||||
|
||||
if key in world.get_excluded_bonuses():
|
||||
main_objective.progress_type = LocationProgressType.EXCLUDED
|
||||
full_clear.progress_type = LocationProgressType.EXCLUDED
|
||||
elif world.is_bonus_excluded(key):
|
||||
full_clear.progress_type = LocationProgressType.EXCLUDED
|
||||
|
||||
dw.locations.append(main_objective)
|
||||
dw.locations.append(full_clear)
|
||||
@@ -0,0 +1,491 @@
|
||||
from worlds.AutoWorld import World, CollectionState
|
||||
from .Locations import LocData, death_wishes, HatInTimeLocation
|
||||
from .Rules import can_use_hat, can_use_hookshot, can_hit, zipline_logic, has_paintings
|
||||
from .Types import HatType
|
||||
from .DeathWishLocations import dw_prereqs, dw_candles
|
||||
from .Items import HatInTimeItem
|
||||
from BaseClasses import Entrance, Location, ItemClassification
|
||||
from worlds.generic.Rules import add_rule
|
||||
from typing import List, Callable
|
||||
|
||||
# Any speedruns expect the player to have Sprint Hat
|
||||
dw_requirements = {
|
||||
"Beat the Heat": LocData(umbrella=True),
|
||||
"So You're Back From Outer Space": LocData(hookshot=True),
|
||||
"She Speedran from Outer Space": LocData(required_hats=[HatType.SPRINT]),
|
||||
"Mafia's Jumps": LocData(required_hats=[HatType.ICE]),
|
||||
"Vault Codes in the Wind": LocData(required_hats=[HatType.SPRINT]),
|
||||
|
||||
"Security Breach": LocData(hit_requirement=1),
|
||||
"Community Rift: Rhythm Jump Studio": LocData(required_hats=[HatType.ICE]),
|
||||
|
||||
"Speedrun Well": LocData(hookshot=True, hit_requirement=1, required_hats=[HatType.SPRINT]),
|
||||
"Boss Rush": LocData(hit_requirement=1, umbrella=True),
|
||||
"Community Rift: Twilight Travels": LocData(hookshot=True, required_hats=[HatType.DWELLER]),
|
||||
|
||||
"Bird Sanctuary": LocData(hookshot=True),
|
||||
"Wound-Up Windmill": LocData(hookshot=True),
|
||||
"The Illness has Speedrun": LocData(hookshot=True, required_hats=[HatType.SPRINT]),
|
||||
"Community Rift: The Mountain Rift": LocData(hookshot=True, required_hats=[HatType.DWELLER]),
|
||||
"Camera Tourist": LocData(misc_required=["Camera Badge"]),
|
||||
|
||||
"The Mustache Gauntlet": LocData(hookshot=True, required_hats=[HatType.DWELLER]),
|
||||
|
||||
"Rift Collapse - Deep Sea": LocData(hookshot=True, required_hats=[HatType.DWELLER]),
|
||||
}
|
||||
|
||||
# Includes main objective requirements
|
||||
dw_bonus_requirements = {
|
||||
# Some One-Hit Hero requirements need badge pins as well because of Hookshot
|
||||
"So You're Back From Outer Space": LocData(required_hats=[HatType.SPRINT]),
|
||||
"Encore! Encore!": LocData(misc_required=["One-Hit Hero Badge"]),
|
||||
|
||||
"10 Seconds until Self-Destruct": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]),
|
||||
|
||||
"Boss Rush": LocData(misc_required=["One-Hit Hero Badge"]),
|
||||
"Community Rift: Twilight Travels": LocData(required_hats=[HatType.BREWING]),
|
||||
|
||||
"Bird Sanctuary": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"], required_hats=[HatType.DWELLER]),
|
||||
"Wound-Up Windmill": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]),
|
||||
|
||||
"The Mustache Gauntlet": LocData(required_hats=[HatType.ICE]),
|
||||
|
||||
"Rift Collapse - Deep Sea": LocData(required_hats=[HatType.SPRINT]),
|
||||
}
|
||||
|
||||
dw_stamp_costs = {
|
||||
"So You're Back From Outer Space": 2,
|
||||
"Collect-a-thon": 5,
|
||||
"She Speedran from Outer Space": 8,
|
||||
"Encore! Encore!": 10,
|
||||
|
||||
"Security Breach": 4,
|
||||
"The Great Big Hootenanny": 7,
|
||||
"10 Seconds until Self-Destruct": 15,
|
||||
"Killing Two Birds": 25,
|
||||
"Snatcher Coins in Nyakuza Metro": 30,
|
||||
|
||||
"Speedrun Well": 10,
|
||||
"Boss Rush": 15,
|
||||
"Quality Time with Snatcher": 20,
|
||||
"Breaching the Contract": 40,
|
||||
|
||||
"Bird Sanctuary": 15,
|
||||
"Wound-Up Windmill": 30,
|
||||
"The Illness has Speedrun": 35,
|
||||
|
||||
"The Mustache Gauntlet": 35,
|
||||
"No More Bad Guys": 50,
|
||||
"Seal the Deal": 70,
|
||||
}
|
||||
|
||||
|
||||
def set_dw_rules(world: World):
|
||||
if "Snatcher's Hit List" not in world.get_excluded_dws() \
|
||||
or "Camera Tourist" not in world.get_excluded_dws():
|
||||
create_enemy_events(world)
|
||||
|
||||
dw_list: List[str] = []
|
||||
if world.multiworld.DWShuffle[world.player].value > 0:
|
||||
dw_list = world.get_dw_shuffle()
|
||||
else:
|
||||
for name in death_wishes.keys():
|
||||
dw_list.append(name)
|
||||
|
||||
for name in dw_list:
|
||||
if name == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2():
|
||||
continue
|
||||
|
||||
dw = world.multiworld.get_region(name, world.player)
|
||||
temp_list: List[Location] = []
|
||||
main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player)
|
||||
full_clear = world.multiworld.get_location(f"{name} - All Clear", world.player)
|
||||
temp_list.append(main_objective)
|
||||
temp_list.append(full_clear)
|
||||
|
||||
if world.multiworld.DWShuffle[world.player].value == 0:
|
||||
if name in dw_stamp_costs.keys():
|
||||
for entrance in dw.entrances:
|
||||
add_rule(entrance, lambda state, n=name: get_total_dw_stamps(state, world) >= dw_stamp_costs[n])
|
||||
|
||||
if world.multiworld.DWEnableBonus[world.player].value == 0:
|
||||
# place nothing, but let the locations exist still, so we can use them for bonus stamp rules
|
||||
full_clear.address = None
|
||||
full_clear.place_locked_item(HatInTimeItem("Nothing", ItemClassification.filler, None, world.player))
|
||||
full_clear.show_in_spoiler = False
|
||||
|
||||
# Stamps are event locations
|
||||
main_stamp = HatInTimeLocation(world.player, f"Main Stamp - {name}", None, dw)
|
||||
bonus_stamps = HatInTimeLocation(world.player, f"Bonus Stamps - {name}", None, dw)
|
||||
main_stamp.show_in_spoiler = False
|
||||
bonus_stamps.show_in_spoiler = False
|
||||
dw.locations.append(main_stamp)
|
||||
dw.locations.append(bonus_stamps)
|
||||
|
||||
main_stamp.place_locked_item(HatInTimeItem(f"1 Stamp - {name}",
|
||||
ItemClassification.progression, None, world.player))
|
||||
bonus_stamps.place_locked_item(HatInTimeItem(f"2 Stamps - {name}",
|
||||
ItemClassification.progression, None, world.player))
|
||||
|
||||
# No need for rules if excluded - stamps will be auto-granted
|
||||
if world.is_dw_excluded(name):
|
||||
continue
|
||||
|
||||
# Specific Rules
|
||||
if name == "The Illness has Speedrun":
|
||||
# killing the flowers without the umbrella is way too slow
|
||||
add_rule(main_objective, lambda state: state.has("Umbrella", world.player))
|
||||
elif name == "The Mustache Gauntlet":
|
||||
# don't get burned bonus requires a way to kill fire crows without being burned
|
||||
add_rule(full_clear, lambda state: state.has("Umbrella", world.player)
|
||||
or can_use_hat(state, world, HatType.ICE))
|
||||
elif name == "Vault Codes in the Wind":
|
||||
add_rule(main_objective, lambda state: can_use_hat(state, world, HatType.TIME_STOP), "or")
|
||||
|
||||
if name in dw_candles:
|
||||
set_candle_dw_rules(name, world)
|
||||
|
||||
main_rule: Callable[[CollectionState], bool]
|
||||
|
||||
for i in range(len(temp_list)):
|
||||
loc = temp_list[i]
|
||||
data: LocData
|
||||
|
||||
if loc.name == main_objective.name:
|
||||
data = dw_requirements.get(name)
|
||||
else:
|
||||
data = dw_bonus_requirements.get(name)
|
||||
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
if data.hookshot:
|
||||
add_rule(loc, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
for hat in data.required_hats:
|
||||
if hat is not HatType.NONE:
|
||||
add_rule(loc, lambda state, h=hat: can_use_hat(state, world, h))
|
||||
|
||||
for misc in data.misc_required:
|
||||
add_rule(loc, lambda state, item=misc: state.has(item, world.player))
|
||||
|
||||
if data.umbrella and world.multiworld.UmbrellaLogic[world.player].value > 0:
|
||||
add_rule(loc, lambda state: state.has("Umbrella", world.player))
|
||||
|
||||
if data.paintings > 0 and world.multiworld.ShuffleSubconPaintings[world.player].value > 0:
|
||||
add_rule(loc, lambda state, paintings=data.paintings: state.has("Progressive Painting Unlock",
|
||||
world.player, paintings))
|
||||
|
||||
if data.hit_requirement > 0:
|
||||
if data.hit_requirement == 1:
|
||||
add_rule(loc, lambda state: can_hit(state, world))
|
||||
elif data.hit_requirement == 2: # Can bypass with Dweller Mask (dweller bells)
|
||||
add_rule(loc, lambda state: can_hit(state, world) or can_use_hat(state, world, HatType.DWELLER))
|
||||
|
||||
main_rule = main_objective.access_rule
|
||||
|
||||
if loc.name == main_objective.name:
|
||||
add_rule(main_stamp, loc.access_rule)
|
||||
elif loc.name == full_clear.name:
|
||||
add_rule(loc, main_rule)
|
||||
# Only set bonus stamp rules if we don't auto complete bonuses
|
||||
if world.multiworld.DWAutoCompleteBonuses[world.player].value == 0 \
|
||||
and not world.is_bonus_excluded(loc.name):
|
||||
add_rule(bonus_stamps, loc.access_rule)
|
||||
|
||||
if world.multiworld.DWShuffle[world.player].value > 0:
|
||||
dw_shuffle = world.get_dw_shuffle()
|
||||
for i in range(len(dw_shuffle)):
|
||||
if i == 0:
|
||||
continue
|
||||
|
||||
name = dw_shuffle[i]
|
||||
prev_dw = world.multiworld.get_region(dw_shuffle[i-1], world.player)
|
||||
entrance = world.multiworld.get_entrance(f"{prev_dw.name} -> {name}", world.player)
|
||||
add_rule(entrance, lambda state, n=prev_dw.name: state.has(f"1 Stamp - {n}", world.player))
|
||||
else:
|
||||
for key, reqs in dw_prereqs.items():
|
||||
if key == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2():
|
||||
continue
|
||||
|
||||
access_rules: List[Callable[[CollectionState], bool]] = []
|
||||
entrances: List[Entrance] = []
|
||||
|
||||
for parent in reqs:
|
||||
entrance = world.multiworld.get_entrance(f"{parent} -> {key}", world.player)
|
||||
entrances.append(entrance)
|
||||
|
||||
if not world.is_dw_excluded(parent):
|
||||
access_rules.append(lambda state, n=parent: state.has(f"1 Stamp - {n}", world.player))
|
||||
|
||||
for entrance in entrances:
|
||||
for rule in access_rules:
|
||||
add_rule(entrance, rule)
|
||||
|
||||
if world.multiworld.EndGoal[world.player].value == 3:
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has("1 Stamp - Seal the Deal",
|
||||
world.player)
|
||||
|
||||
|
||||
def get_total_dw_stamps(state: CollectionState, world: World) -> int:
|
||||
if world.multiworld.DWShuffle[world.player].value > 0:
|
||||
return 999 # no stamp costs in death wish shuffle
|
||||
|
||||
count: int = 0
|
||||
peace_and_tranquility: bool = world.multiworld.DWEnableBonus[world.player].value == 0 \
|
||||
and world.multiworld.DWAutoCompleteBonuses[world.player].value == 0
|
||||
|
||||
for name in death_wishes:
|
||||
if name == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2():
|
||||
continue
|
||||
|
||||
if state.has(f"1 Stamp - {name}", world.player):
|
||||
count += 1
|
||||
else:
|
||||
continue
|
||||
|
||||
# If bonus rewards and auto bonus completion is off, obtaining stamps via P&T is in logic
|
||||
# Candles don't have P&T
|
||||
if peace_and_tranquility and name not in dw_candles:
|
||||
count += 2
|
||||
continue
|
||||
|
||||
if state.has(f"2 Stamps - {name}", world.player):
|
||||
count += 2
|
||||
elif name not in dw_candles:
|
||||
# all non-candle bonus requirements allow the player to get the other stamp (like not having One Hit Hero)
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def set_candle_dw_rules(name: str, world: World):
|
||||
main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player)
|
||||
full_clear = world.multiworld.get_location(f"{name} - All Clear", world.player)
|
||||
|
||||
if name == "Zero Jumps":
|
||||
add_rule(main_objective, lambda state: get_zero_jump_clear_count(state, world) >= 1)
|
||||
add_rule(full_clear, lambda state: get_zero_jump_clear_count(state, world) >= 4
|
||||
and state.has("Train Rush Cleared", world.player) and can_use_hat(state, world, HatType.ICE))
|
||||
|
||||
elif name == "Snatcher's Hit List":
|
||||
add_rule(main_objective, lambda state: state.has("Mafia Goon", world.player))
|
||||
add_rule(full_clear, lambda state: get_reachable_enemy_count(state, world) >= 12)
|
||||
|
||||
elif name == "Camera Tourist":
|
||||
add_rule(main_objective, lambda state: get_reachable_enemy_count(state, world) >= 8)
|
||||
add_rule(full_clear, lambda state: can_reach_all_bosses(state, world))
|
||||
|
||||
elif name == "Snatcher Coins in Mafia Town":
|
||||
add_rule(main_objective, lambda state: state.has("MT Access", world.player)
|
||||
or state.has("HUMT Access", world.player))
|
||||
|
||||
add_rule(full_clear, lambda state: state.has("CTR Access", world.player)
|
||||
or state.has("HUMT Access", world.player)
|
||||
and (world.multiworld.UmbrellaLogic[world.player].value == 0 or state.has("Umbrella", world.player))
|
||||
or state.has("DWTM Access", world.player)
|
||||
or state.has("TGV Access", world.player))
|
||||
|
||||
elif name == "Snatcher Coins in Battle of the Birds":
|
||||
add_rule(main_objective, lambda state: state.has("PP Access", world.player)
|
||||
or state.has("DBS Access", world.player)
|
||||
or state.has("Train Rush Cleared", world.player))
|
||||
|
||||
add_rule(full_clear, lambda state: state.has("PP Access", world.player)
|
||||
and state.has("DBS Access", world.player)
|
||||
and state.has("Train Rush Cleared", world.player))
|
||||
|
||||
elif name == "Snatcher Coins in Subcon Forest":
|
||||
add_rule(main_objective, lambda state: state.has("SF Access", world.player))
|
||||
|
||||
add_rule(main_objective, lambda state: has_paintings(state, world, 1) and (can_use_hookshot(state, world)
|
||||
or can_hit(state, world) or can_use_hat(state, world, HatType.DWELLER))
|
||||
or has_paintings(state, world, 3))
|
||||
|
||||
add_rule(full_clear, lambda state: has_paintings(state, world, 3) and can_use_hookshot(state, world)
|
||||
and (can_hit(state, world) or can_use_hat(state, world, HatType.DWELLER)))
|
||||
|
||||
elif name == "Snatcher Coins in Alpine Skyline":
|
||||
add_rule(main_objective, lambda state: state.has("LC Access", world.player)
|
||||
or state.has("WM Access", world.player))
|
||||
|
||||
add_rule(full_clear, lambda state: state.has("LC Access", world.player)
|
||||
and state.has("WM Access", world.player))
|
||||
|
||||
elif name == "Snatcher Coins in Nyakuza Metro":
|
||||
add_rule(main_objective, lambda state: state.has("Bluefin Tunnel Cleared", world.player)
|
||||
or (state.has("Nyakuza Intro Cleared", world.player)
|
||||
and (state.has("Metro Ticket - Pink", world.player)
|
||||
or state.has("Metro Ticket - Yellow", world.player)
|
||||
and state.has("Metro Ticket - Blue", world.player))))
|
||||
|
||||
add_rule(full_clear, lambda state: state.has("Bluefin Tunnel Cleared", world.player)
|
||||
and (state.has("Nyakuza Intro Cleared", world.player)
|
||||
and (state.has("Metro Ticket - Pink", world.player)
|
||||
or state.has("Metro Ticket - Yellow", world.player)
|
||||
and state.has("Metro Ticket - Blue", world.player))))
|
||||
|
||||
|
||||
def get_zero_jump_clear_count(state: CollectionState, world: World) -> int:
|
||||
total: int = 0
|
||||
|
||||
for name, hats in zero_jumps.items():
|
||||
if not state.has(f"{name} Cleared", world.player):
|
||||
continue
|
||||
|
||||
valid: bool = True
|
||||
|
||||
for hat in hats:
|
||||
if not can_use_hat(state, world, hat):
|
||||
valid = False
|
||||
break
|
||||
|
||||
if valid:
|
||||
total += 1
|
||||
|
||||
return total
|
||||
|
||||
|
||||
def get_reachable_enemy_count(state: CollectionState, world: World) -> int:
|
||||
count: int = 0
|
||||
for enemy in hit_list.keys():
|
||||
if enemy in bosses:
|
||||
continue
|
||||
|
||||
if state.has(enemy, world.player):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def can_reach_all_bosses(state: CollectionState, world: World) -> bool:
|
||||
for boss in bosses:
|
||||
if not state.has(boss, world.player):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def create_enemy_events(world: World):
|
||||
no_tourist = "Camera Tourist" in world.get_excluded_dws() or "Camera Tourist" in world.get_excluded_bonuses()
|
||||
|
||||
for enemy, regions in hit_list.items():
|
||||
if no_tourist and enemy in bosses:
|
||||
continue
|
||||
|
||||
for area in regions:
|
||||
if (area == "Bon Voyage!" or area == "Time Rift - Deep Sea") and not world.is_dlc1():
|
||||
continue
|
||||
|
||||
if area == "Time Rift - Tour" and not world.is_dlc1() \
|
||||
or world.multiworld.ExcludeTour[world.player].value > 0:
|
||||
continue
|
||||
|
||||
if area == "Bluefin Tunnel" and not world.is_dlc2():
|
||||
continue
|
||||
|
||||
if world.multiworld.DWShuffle[world.player].value > 0 and area in death_wishes \
|
||||
and area not in world.get_dw_shuffle():
|
||||
continue
|
||||
|
||||
region = world.multiworld.get_region(area, world.player)
|
||||
event = HatInTimeLocation(world.player, f"{enemy} - {area}", None, region)
|
||||
event.place_locked_item(HatInTimeItem(enemy, ItemClassification.progression, None, world.player))
|
||||
region.locations.append(event)
|
||||
event.show_in_spoiler = False
|
||||
|
||||
if enemy == "Toxic Flower":
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
if area == "The Illness has Spread":
|
||||
add_rule(event, lambda state: not zipline_logic(world) or
|
||||
state.has("Zipline Unlock - The Birdhouse Path", world.player)
|
||||
or state.has("Zipline Unlock - The Lava Cake Path", world.player)
|
||||
or state.has("Zipline Unlock - The Windmill Path", world.player))
|
||||
|
||||
elif enemy == "Director":
|
||||
if area == "Dead Bird Studio Basement":
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
elif enemy == "Snatcher" or enemy == "Mustache Girl":
|
||||
if area == "Boss Rush":
|
||||
# need to be able to kill toilet
|
||||
add_rule(event, lambda state: can_hit(state, world))
|
||||
|
||||
elif area == "The Finale" and enemy == "Mustache Girl":
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world)
|
||||
and can_use_hat(state, world, HatType.DWELLER))
|
||||
|
||||
elif enemy == "Shock Squid" or enemy == "Ninja Cat":
|
||||
if area == "Time Rift - Deep Sea":
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
|
||||
# Zero Jumps completable levels, with required hats if any
|
||||
zero_jumps = {
|
||||
"Welcome to Mafia Town": [],
|
||||
"Cheating the Race": [HatType.TIME_STOP],
|
||||
"Picture Perfect": [],
|
||||
"Train Rush": [HatType.ICE],
|
||||
"Contractual Obligations": [],
|
||||
"Your Contract has Expired": [],
|
||||
"Mail Delivery Service": [], # rule for needing sprint is already on act completion
|
||||
}
|
||||
|
||||
# Enemies for Snatcher's Hit List/Camera Tourist, and where to find them
|
||||
hit_list = {
|
||||
"Mafia Goon": ["Mafia Town Area", "Time Rift - Mafia of Cooks", "Time Rift - Tour",
|
||||
"Bon Voyage!", "The Mustache Gauntlet", "Rift Collapse: Mafia of Cooks",
|
||||
"So You're Back From Outer Space"],
|
||||
|
||||
"Sleepy Raccoon": ["She Came from Outer Space", "Down with the Mafia!", "The Twilight Bell",
|
||||
"She Speedran from Outer Space", "Mafia's Jumps", "The Mustache Gauntlet",
|
||||
"Time Rift - Sleepy Subcon", "Rift Collapse: Sleepy Subcon"],
|
||||
|
||||
"UFO": ["Picture Perfect", "So You're Back From Outer Space", "Community Rift: Rhythm Jump Studio"],
|
||||
|
||||
"Rat": ["Down with the Mafia!", "Bluefin Tunnel"],
|
||||
|
||||
"Shock Squid": ["Bon Voyage!", "Time Rift - Sleepy Subcon", "Time Rift - Deep Sea",
|
||||
"Rift Collapse: Sleepy Subcon"],
|
||||
|
||||
"Shromb Egg": ["The Birdhouse", "Bird Sanctuary"],
|
||||
|
||||
"Spider": ["Subcon Forest Area", "The Mustache Gauntlet", "Speedrun Well",
|
||||
"The Lava Cake", "The Windmill"],
|
||||
|
||||
"Crow": ["Mafia Town Area", "The Birdhouse", "Time Rift - Tour", "Bird Sanctuary",
|
||||
"Time Rift - Alpine Skyline", "Rift Collapse: Alpine Skyline"],
|
||||
|
||||
"Pompous Crow": ["The Birdhouse", "Time Rift - The Lab", "Bird Sanctuary", "The Mustache Gauntlet"],
|
||||
|
||||
"Fiery Crow": ["The Finale", "The Lava Cake", "The Mustache Gauntlet"],
|
||||
|
||||
"Express Owl": ["The Finale", "Time Rift - The Owl Express", "Time Rift - Deep Sea"],
|
||||
|
||||
"Ninja Cat": ["The Birdhouse", "The Windmill", "Bluefin Tunnel", "The Mustache Gauntlet",
|
||||
"Time Rift - Curly Tail Trail", "Time Rift - Alpine Skyline", "Time Rift - Deep Sea",
|
||||
"Rift Collapse: Alpine Skyline"],
|
||||
|
||||
# Bosses
|
||||
"Mafia Boss": ["Down with the Mafia!", "Encore! Encore!", "Boss Rush"],
|
||||
|
||||
"Conductor": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
|
||||
"Toilet": ["Toilet of Doom", "Boss Rush"],
|
||||
|
||||
"Snatcher": ["Your Contract has Expired", "Breaching the Contract", "Boss Rush",
|
||||
"Quality Time with Snatcher"],
|
||||
|
||||
"Toxic Flower": ["The Illness has Spread", "The Illness has Speedrun"],
|
||||
|
||||
"Mustache Girl": ["The Finale", "Boss Rush", "No More Bad Guys"],
|
||||
}
|
||||
|
||||
bosses = [
|
||||
"Mafia Boss",
|
||||
"Conductor",
|
||||
"Toilet",
|
||||
"Snatcher",
|
||||
"Toxic Flower",
|
||||
"Mustache Girl",
|
||||
]
|
||||
|
||||
@@ -1,19 +1,121 @@
|
||||
from BaseClasses import Item, ItemClassification
|
||||
from worlds.AutoWorld import World
|
||||
from .Types import HatDLC
|
||||
import typing
|
||||
from .Types import HatDLC, HatType
|
||||
from .Locations import get_total_locations
|
||||
from .Rules import get_difficulty, is_player_knowledgeable
|
||||
from typing import Optional, NamedTuple, List, Dict
|
||||
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: typing.Optional[int]
|
||||
class ItemData(NamedTuple):
|
||||
code: Optional[int]
|
||||
classification: ItemClassification
|
||||
dlc_flags: typing.Optional[HatDLC] = HatDLC.none
|
||||
dlc_flags: Optional[HatDLC] = HatDLC.none
|
||||
|
||||
|
||||
class HatInTimeItem(Item):
|
||||
game: str = "A Hat in Time"
|
||||
|
||||
|
||||
def create_itempool(world: World) -> List[Item]:
|
||||
itempool: List[Item] = []
|
||||
if not world.is_dw_only():
|
||||
calculate_yarn_costs(world)
|
||||
yarn_pool: List[Item] = create_multiple_items(world, "Yarn",
|
||||
world.multiworld.YarnAvailable[world.player].value,
|
||||
ItemClassification.progression_skip_balancing)
|
||||
|
||||
for i in range(int(len(yarn_pool) * (0.01 * world.multiworld.YarnBalancePercent[world.player].value))):
|
||||
yarn_pool[i].classification = ItemClassification.progression
|
||||
|
||||
itempool += yarn_pool
|
||||
|
||||
for name in item_table.keys():
|
||||
if name == "Yarn":
|
||||
continue
|
||||
|
||||
if not item_dlc_enabled(world, name):
|
||||
continue
|
||||
|
||||
item_type: ItemClassification = item_table.get(name).classification
|
||||
if get_difficulty(world) >= 1 or is_player_knowledgeable(world) \
|
||||
and (name == "Scooter Badge" or name == "No Bonk Badge"):
|
||||
item_type = ItemClassification.progression
|
||||
|
||||
# some death wish bonuses require one hit hero + hookshot
|
||||
if world.is_dw() and name == "Badge Pin":
|
||||
item_type = ItemClassification.progression
|
||||
|
||||
if world.is_dw_only():
|
||||
if item_type is ItemClassification.progression \
|
||||
or item_type is ItemClassification.progression_skip_balancing:
|
||||
continue
|
||||
|
||||
# progression balance anything useful, since we have basically no progression in this mode
|
||||
if item_type is ItemClassification.useful:
|
||||
item_type = ItemClassification.progression
|
||||
|
||||
if item_type is ItemClassification.filler or item_type is ItemClassification.trap:
|
||||
continue
|
||||
|
||||
if name in act_contracts.keys() and world.multiworld.ShuffleActContracts[world.player].value == 0:
|
||||
continue
|
||||
|
||||
if name in alps_hooks.keys() and world.multiworld.ShuffleAlpineZiplines[world.player].value == 0:
|
||||
continue
|
||||
|
||||
if name == "Progressive Painting Unlock" \
|
||||
and world.multiworld.ShuffleSubconPaintings[world.player].value == 0:
|
||||
continue
|
||||
|
||||
if world.multiworld.StartWithCompassBadge[world.player].value > 0 and name == "Compass Badge":
|
||||
continue
|
||||
|
||||
if name == "Time Piece":
|
||||
tp_count: int = 40
|
||||
max_extra: int = 0
|
||||
if world.is_dlc1():
|
||||
max_extra += 6
|
||||
|
||||
if world.is_dlc2():
|
||||
max_extra += 10
|
||||
|
||||
tp_count += min(max_extra, world.multiworld.MaxExtraTimePieces[world.player].value)
|
||||
tp_list: List[Item] = create_multiple_items(world, name, tp_count, item_type)
|
||||
|
||||
for i in range(int(len(tp_list) * (0.01 * world.multiworld.TimePieceBalancePercent[world.player].value))):
|
||||
tp_list[i].classification = ItemClassification.progression
|
||||
|
||||
itempool += tp_list
|
||||
continue
|
||||
|
||||
itempool += create_multiple_items(world, name, item_frequencies.get(name, 1), item_type)
|
||||
|
||||
total_locations: int = get_total_locations(world)
|
||||
itempool += create_junk_items(world, total_locations - len(itempool))
|
||||
return itempool
|
||||
|
||||
|
||||
def calculate_yarn_costs(world: World):
|
||||
mw = world.multiworld
|
||||
p = world.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: int = mw.random.randint(min(min_yarn_cost, max_yarn_cost), max(max_yarn_cost, min_yarn_cost))
|
||||
world.get_hat_yarn_costs()[HatType(i)] = cost
|
||||
max_cost += cost
|
||||
|
||||
available_yarn: int = mw.YarnAvailable[p].value
|
||||
if max_cost > available_yarn:
|
||||
mw.YarnAvailable[p].value = max_cost
|
||||
available_yarn = max_cost
|
||||
|
||||
if max_cost + mw.MinExtraYarn[p].value > available_yarn:
|
||||
mw.YarnAvailable[p].value += (max_cost + mw.MinExtraYarn[p].value) - available_yarn
|
||||
|
||||
|
||||
def item_dlc_enabled(world: World, name: str) -> bool:
|
||||
data = item_table[name]
|
||||
|
||||
@@ -29,43 +131,38 @@ def item_dlc_enabled(world: World, name: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def get_total_time_pieces(world: World) -> int:
|
||||
count: int = 40
|
||||
if world.is_dlc1():
|
||||
count += 6
|
||||
|
||||
if world.is_dlc2():
|
||||
count += 10
|
||||
|
||||
return min(40+world.multiworld.MaxExtraTimePieces[world.player].value, count)
|
||||
|
||||
|
||||
def create_item(world: World, name: str) -> Item:
|
||||
data = item_table[name]
|
||||
return HatInTimeItem(name, data.classification, data.code, world.player)
|
||||
|
||||
|
||||
def create_multiple_items(world: World, name: str, count: int = 1) -> typing.List[Item]:
|
||||
def create_multiple_items(world: World, name: str, count: int = 1,
|
||||
item_type: Optional[ItemClassification] = ItemClassification.progression) -> List[Item]:
|
||||
|
||||
data = item_table[name]
|
||||
itemlist: typing.List[Item] = []
|
||||
itemlist: List[Item] = []
|
||||
|
||||
for i in range(count):
|
||||
itemlist += [HatInTimeItem(name, data.classification, data.code, world.player)]
|
||||
itemlist += [HatInTimeItem(name, item_type, data.code, world.player)]
|
||||
|
||||
return itemlist
|
||||
|
||||
|
||||
def create_junk_items(world: World, count: int) -> typing.List[Item]:
|
||||
def create_junk_items(world: World, count: int) -> List[Item]:
|
||||
trap_chance = world.multiworld.TrapChance[world.player].value
|
||||
junk_pool: typing.List[Item] = []
|
||||
junk_list: typing.Dict[str, int] = {}
|
||||
trap_list: typing.Dict[str, int] = {}
|
||||
junk_pool: List[Item] = []
|
||||
junk_list: Dict[str, int] = {}
|
||||
trap_list: Dict[str, int] = {}
|
||||
ic: ItemClassification
|
||||
|
||||
for name in item_table.keys():
|
||||
ic = item_table[name].classification
|
||||
if ic == ItemClassification.filler:
|
||||
if world.is_dw_only() and "Pons" in name:
|
||||
continue
|
||||
|
||||
junk_list[name] = junk_weights.get(name)
|
||||
|
||||
elif trap_chance > 0 and ic == ItemClassification.trap:
|
||||
if name == "Baby Trap":
|
||||
trap_list[name] = world.multiworld.BabyTrapWeight[world.player].value
|
||||
@@ -75,12 +172,12 @@ def create_junk_items(world: World, count: int) -> typing.List[Item]:
|
||||
trap_list[name] = world.multiworld.ParadeTrapWeight[world.player].value
|
||||
|
||||
for i in range(count):
|
||||
if trap_chance > 0 and world.multiworld.random.randint(1, 100) <= trap_chance:
|
||||
if trap_chance > 0 and world.random.randint(1, 100) <= trap_chance:
|
||||
junk_pool += [world.create_item(
|
||||
world.multiworld.random.choices(list(trap_list.keys()), weights=list(trap_list.values()), k=1)[0])]
|
||||
world.random.choices(list(trap_list.keys()), weights=list(trap_list.values()), k=1)[0])]
|
||||
else:
|
||||
junk_pool += [world.create_item(
|
||||
world.multiworld.random.choices(list(junk_list.keys()), weights=list(junk_list.values()), k=1)[0])]
|
||||
world.random.choices(list(junk_list.keys()), weights=list(junk_list.values()), k=1)[0])]
|
||||
|
||||
return junk_pool
|
||||
|
||||
@@ -110,21 +207,22 @@ ahit_items = {
|
||||
"Hover Badge": ItemData(300026, ItemClassification.useful),
|
||||
"Hookshot Badge": ItemData(300027, ItemClassification.progression),
|
||||
"Item Magnet Badge": ItemData(300028, ItemClassification.useful),
|
||||
"No Bonk Badge": ItemData(300029, ItemClassification.progression),
|
||||
"No Bonk Badge": ItemData(300029, ItemClassification.useful),
|
||||
"Compass Badge": ItemData(300030, ItemClassification.useful),
|
||||
"Scooter Badge": ItemData(300031, ItemClassification.progression),
|
||||
"Badge Pin": ItemData(300043, ItemClassification.useful),
|
||||
"Scooter Badge": ItemData(300031, ItemClassification.useful),
|
||||
"One-Hit Hero Badge": ItemData(300038, ItemClassification.progression, HatDLC.death_wish),
|
||||
"Camera Badge": ItemData(300042, ItemClassification.progression, HatDLC.death_wish),
|
||||
|
||||
# Other
|
||||
# "Rift Token": ItemData(300032, ItemClassification.filler),
|
||||
"Random Cosmetic": ItemData(300044, ItemClassification.filler),
|
||||
"Umbrella": ItemData(300033, ItemClassification.progression),
|
||||
"Badge Pin": ItemData(300043, ItemClassification.useful),
|
||||
|
||||
# Garbage items
|
||||
"25 Pons": ItemData(300034, ItemClassification.filler),
|
||||
"50 Pons": ItemData(300035, ItemClassification.filler),
|
||||
"100 Pons": ItemData(300036, ItemClassification.filler),
|
||||
"Health Pon": ItemData(300037, ItemClassification.filler),
|
||||
"Random Cosmetic": ItemData(300044, ItemClassification.filler),
|
||||
|
||||
# Traps
|
||||
"Baby Trap": ItemData(300039, ItemClassification.trap),
|
||||
@@ -144,10 +242,6 @@ ahit_items = {
|
||||
"Metro Ticket - Green": ItemData(300046, ItemClassification.progression, HatDLC.dlc2),
|
||||
"Metro Ticket - Blue": ItemData(300047, ItemClassification.progression, HatDLC.dlc2),
|
||||
"Metro Ticket - Pink": ItemData(300048, ItemClassification.progression, HatDLC.dlc2),
|
||||
|
||||
# Death Wish items
|
||||
"One-Hit Hero Badge": ItemData(300038, ItemClassification.progression, HatDLC.death_wish),
|
||||
"Camera Badge": ItemData(300042, ItemClassification.progression, HatDLC.death_wish),
|
||||
}
|
||||
|
||||
act_contracts = {
|
||||
@@ -180,10 +274,10 @@ item_frequencies = {
|
||||
|
||||
junk_weights = {
|
||||
"25 Pons": 50,
|
||||
"50 Pons": 10,
|
||||
"50 Pons": 25,
|
||||
"100 Pons": 10,
|
||||
"Health Pon": 35,
|
||||
"100 Pons": 5,
|
||||
"Random Cosmetic": 25,
|
||||
"Random Cosmetic": 35,
|
||||
}
|
||||
|
||||
item_table = {
|
||||
|
||||
@@ -6,12 +6,13 @@ from .Options import TasksanityCheckCount
|
||||
|
||||
|
||||
class LocData(NamedTuple):
|
||||
id: int
|
||||
region: str
|
||||
id: Optional[int] = 0
|
||||
region: Optional[str] = ""
|
||||
required_hats: Optional[List[HatType]] = [HatType.NONE]
|
||||
hookshot: Optional[bool] = False
|
||||
dlc_flags: Optional[HatDLC] = HatDLC.none
|
||||
paintings: Optional[int] = 0 # Paintings required for Subcon painting shuffle
|
||||
misc_required: Optional[List[str]] = []
|
||||
|
||||
# For UmbrellaLogic setting
|
||||
umbrella: Optional[bool] = False # Umbrella required for this check
|
||||
@@ -29,12 +30,28 @@ class HatInTimeLocation(Location):
|
||||
def get_total_locations(world: World) -> int:
|
||||
total: int = 0
|
||||
|
||||
for (name) in location_table.keys():
|
||||
if is_location_valid(world, name):
|
||||
total += 1
|
||||
if not world.is_dw_only():
|
||||
for (name) in location_table.keys():
|
||||
if is_location_valid(world, name):
|
||||
total += 1
|
||||
|
||||
if world.is_dlc1() and world.multiworld.Tasksanity[world.player].value > 0:
|
||||
total += world.multiworld.TasksanityCheckCount[world.player].value
|
||||
if world.is_dlc1() and world.multiworld.Tasksanity[world.player].value > 0:
|
||||
total += world.multiworld.TasksanityCheckCount[world.player].value
|
||||
|
||||
if world.is_dw():
|
||||
if world.multiworld.DWShuffle[world.player].value > 0:
|
||||
total += len(world.get_dw_shuffle())
|
||||
if world.multiworld.DWEnableBonus[world.player].value > 0:
|
||||
total += len(world.get_dw_shuffle())
|
||||
else:
|
||||
total += 37
|
||||
if world.is_dlc2():
|
||||
total += 1
|
||||
|
||||
if world.multiworld.DWEnableBonus[world.player].value > 0:
|
||||
total += 37
|
||||
if world.is_dlc2():
|
||||
total += 1
|
||||
|
||||
return total
|
||||
|
||||
@@ -58,15 +75,20 @@ def is_location_valid(world: World, location: str) -> bool:
|
||||
if not location_dlc_enabled(world, location):
|
||||
return False
|
||||
|
||||
if location in storybook_pages.keys() \
|
||||
and world.multiworld.ShuffleStorybookPages[world.player].value == 0:
|
||||
if world.multiworld.ShuffleStorybookPages[world.player].value == 0 \
|
||||
and location in storybook_pages.keys():
|
||||
return False
|
||||
|
||||
if location in shop_locations and location not in world.shop_locs:
|
||||
if location not in world.shop_locs and location in shop_locations:
|
||||
return False
|
||||
|
||||
data = location_table.get(location) or event_locs.get(location)
|
||||
if data.region == "Time Rift - Tour" and world.multiworld.ExcludeTour[world.player].value > 0:
|
||||
if world.multiworld.ExcludeTour[world.player].value > 0 and data.region == "Time Rift - Tour":
|
||||
return False
|
||||
|
||||
# No need for all those event items if we're not doing candles
|
||||
if data.dlc_flags is HatDLC.death_wish and world.multiworld.DWExcludeCandles[world.player].value > 0 \
|
||||
and location in event_locs.keys():
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -76,7 +98,11 @@ def get_location_names() -> Dict[str, int]:
|
||||
names = {name: data.id for name, data in location_table.items()}
|
||||
id_start: int = get_tasksanity_start_id()
|
||||
for i in range(TasksanityCheckCount.range_end):
|
||||
names.setdefault(format("Tasksanity Check %i") % (i+1), id_start+i)
|
||||
names.setdefault(f"Tasksanity Check {i+1}", id_start+i)
|
||||
|
||||
for (key, loc_id) in death_wishes.items():
|
||||
names.setdefault(f"{key} - Main Objective", loc_id)
|
||||
names.setdefault(f"{key} - All Clear", loc_id+1)
|
||||
|
||||
return names
|
||||
|
||||
@@ -169,7 +195,7 @@ ahit_locations = {
|
||||
"Dead Bird Studio Basement - Cameras": LocData(305431, "Dead Bird Studio Basement", hookshot=True),
|
||||
"Dead Bird Studio Basement - Locked Room": LocData(305819, "Dead Bird Studio Basement", hookshot=True),
|
||||
|
||||
# 320000 range - Subcon Forest
|
||||
# Subcon Forest
|
||||
"Contractual Obligations - Cherry Bomb Bone Cage": LocData(324761, "Contractual Obligations"),
|
||||
"Subcon Village - Tree Top Ice Cube": LocData(325078, "Subcon Forest Area"),
|
||||
"Subcon Village - Graveyard Ice Cube": LocData(325077, "Subcon Forest Area"),
|
||||
@@ -178,7 +204,8 @@ ahit_locations = {
|
||||
"Subcon Village - Snatcher Statue Chest": LocData(323730, "Subcon Forest Area", paintings=1),
|
||||
"Subcon Village - Stump Platform Chest": LocData(323729, "Subcon Forest Area"),
|
||||
"Subcon Forest - Giant Tree Climb": LocData(325470, "Subcon Forest Area"),
|
||||
|
||||
|
||||
"Subcon Forest - Ice Cube Shack": LocData(324465, "Subcon Forest Area", paintings=1),
|
||||
"Subcon Forest - Swamp Gravestone": LocData(326296, "Subcon Forest Area",
|
||||
required_hats=[HatType.BREWING], paintings=1),
|
||||
|
||||
@@ -189,30 +216,6 @@ ahit_locations = {
|
||||
"Subcon Forest - Swamp Treehouse": LocData(325468, "Subcon Forest Area", paintings=1),
|
||||
"Subcon Forest - Swamp Tree Chest": LocData(323728, "Subcon Forest Area", paintings=1),
|
||||
|
||||
"Subcon Forest - Dweller Stump": LocData(324767, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Floating Rocks": LocData(324464, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Platforming Tree A": LocData(324709, "Subcon Forest Area", paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Platforming Tree B": LocData(324855, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Giant Time Piece": LocData(325473, "Subcon Forest Area", paintings=3),
|
||||
"Subcon Forest - Gallows": LocData(325472, "Subcon Forest Area", paintings=3),
|
||||
|
||||
"Subcon Forest - Green and Purple Dweller Rocks": LocData(325082, "Subcon Forest Area", paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Shack": LocData(324463, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Tall Tree Hookshot Swing": LocData(324766, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER],
|
||||
hookshot=True,
|
||||
paintings=3),
|
||||
|
||||
"Subcon Forest - Burning House": LocData(324710, "Subcon Forest Area", paintings=2),
|
||||
"Subcon Forest - Burning Tree Climb": LocData(325079, "Subcon Forest Area", paintings=2),
|
||||
"Subcon Forest - Burning Stump Chest": LocData(323731, "Subcon Forest Area", paintings=2),
|
||||
@@ -221,7 +224,6 @@ ahit_locations = {
|
||||
"Subcon Forest - Spider Bone Cage B": LocData(325080, "Subcon Forest Area", paintings=2),
|
||||
"Subcon Forest - Triple Spider Bounce": LocData(324765, "Subcon Forest Area", paintings=2),
|
||||
"Subcon Forest - Noose Treehouse": LocData(324856, "Subcon Forest Area", hookshot=True, paintings=2),
|
||||
"Subcon Forest - Ice Cube Shack": LocData(324465, "Subcon Forest Area", paintings=1),
|
||||
|
||||
"Subcon Forest - Long Tree Climb Chest": LocData(323734, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=2),
|
||||
@@ -234,6 +236,30 @@ ahit_locations = {
|
||||
|
||||
"Subcon Forest - Magnet Badge Bush": LocData(325479, "Subcon Forest Area",
|
||||
required_hats=[HatType.BREWING], paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Stump": LocData(324767, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Floating Rocks": LocData(324464, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Platforming Tree A": LocData(324709, "Subcon Forest Area", paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Platforming Tree B": LocData(324855, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Giant Time Piece": LocData(325473, "Subcon Forest Area", paintings=3),
|
||||
"Subcon Forest - Gallows": LocData(325472, "Subcon Forest Area", paintings=3),
|
||||
|
||||
"Subcon Forest - Green and Purple Dweller Rocks": LocData(325082, "Subcon Forest Area", paintings=3),
|
||||
|
||||
"Subcon Forest - Dweller Shack": LocData(324463, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Tall Tree Hookshot Swing": LocData(324766, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER],
|
||||
hookshot=True,
|
||||
paintings=3),
|
||||
|
||||
"Subcon Well - Hookshot Badge Chest": LocData(324114, "The Subcon Well", hit_requirement=1, paintings=1),
|
||||
"Subcon Well - Above Chest": LocData(324612, "The Subcon Well", hit_requirement=1, paintings=1),
|
||||
@@ -245,7 +271,7 @@ ahit_locations = {
|
||||
"Queen Vanessa's Manor - Hall Chest": LocData(323896, "Queen Vanessa's Manor", hit_requirement=2, paintings=1),
|
||||
"Queen Vanessa's Manor - Chandelier": LocData(325546, "Queen Vanessa's Manor", hit_requirement=2, paintings=1),
|
||||
|
||||
# 330000 range - Alpine Skyline
|
||||
# Alpine Skyline
|
||||
"Alpine Skyline - Goat Village: Below Hookpoint": LocData(334856, "Goat Village"),
|
||||
"Alpine Skyline - Goat Village: Hidden Branch": LocData(334855, "Goat Village"),
|
||||
"Alpine Skyline - Goat Refinery": LocData(333635, "Alpine Skyline Area"),
|
||||
@@ -320,7 +346,6 @@ ahit_locations = {
|
||||
}
|
||||
|
||||
act_completions = {
|
||||
# 310000 range - Act Completions
|
||||
"Act Completion (Time Rift - Gallery)": LocData(312758, "Time Rift - Gallery"),
|
||||
"Act Completion (Time Rift - The Lab)": LocData(312838, "Time Rift - The Lab"),
|
||||
|
||||
@@ -479,13 +504,6 @@ storybook_pages = {
|
||||
"Rumbi Factory - Page: Last Area": LocData(345883, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2),
|
||||
}
|
||||
|
||||
contract_locations = {
|
||||
"Snatcher's Contract - The Subcon Well": LocData(300200, "Contractual Obligations"),
|
||||
"Snatcher's Contract - Toilet of Doom": LocData(300201, "Subcon Forest Area"),
|
||||
"Snatcher's Contract - Queen Vanessa's Manor": LocData(300202, "Subcon Forest Area"),
|
||||
"Snatcher's Contract - Mail Delivery Service": LocData(300203, "Subcon Forest Area"),
|
||||
}
|
||||
|
||||
shop_locations = {
|
||||
"Badge Seller - Item 1": LocData(301003, "Badge Seller"),
|
||||
"Badge Seller - Item 2": LocData(301004, "Badge Seller"),
|
||||
@@ -623,6 +641,13 @@ shop_locations = {
|
||||
|
||||
}
|
||||
|
||||
contract_locations = {
|
||||
"Snatcher's Contract - The Subcon Well": LocData(300200, "Contractual Obligations"),
|
||||
"Snatcher's Contract - Toilet of Doom": LocData(300201, "Subcon Forest Area"),
|
||||
"Snatcher's Contract - Queen Vanessa's Manor": LocData(300202, "Subcon Forest Area"),
|
||||
"Snatcher's Contract - Mail Delivery Service": LocData(300203, "Subcon Forest Area"),
|
||||
}
|
||||
|
||||
# Don't put any of the locations from peaks here, the rules for their entrances are set already
|
||||
zipline_unlocks = {
|
||||
"Alpine Skyline - Bird Pass Fork": "Zipline Unlock - The Birdhouse Path",
|
||||
@@ -652,7 +677,9 @@ tihs_locations = [
|
||||
|
||||
event_locs = {
|
||||
"HUMT Access": LocData(0, "Heating Up Mafia Town", act_complete_event=False),
|
||||
"Subcon Forest Access": LocData(0, "Subcon Forest Area", act_complete_event=False),
|
||||
"TOD Access": LocData(0, "Toilet of Doom", act_complete_event=False),
|
||||
"YCHE Access": LocData(0, "Your Contract has Expired", act_complete_event=False),
|
||||
|
||||
"Birdhouse Cleared": LocData(0, "The Birdhouse"),
|
||||
"Lava Cake Cleared": LocData(0, "The Lava Cake"),
|
||||
"Windmill Cleared": LocData(0, "The Windmill"),
|
||||
@@ -670,6 +697,39 @@ event_locs = {
|
||||
"Green Clean Manhole Cleared": LocData(0, "Green Clean Manhole", dlc_flags=HatDLC.dlc2),
|
||||
"Pink Paw Manhole Cleared": LocData(0, "Pink Paw Manhole", dlc_flags=HatDLC.dlc2),
|
||||
"Rush Hour Cleared": LocData(0, "Rush Hour", dlc_flags=HatDLC.dlc2),
|
||||
|
||||
|
||||
# -------------- Death Wish Candle Related --------------- #
|
||||
|
||||
|
||||
# Snatcher Coins
|
||||
"MT Access": LocData(0, "Mafia Town Area", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"DWTM Access": LocData(0, "Down with the Mafia!", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"CTR Access": LocData(0, "Cheating the Race", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"TGV Access": LocData(0, "The Golden Vault", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"DBS Access": LocData(0, "Dead Bird Studio - Elevator Area", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"PP Access": LocData(0, "Picture Perfect", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"SF Access": LocData(0, "Subcon Forest Area", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"LC Access": LocData(0, "The Lava Cake", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"WM Access": LocData(0, "The Windmill", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
# Camera Tourist
|
||||
"Mafia Boss": LocData(0, "Down with the Mafia!", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"Conductor": LocData(0, "Dead Bird Studio Basement", dlc_flags=HatDLC.death_wish),
|
||||
"Snatcher": LocData(0, "Your Contract has Expired", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
"Evil Flower": LocData(0, "The Illness has Spread", act_complete_event=False, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
# Zero Jumps
|
||||
"Welcome to Mafia Town Cleared": LocData(0, "Welcome to Mafia Town", dlc_flags=HatDLC.death_wish),
|
||||
"Picture Perfect Cleared": LocData(0, "Picture Perfect", dlc_flags=HatDLC.death_wish),
|
||||
"Contractual Obligations Cleared": LocData(0, "Contractual Obligations", dlc_flags=HatDLC.death_wish),
|
||||
"Your Contract has Expired Cleared": LocData(0, "Your Contract has Expired", dlc_flags=HatDLC.death_wish),
|
||||
"Mail Delivery Service Cleared": LocData(0, "Mail Delivery Service", dlc_flags=HatDLC.death_wish),
|
||||
"Cheating the Race Cleared": LocData(0, "Cheating the Race", dlc_flags=HatDLC.death_wish),
|
||||
"Train Rush Cleared": LocData(0, "Train Rush", dlc_flags=HatDLC.death_wish),
|
||||
}
|
||||
|
||||
location_table = {
|
||||
@@ -679,3 +739,52 @@ location_table = {
|
||||
**contract_locations,
|
||||
**shop_locations,
|
||||
}
|
||||
|
||||
# DO NOT ALTER THE ORDER OF THIS LIST
|
||||
# This file is in here instead of DeathWishLocations.py to prevent circular import problems
|
||||
death_wishes = {
|
||||
"Beat the Heat": 350000,
|
||||
"Snatcher's Hit List": 350002,
|
||||
"So You're Back From Outer Space": 350004,
|
||||
"Collect-a-thon": 350006,
|
||||
"Rift Collapse: Mafia of Cooks": 350008,
|
||||
"She Speedran from Outer Space": 350010,
|
||||
"Mafia's Jumps": 350012,
|
||||
"Vault Codes in the Wind": 350014,
|
||||
"Encore! Encore!": 350016,
|
||||
"Snatcher Coins in Mafia Town": 350018,
|
||||
|
||||
"Security Breach": 350020,
|
||||
"The Great Big Hootenanny": 350022,
|
||||
"Rift Collapse: Dead Bird Studio": 350024,
|
||||
"10 Seconds until Self-Destruct": 350026,
|
||||
"Killing Two Birds": 350028,
|
||||
"Snatcher Coins in Battle of the Birds": 350030,
|
||||
"Zero Jumps": 350032,
|
||||
|
||||
"Speedrun Well": 350034,
|
||||
"Rift Collapse: Sleepy Subcon": 350036,
|
||||
"Boss Rush": 350038,
|
||||
"Quality Time with Snatcher": 350040,
|
||||
"Breaching the Contract": 350042,
|
||||
"Snatcher Coins in Subcon Forest": 350044,
|
||||
|
||||
"Bird Sanctuary": 350046,
|
||||
"Rift Collapse: Alpine Skyline": 350048,
|
||||
"Wound-Up Windmill": 350050,
|
||||
"The Illness has Speedrun": 350052,
|
||||
"Snatcher Coins in Alpine Skyline": 350054,
|
||||
"Camera Tourist": 350056,
|
||||
|
||||
"The Mustache Gauntlet": 350058,
|
||||
"No More Bad Guys": 350060,
|
||||
|
||||
"Seal the Deal": 350062,
|
||||
"Rift Collapse: Deep Sea": 350064,
|
||||
"Cruisin' for a Bruisin'": 350066,
|
||||
|
||||
"Community Rift: Rhythm Jump Studio": 350068,
|
||||
"Community Rift: Twilight Travels": 350070,
|
||||
"Community Rift: The Mountain Rift": 350072,
|
||||
"Snatcher Coins in Nyakuza Metro": 350074,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import typing
|
||||
from worlds.AutoWorld import World
|
||||
from Options import Option, Range, Toggle, DeathLink, Choice, OptionDict
|
||||
from .Items import get_total_time_pieces
|
||||
|
||||
|
||||
def adjust_options(world: World):
|
||||
@@ -46,25 +45,59 @@ def adjust_options(world: World):
|
||||
if world.multiworld.EndGoal[world.player].value == 2 and world.multiworld.EnableDLC2[world.player].value == 0:
|
||||
world.multiworld.EndGoal[world.player].value = 1
|
||||
|
||||
# Don't allow Seal the Deal goal if Death Wish content is disabled
|
||||
if world.multiworld.EndGoal[world.player].value == 3 and not world.is_dw():
|
||||
world.multiworld.EndGoal[world.player].value = 1
|
||||
|
||||
if world.multiworld.DWEnableBonus[world.player].value > 0:
|
||||
world.multiworld.DWAutoCompleteBonuses[world.player].value = 0
|
||||
|
||||
if world.is_dw_only():
|
||||
world.multiworld.EndGoal[world.player].value = 3
|
||||
world.multiworld.ActRandomizer[world.player].value = 0
|
||||
world.multiworld.ShuffleAlpineZiplines[world.player].value = 0
|
||||
world.multiworld.ShuffleSubconPaintings[world.player].value = 0
|
||||
world.multiworld.ShuffleStorybookPages[world.player].value = 0
|
||||
world.multiworld.ShuffleActContracts[world.player].value = 0
|
||||
world.multiworld.EnableDLC1[world.player].value = 0
|
||||
world.multiworld.LogicDifficulty[world.player].value = 0
|
||||
world.multiworld.KnowledgeChecks[world.player].value = 0
|
||||
world.multiworld.DWTimePieceRequirement[world.player].value = 0
|
||||
world.multiworld.progression_balancing[world.player].value = 0
|
||||
|
||||
|
||||
def get_total_time_pieces(world: World) -> int:
|
||||
count: int = 40
|
||||
if world.is_dlc1():
|
||||
count += 6
|
||||
|
||||
if world.is_dlc2():
|
||||
count += 10
|
||||
|
||||
return min(40+world.multiworld.MaxExtraTimePieces[world.player].value, count)
|
||||
|
||||
|
||||
# General
|
||||
class EndGoal(Choice):
|
||||
"""The end goal required to beat the game.
|
||||
Finale: Reach Time's End and beat Mustache Girl. The Finale will be in its vanilla location.
|
||||
|
||||
Rush Hour: Reach and complete Rush Hour. The level will be in its vanilla location and Chapter 7
|
||||
will be the final chapter. You also must find Nyakuza Metro itself and complete all of its levels.
|
||||
Requires DLC2 content to be enabled."""
|
||||
Requires DLC2 content to be enabled.
|
||||
|
||||
Seal the Deal: Reach and complete the Seal the Deal death wish main objective.
|
||||
Requires Death Wish content to be enabled."""
|
||||
display_name = "End Goal"
|
||||
option_finale = 1
|
||||
option_rush_hour = 2
|
||||
option_seal_the_deal = 3
|
||||
default = 1
|
||||
|
||||
|
||||
class ActRandomizer(Choice):
|
||||
"""If enabled, shuffle the game's Acts between each other.
|
||||
Light will cause Time Rifts to only be shuffled amongst each other,
|
||||
and Blue Time Rifts and Purple Time Rifts are shuffled separately."""
|
||||
and Blue Time Rifts and Purple Time Rifts to be shuffled separately."""
|
||||
display_name = "Shuffle Acts"
|
||||
option_false = 0
|
||||
option_light = 1
|
||||
@@ -77,14 +110,9 @@ class ActPlando(OptionDict):
|
||||
display_name = "Act Plando"
|
||||
|
||||
|
||||
class ShuffleAlpineZiplines(Toggle):
|
||||
"""If enabled, Alpine's zipline paths leading to the peaks will be locked behind items."""
|
||||
display_name = "Shuffle Alpine Ziplines"
|
||||
default = 0
|
||||
|
||||
|
||||
class FinaleShuffle(Toggle):
|
||||
"""If enabled, chapter finales will only be shuffled amongst each other in act shuffle."""
|
||||
display_name = "Finale Shuffle"
|
||||
default = 0
|
||||
|
||||
|
||||
@@ -105,9 +133,13 @@ class KnowledgeChecks(Toggle):
|
||||
default = 0
|
||||
|
||||
|
||||
class RandomizeHatOrder(Toggle):
|
||||
"""Randomize the order that hats are stitched in."""
|
||||
class RandomizeHatOrder(Choice):
|
||||
"""Randomize the order that hats are stitched in.
|
||||
Time Stop Last will force Time Stop to be the last hat in the sequence."""
|
||||
display_name = "Randomize Hat Order"
|
||||
option_false = 0
|
||||
option_true = 1
|
||||
option_time_stop_last = 2
|
||||
default = 1
|
||||
|
||||
|
||||
@@ -127,12 +159,6 @@ class TimePieceBalancePercent(Range):
|
||||
range_end = 100
|
||||
|
||||
|
||||
class UmbrellaLogic(Toggle):
|
||||
"""Makes Hat Kid's default punch attack do absolutely nothing, making the Umbrella much more relevant and useful"""
|
||||
display_name = "Umbrella Logic"
|
||||
default = 0
|
||||
|
||||
|
||||
class StartWithCompassBadge(Toggle):
|
||||
"""If enabled, start with the Compass Badge. In Archipelago, the Compass Badge will track all items in the world
|
||||
(instead of just Relics). Recommended if you're not familiar with where item locations are."""
|
||||
@@ -151,6 +177,12 @@ class CompassBadgeMode(Choice):
|
||||
default = 1
|
||||
|
||||
|
||||
class UmbrellaLogic(Toggle):
|
||||
"""Makes Hat Kid's default punch attack do absolutely nothing, making the Umbrella much more relevant and useful"""
|
||||
display_name = "Umbrella Logic"
|
||||
default = 0
|
||||
|
||||
|
||||
class ShuffleStorybookPages(Toggle):
|
||||
"""If enabled, each storybook page in the purple Time Rifts is an item check.
|
||||
The Compass Badge can track these down for you."""
|
||||
@@ -164,6 +196,12 @@ class ShuffleActContracts(Toggle):
|
||||
default = 1
|
||||
|
||||
|
||||
class ShuffleAlpineZiplines(Toggle):
|
||||
"""If enabled, Alpine's zipline paths leading to the peaks will be locked behind items."""
|
||||
display_name = "Shuffle Alpine Ziplines"
|
||||
default = 0
|
||||
|
||||
|
||||
class ShuffleSubconPaintings(Toggle):
|
||||
"""If enabled, shuffle items into the pool that unlock Subcon Forest fire spirit paintings.
|
||||
These items are progressive, with the order of Village-Swamp-Courtyard."""
|
||||
@@ -181,13 +219,138 @@ class StartingChapter(Choice):
|
||||
default = 1
|
||||
|
||||
|
||||
class ChapterCostIncrement(Range):
|
||||
"""Lower values mean chapter costs increase slower. Higher values make the cost differences more steep."""
|
||||
display_name = "Chapter Cost Increment"
|
||||
range_start = 1
|
||||
range_end = 8
|
||||
default = 4
|
||||
|
||||
|
||||
class ChapterCostMinDifference(Range):
|
||||
"""The minimum difference between chapter costs."""
|
||||
display_name = "Minimum Chapter Cost Difference"
|
||||
range_start = 1
|
||||
range_end = 8
|
||||
default = 4
|
||||
|
||||
|
||||
class LowestChapterCost(Range):
|
||||
"""Value determining the lowest possible cost for a chapter.
|
||||
Chapter costs will, progressively, be calculated based on this value (except for the final chapter)."""
|
||||
display_name = "Lowest Possible Chapter Cost"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 5
|
||||
|
||||
|
||||
class HighestChapterCost(Range):
|
||||
"""Value determining the highest possible cost for a chapter.
|
||||
Chapter costs will, progressively, be calculated based on this value (except for the final chapter)."""
|
||||
display_name = "Highest Possible Chapter Cost"
|
||||
range_start = 15
|
||||
range_end = 45
|
||||
default = 25
|
||||
|
||||
|
||||
class FinalChapterMinCost(Range):
|
||||
"""Minimum Time Pieces required to enter the final chapter. This is part of your goal."""
|
||||
display_name = "Final Chapter Minimum Time Piece Cost"
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 30
|
||||
|
||||
|
||||
class FinalChapterMaxCost(Range):
|
||||
"""Maximum Time Pieces required to enter the final chapter. This is part of your goal."""
|
||||
display_name = "Final Chapter Maximum Time Piece Cost"
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 35
|
||||
|
||||
|
||||
class MaxExtraTimePieces(Range):
|
||||
"""Maximum amount of extra Time Pieces from the DLCs.
|
||||
Arctic Cruise will add up to 6. Nyakuza Metro will add up to 10. The absolute maximum is 56."""
|
||||
display_name = "Max Extra Time Pieces"
|
||||
range_start = 0
|
||||
range_end = 16
|
||||
default = 16
|
||||
|
||||
|
||||
class YarnCostMin(Range):
|
||||
"""The minimum possible yarn needed to stitch a hat."""
|
||||
display_name = "Minimum Yarn Cost"
|
||||
range_start = 1
|
||||
range_end = 12
|
||||
default = 4
|
||||
|
||||
|
||||
class YarnCostMax(Range):
|
||||
"""The maximum possible yarn needed to stitch a hat."""
|
||||
display_name = "Maximum Yarn Cost"
|
||||
range_start = 1
|
||||
range_end = 12
|
||||
default = 8
|
||||
|
||||
|
||||
class YarnAvailable(Range):
|
||||
"""How much yarn is available to collect in the item pool."""
|
||||
display_name = "Yarn Available"
|
||||
range_start = 30
|
||||
range_end = 75
|
||||
default = 45
|
||||
|
||||
|
||||
class MinExtraYarn(Range):
|
||||
"""The minimum amount of extra yarn in the item pool.
|
||||
There must be at least this much more yarn over the total amount of yarn needed to craft all hats.
|
||||
For example, if this option's value is 10, and the total yarn needed to craft all hats is 40,
|
||||
there must be at least 50 yarn in the pool."""
|
||||
display_name = "Max Extra Yarn"
|
||||
range_start = 0
|
||||
range_end = 15
|
||||
default = 10
|
||||
|
||||
|
||||
class MinPonCost(Range):
|
||||
"""The minimum amount of Pons that any shop item can cost."""
|
||||
display_name = "Minimum Shop Pon Cost"
|
||||
range_start = 10
|
||||
range_end = 800
|
||||
default = 75
|
||||
|
||||
|
||||
class MaxPonCost(Range):
|
||||
"""The maximum amount of Pons that any shop item can cost."""
|
||||
display_name = "Maximum Shop Pon Cost"
|
||||
range_start = 10
|
||||
range_end = 800
|
||||
default = 300
|
||||
|
||||
|
||||
class BadgeSellerMinItems(Range):
|
||||
"""The smallest amount of items that the Badge Seller can have for sale."""
|
||||
display_name = "Badge Seller Minimum Items"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 4
|
||||
|
||||
|
||||
class BadgeSellerMaxItems(Range):
|
||||
"""The largest amount of items that the Badge Seller can have for sale."""
|
||||
display_name = "Badge Seller Maximum Items"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 8
|
||||
|
||||
|
||||
class CTRWithSprint(Toggle):
|
||||
"""If enabled, clearing Cheating the Race with just Sprint Hat can be in logic."""
|
||||
display_name = "Cheating the Race with Sprint Hat"
|
||||
default = 0
|
||||
|
||||
|
||||
# DLC
|
||||
class EnableDLC1(Toggle):
|
||||
"""Shuffle content from The Arctic Cruise (Chapter 6) into the game. This also includes the Tour time rift.
|
||||
DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE SEAL THE DEAL DLC INSTALLED!!!"""
|
||||
@@ -279,151 +442,118 @@ class BaseballBat(Toggle):
|
||||
default = 0
|
||||
|
||||
|
||||
class ChapterCostIncrement(Range):
|
||||
"""Lower values mean chapter costs increase slower. Higher values make the cost differences more steep."""
|
||||
display_name = "Chapter Cost Increment"
|
||||
range_start = 1
|
||||
range_end = 8
|
||||
default = 4
|
||||
|
||||
|
||||
class ChapterCostMinDifference(Range):
|
||||
"""The minimum difference between chapter costs."""
|
||||
display_name = "Minimum Chapter Cost Difference"
|
||||
range_start = 1
|
||||
range_end = 8
|
||||
default = 5
|
||||
|
||||
|
||||
class LowestChapterCost(Range):
|
||||
"""Value determining the lowest possible cost for a chapter.
|
||||
Chapter costs will, progressively, be calculated based on this value (except for the final chapter)."""
|
||||
display_name = "Lowest Possible Chapter Cost"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 5
|
||||
|
||||
|
||||
class HighestChapterCost(Range):
|
||||
"""Value determining the highest possible cost for a chapter.
|
||||
Chapter costs will, progressively, be calculated based on this value (except for the final chapter)."""
|
||||
display_name = "Highest Possible Chapter Cost"
|
||||
range_start = 15
|
||||
range_end = 45
|
||||
default = 25
|
||||
|
||||
|
||||
class FinalChapterMinCost(Range):
|
||||
"""Minimum Time Pieces required to enter the final chapter. This is part of your goal."""
|
||||
display_name = "Final Chapter Minimum Time Piece Cost"
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 30
|
||||
|
||||
|
||||
class FinalChapterMaxCost(Range):
|
||||
"""Maximum Time Pieces required to enter the final chapter. This is part of your goal."""
|
||||
display_name = "Final Chapter Maximum Time Piece Cost"
|
||||
range_start = 0
|
||||
range_end = 50
|
||||
default = 35
|
||||
|
||||
|
||||
class MaxExtraTimePieces(Range):
|
||||
"""Maximum amount of extra Time Pieces from the DLCs.
|
||||
Arctic Cruise will add up to 6. Nyakuza Metro will add up to 10. The absolute maximum is 56."""
|
||||
display_name = "Max Extra Time Pieces"
|
||||
range_start = 0
|
||||
range_end = 16
|
||||
default = 16
|
||||
|
||||
|
||||
# Death Wish
|
||||
class EnableDeathWish(Toggle):
|
||||
"""NOT IMPLEMENTED Shuffle Death Wish contracts into the game.
|
||||
Each contract by default will have a single check granted upon completion.
|
||||
"""Shuffle Death Wish contracts into the game. Each contract by default will have 1 check granted upon completion.
|
||||
DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE SEAL THE DEAL DLC INSTALLED!!!"""
|
||||
display_name = "Enable Death Wish"
|
||||
default = 0
|
||||
|
||||
|
||||
class DeathWishOnly(Toggle):
|
||||
"""An alternative gameplay mode that allows you to exclusively play Death Wish in a seed.
|
||||
This has the following effects:
|
||||
- Death Wish is instantly unlocked from the start
|
||||
- All hats and other progression items are instantly given to you
|
||||
- Useful items such as Fast Hatter Badge will still be in the item pool instead of in your inventory at the start
|
||||
- All chapters and their levels are unlocked, act shuffle is forced off
|
||||
- Any checks other than Death Wish contracts are completely removed
|
||||
- All Pons in the item pool are replaced with Health Pons or random cosmetics
|
||||
- The EndGoal option is forced to complete Seal the Deal"""
|
||||
display_name = "Death Wish Only"
|
||||
default = 0
|
||||
|
||||
|
||||
class DWShuffle(Toggle):
|
||||
"""An alternative mode for Death Wish where each contract is unlocked one by one, in a random order.
|
||||
Stamp requirements to unlock contracts is removed. Any excluded contracts will not be shuffled into the sequence.
|
||||
If Seal the Deal is the end goal, it will always be the last Death Wish in the sequence.
|
||||
Disabling candles is highly recommended."""
|
||||
display_name = "Death Wish Shuffle"
|
||||
default = 0
|
||||
|
||||
|
||||
class DWShuffleCountMin(Range):
|
||||
"""The minimum number of Death Wishes that can be in the Death Wish shuffle sequence.
|
||||
The final result is clamped at the number of non-excluded Death Wishes."""
|
||||
display_name = "Death Wish Shuffle Minimum Count"
|
||||
range_start = 5
|
||||
range_end = 38
|
||||
default = 18
|
||||
|
||||
|
||||
class DWShuffleCountMax(Range):
|
||||
"""The maximum number of Death Wishes that can be in the Death Wish shuffle sequence.
|
||||
The final result is clamped at the number of non-excluded Death Wishes."""
|
||||
display_name = "Death Wish Shuffle Maximum Count"
|
||||
range_start = 5
|
||||
range_end = 38
|
||||
default = 25
|
||||
|
||||
|
||||
class DWEnableBonus(Toggle):
|
||||
"""NOT IMPLEMENTED In Death Wish, allow the full completion of contracts to reward items."""
|
||||
"""In Death Wish, allow the full completion of contracts to reward items.
|
||||
WARNING!! Only for the brave! This option can create VERY DIFFICULT SEEDS!
|
||||
ONLY turn this on if you know what you are doing to yourself and everyone else in the multiworld!
|
||||
Using Peace and Tranquility to auto-complete the bonuses will NOT count!"""
|
||||
display_name = "Shuffle Death Wish Full Completions"
|
||||
default = 0
|
||||
|
||||
|
||||
class DWAutoCompleteBonuses(Toggle):
|
||||
"""If enabled, auto complete all bonus stamps after completing the main objective in a Death Wish.
|
||||
This option will have no effect if bonus checks (DWEnableBonus) are turned on."""
|
||||
display_name = "Auto Complete Bonus Stamps"
|
||||
default = 1
|
||||
|
||||
|
||||
class DWExcludeAnnoyingContracts(Toggle):
|
||||
"""NOT IMPLEMENTED Exclude Death Wish contracts from the pool that are particularly tedious or take a long time to reach/clear."""
|
||||
"""Exclude Death Wish contracts from the pool that are particularly tedious or take a long time to reach/clear.
|
||||
Excluded Death Wishes are automatically completed as soon as they are unlocked.
|
||||
This option currently excludes the following contracts:
|
||||
- Vault Codes in the Wind
|
||||
- Boss Rush
|
||||
- Camera Tourist
|
||||
- The Mustache Gauntlet
|
||||
- Rift Collapse: Deep Sea
|
||||
- Cruisin' for a Bruisin'
|
||||
- Seal the Deal (non-excluded if goal, but the checks are still excluded)"""
|
||||
display_name = "Exclude Annoying Death Wish Contracts"
|
||||
default = 1
|
||||
|
||||
|
||||
class DWExcludeAnnoyingBonuses(Toggle):
|
||||
"""NOT IMPLEMENTED If Death Wish full completions are shuffled in, exclude particularly tedious Death Wish full completions
|
||||
from the pool. DANGER! DISABLE AT YOUR OWN RISK! THIS OPTION WHEN DISABLED CAN CREATE VERY DIFFICULT SEEDS!!!"""
|
||||
"""If Death Wish full completions are shuffled in, exclude tedious Death Wish full completions from the pool.
|
||||
Excluded bonus Death Wishes automatically reward their bonus stamps upon completion of the main objective.
|
||||
This option currently excludes the following bonuses:
|
||||
- So You're Back From Outer Space
|
||||
- Encore! Encore!
|
||||
- Snatcher's Hit List
|
||||
- 10 Seconds until Self-Destruct
|
||||
- Killing Two Birds
|
||||
- Snatcher Coins in Battle of the Birds
|
||||
- Zero Jumps
|
||||
- Bird Sanctuary
|
||||
- Wound-Up Windmill
|
||||
- Snatcher Coins in Alpine Skyline
|
||||
- Seal the Deal"""
|
||||
display_name = "Exclude Annoying Death Wish Full Completions"
|
||||
default = 1
|
||||
|
||||
|
||||
# Yarn
|
||||
class YarnCostMin(Range):
|
||||
"""The minimum possible yarn needed to stitch each hat."""
|
||||
display_name = "Minimum Yarn Cost"
|
||||
range_start = 1
|
||||
range_end = 12
|
||||
default = 4
|
||||
class DWExcludeCandles(Toggle):
|
||||
"""If enabled, exclude all candle Death Wishes."""
|
||||
display_name = "Exclude Candle Death Wishes"
|
||||
default = 1
|
||||
|
||||
|
||||
class YarnCostMax(Range):
|
||||
"""The maximum possible yarn needed to stitch each hat."""
|
||||
display_name = "Maximum Yarn Cost"
|
||||
range_start = 1
|
||||
range_end = 12
|
||||
default = 8
|
||||
|
||||
|
||||
class YarnAvailable(Range):
|
||||
"""How much yarn is available to collect in the item pool."""
|
||||
display_name = "Yarn Available"
|
||||
range_start = 30
|
||||
range_end = 75
|
||||
default = 45
|
||||
|
||||
|
||||
class MinPonCost(Range):
|
||||
"""The minimum amount of Pons that any shop item can cost."""
|
||||
display_name = "Minimum Shop Pon Cost"
|
||||
range_start = 10
|
||||
range_end = 800
|
||||
default = 75
|
||||
|
||||
|
||||
class MaxPonCost(Range):
|
||||
"""The maximum amount of Pons that any shop item can cost."""
|
||||
display_name = "Maximum Shop Pon Cost"
|
||||
range_start = 10
|
||||
range_end = 800
|
||||
default = 400
|
||||
|
||||
|
||||
class BadgeSellerMinItems(Range):
|
||||
"""The smallest amount of items that the Badge Seller can have for sale."""
|
||||
display_name = "Badge Seller Minimum Items"
|
||||
class DWTimePieceRequirement(Range):
|
||||
"""How many Time Pieces that will be required to unlock Death Wish."""
|
||||
display_name = "Death Wish Time Piece Requirement"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 4
|
||||
range_end = 35
|
||||
default = 15
|
||||
|
||||
|
||||
class BadgeSellerMaxItems(Range):
|
||||
"""The largest amount of items that the Badge Seller can have for sale."""
|
||||
display_name = "Badge Seller Maximum Items"
|
||||
range_start = 0
|
||||
range_end = 10
|
||||
default = 8
|
||||
|
||||
|
||||
# Traps
|
||||
class TrapChance(Range):
|
||||
"""The chance for any junk item in the pool to be replaced by a trap."""
|
||||
display_name = "Trap Chance"
|
||||
@@ -487,7 +617,18 @@ ahit_options: typing.Dict[str, type(Option)] = {
|
||||
"ExcludeTour": ExcludeTour,
|
||||
"ShipShapeCustomTaskGoal": ShipShapeCustomTaskGoal,
|
||||
|
||||
"EnableDeathWish": EnableDeathWish,
|
||||
"EnableDeathWish": EnableDeathWish,
|
||||
"DWShuffle": DWShuffle,
|
||||
"DWShuffleCountMin": DWShuffleCountMin,
|
||||
"DWShuffleCountMax": DWShuffleCountMax,
|
||||
"DeathWishOnly": DeathWishOnly,
|
||||
"DWEnableBonus": DWEnableBonus,
|
||||
"DWAutoCompleteBonuses": DWAutoCompleteBonuses,
|
||||
"DWExcludeAnnoyingContracts": DWExcludeAnnoyingContracts,
|
||||
"DWExcludeAnnoyingBonuses": DWExcludeAnnoyingBonuses,
|
||||
"DWExcludeCandles": DWExcludeCandles,
|
||||
"DWTimePieceRequirement": DWTimePieceRequirement,
|
||||
|
||||
"EnableDLC2": EnableDLC2,
|
||||
"BaseballBat": BaseballBat,
|
||||
"MetroMinPonCost": MetroMinPonCost,
|
||||
@@ -507,6 +648,7 @@ ahit_options: typing.Dict[str, type(Option)] = {
|
||||
"YarnCostMin": YarnCostMin,
|
||||
"YarnCostMax": YarnCostMax,
|
||||
"YarnAvailable": YarnAvailable,
|
||||
"MinExtraYarn": MinExtraYarn,
|
||||
|
||||
"MinPonCost": MinPonCost,
|
||||
"MaxPonCost": MaxPonCost,
|
||||
@@ -523,34 +665,39 @@ ahit_options: typing.Dict[str, type(Option)] = {
|
||||
|
||||
slot_data_options: typing.Dict[str, type(Option)] = {
|
||||
|
||||
"EndGoal": EndGoal,
|
||||
"ActRandomizer": ActRandomizer,
|
||||
"ShuffleAlpineZiplines": ShuffleAlpineZiplines,
|
||||
"LogicDifficulty": LogicDifficulty,
|
||||
"KnowledgeChecks": KnowledgeChecks,
|
||||
"RandomizeHatOrder": RandomizeHatOrder,
|
||||
"UmbrellaLogic": UmbrellaLogic,
|
||||
"CompassBadgeMode": CompassBadgeMode,
|
||||
"ShuffleStorybookPages": ShuffleStorybookPages,
|
||||
"ShuffleActContracts": ShuffleActContracts,
|
||||
"ShuffleSubconPaintings": ShuffleSubconPaintings,
|
||||
"EndGoal": EndGoal,
|
||||
"ActRandomizer": ActRandomizer,
|
||||
"ShuffleAlpineZiplines": ShuffleAlpineZiplines,
|
||||
"LogicDifficulty": LogicDifficulty,
|
||||
"KnowledgeChecks": KnowledgeChecks,
|
||||
"RandomizeHatOrder": RandomizeHatOrder,
|
||||
"UmbrellaLogic": UmbrellaLogic,
|
||||
"CompassBadgeMode": CompassBadgeMode,
|
||||
"ShuffleStorybookPages": ShuffleStorybookPages,
|
||||
"ShuffleActContracts": ShuffleActContracts,
|
||||
"ShuffleSubconPaintings": ShuffleSubconPaintings,
|
||||
|
||||
"EnableDLC1": EnableDLC1,
|
||||
"Tasksanity": Tasksanity,
|
||||
"TasksanityTaskStep": TasksanityTaskStep,
|
||||
"TasksanityCheckCount": TasksanityCheckCount,
|
||||
"ShipShapeCustomTaskGoal": ShipShapeCustomTaskGoal,
|
||||
"ExcludeTour": ExcludeTour,
|
||||
"EnableDLC1": EnableDLC1,
|
||||
"Tasksanity": Tasksanity,
|
||||
"TasksanityTaskStep": TasksanityTaskStep,
|
||||
"TasksanityCheckCount": TasksanityCheckCount,
|
||||
"ShipShapeCustomTaskGoal": ShipShapeCustomTaskGoal,
|
||||
"ExcludeTour": ExcludeTour,
|
||||
|
||||
"EnableDeathWish": EnableDeathWish,
|
||||
"EnableDeathWish": EnableDeathWish,
|
||||
"DWShuffle": DWShuffle,
|
||||
"DeathWishOnly": DeathWishOnly,
|
||||
"DWEnableBonus": DWEnableBonus,
|
||||
"DWAutoCompleteBonuses": DWAutoCompleteBonuses,
|
||||
"DWTimePieceRequirement": DWTimePieceRequirement,
|
||||
|
||||
"EnableDLC2": EnableDLC2,
|
||||
"MetroMinPonCost": MetroMinPonCost,
|
||||
"MetroMaxPonCost": MetroMaxPonCost,
|
||||
"BaseballBat": BaseballBat,
|
||||
"EnableDLC2": EnableDLC2,
|
||||
"MetroMinPonCost": MetroMinPonCost,
|
||||
"MetroMaxPonCost": MetroMaxPonCost,
|
||||
"BaseballBat": BaseballBat,
|
||||
|
||||
"MinPonCost": MinPonCost,
|
||||
"MaxPonCost": MaxPonCost,
|
||||
"MinPonCost": MinPonCost,
|
||||
"MaxPonCost": MaxPonCost,
|
||||
|
||||
"death_link": DeathLink,
|
||||
"death_link": DeathLink,
|
||||
}
|
||||
|
||||
@@ -254,12 +254,16 @@ blacklisted_acts = {
|
||||
|
||||
# Blacklisted act shuffle combinations to help prevent impossible layouts. Mostly for free roam acts.
|
||||
blacklisted_combos = {
|
||||
"The Illness has Spread": ["Alpine Free Roam"],
|
||||
"Rush Hour": ["Nyakuza Free Roam"],
|
||||
"Time Rift - The Owl Express": ["Alpine Free Roam", "Nyakuza Free Roam"],
|
||||
"The Illness has Spread": ["Nyakuza Free Roam", "Alpine Free Roam"],
|
||||
"Rush Hour": ["Nyakuza Free Roam", "Alpine Free Roam"],
|
||||
"Time Rift - The Owl Express": ["Alpine Free Roam", "Nyakuza Free Roam", "Bon Voyage!"],
|
||||
"Time Rift - The Moon": ["Alpine Free Roam", "Nyakuza Free Roam"],
|
||||
"Time Rift - Dead Bird Studio": ["Alpine Free Roam", "Nyakuza Free Roam"],
|
||||
"Time Rift - Curly Tail Trail": ["Nyakuza Free Roam"],
|
||||
"Time Rift - The Twilight Bell": ["Nyakuza Free Roam"],
|
||||
"Time Rift - Alpine Skyline": ["Nyakuza Free Roam"],
|
||||
"Time Rift - Rumbi Factory": ["Alpine Free Roam"],
|
||||
"Time Rift - Deep Sea": ["Alpine Free Roam", "Nyakuza Free Roam"],
|
||||
}
|
||||
|
||||
|
||||
@@ -271,6 +275,11 @@ def create_regions(world: World):
|
||||
# ------------------------------------------- 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"))
|
||||
|
||||
@@ -418,8 +427,8 @@ 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 = "{name} Portal - Entrance {num}"
|
||||
connect_regions(act_region, region, entrance_name.format(name=region.name, num=i), world.player)
|
||||
entrance_name = f"{region.name} Portal - Entrance {i}"
|
||||
connect_regions(act_region, region, entrance_name, world.player)
|
||||
i += 1
|
||||
|
||||
|
||||
@@ -427,7 +436,7 @@ 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.multiworld.TasksanityCheckCount[world.player].value):
|
||||
location = HatInTimeLocation(world.player, format("Tasksanity Check %i" % (i+1)), id_start+i, ship_shape)
|
||||
location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape)
|
||||
ship_shape.locations.append(location)
|
||||
|
||||
|
||||
@@ -603,7 +612,7 @@ def randomize_act_entrances(world: World):
|
||||
|
||||
candidate: Region
|
||||
if len(candidate_list) > 0:
|
||||
candidate = candidate_list[world.multiworld.random.randint(0, len(candidate_list)-1)]
|
||||
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:
|
||||
@@ -619,7 +628,7 @@ def randomize_act_entrances(world: World):
|
||||
if region.name in rift_access_regions.keys():
|
||||
rift_dict.setdefault(region.name, candidate)
|
||||
|
||||
world.update_chapter_act_info(region, candidate)
|
||||
update_chapter_act_info(world, region, candidate)
|
||||
continue
|
||||
|
||||
if region.name in rift_access_regions.keys():
|
||||
@@ -634,14 +643,14 @@ def randomize_act_entrances(world: World):
|
||||
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)
|
||||
|
||||
world.update_chapter_act_info(region, 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)
|
||||
world.update_chapter_act_info(region, region)
|
||||
update_chapter_act_info(world, region, region)
|
||||
|
||||
set_rift_rules(world, rift_dict)
|
||||
|
||||
@@ -650,7 +659,7 @@ 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 = format("%s Portal - Entrance %i" % (time_rift.name, i))
|
||||
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
|
||||
@@ -686,6 +695,9 @@ 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
|
||||
|
||||
@@ -710,11 +722,11 @@ def create_badge_seller(world: World) -> Region:
|
||||
max_items: int = 0
|
||||
|
||||
if world.multiworld.BadgeSellerMaxItems[world.player].value > 0:
|
||||
max_items = world.multiworld.random.randint(world.multiworld.BadgeSellerMinItems[world.player].value,
|
||||
max_items = world.random.randint(world.multiworld.BadgeSellerMinItems[world.player].value,
|
||||
world.multiworld.BadgeSellerMaxItems[world.player].value)
|
||||
|
||||
if max_items <= 0:
|
||||
world.badge_seller_count = 0
|
||||
world.set_badge_seller_count(0)
|
||||
return badge_seller
|
||||
|
||||
for (key, data) in shop_locations.items():
|
||||
@@ -729,14 +741,15 @@ def create_badge_seller(world: World) -> Region:
|
||||
if count >= max_items:
|
||||
break
|
||||
|
||||
world.badge_seller_count = max_items
|
||||
world.set_badge_seller_count(max_items)
|
||||
return badge_seller
|
||||
|
||||
|
||||
def connect_regions(start_region: Region, exit_region: Region, entrancename: str, player: int):
|
||||
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.
|
||||
@@ -785,12 +798,29 @@ 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 = world.multiworld.NyakuzaThugMinShopItems[world.player].value
|
||||
max_items: int = world.multiworld.NyakuzaThugMaxShopItems[world.player].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 == "":
|
||||
@@ -800,14 +830,14 @@ def create_thug_shops(world: World):
|
||||
continue
|
||||
|
||||
try:
|
||||
if world.nyakuza_thug_items[data.nyakuza_thug] <= 0:
|
||||
if thug_items[data.nyakuza_thug] <= 0:
|
||||
continue
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if count == -1:
|
||||
count = world.multiworld.random.randint(min_items, max_items)
|
||||
world.nyakuza_thug_items.setdefault(data.nyakuza_thug, count)
|
||||
count = world.random.randint(min_items, max_items)
|
||||
thug_items.setdefault(data.nyakuza_thug, count)
|
||||
if count <= 0:
|
||||
continue
|
||||
|
||||
@@ -823,6 +853,8 @@ def create_thug_shops(world: World):
|
||||
step = 0
|
||||
count = -1
|
||||
|
||||
world.set_nyakuza_thug_items(thug_items)
|
||||
|
||||
|
||||
def create_events(world: World) -> int:
|
||||
count: int = 0
|
||||
@@ -832,9 +864,10 @@ def create_events(world: World) -> int:
|
||||
continue
|
||||
|
||||
event: Location = create_event(name, world.multiworld.get_region(data.region, world.player), world)
|
||||
event.show_in_spoiler = False
|
||||
|
||||
if data.act_complete_event:
|
||||
act_completion: str = format("Act Completion (%s)" % data.region)
|
||||
act_completion: str = f"Act Completion ({data.region})"
|
||||
event.access_rule = world.multiworld.get_location(act_completion, world.player).access_rule
|
||||
|
||||
count += 1
|
||||
|
||||
@@ -37,8 +37,9 @@ def can_use_hat(state: CollectionState, world: World, hat: HatType) -> bool:
|
||||
|
||||
def get_hat_cost(world: World, hat: HatType) -> int:
|
||||
cost: int = 0
|
||||
costs = world.get_hat_yarn_costs()
|
||||
for h in world.get_hat_craft_order():
|
||||
cost += world.get_hat_yarn_costs().get(h)
|
||||
cost += costs[h]
|
||||
if h == hat:
|
||||
break
|
||||
|
||||
@@ -120,7 +121,7 @@ def can_clear_act(state: CollectionState, world: World, act_entrance: str) -> bo
|
||||
if "Free Roam" in entrance.connected_region.name:
|
||||
return True
|
||||
|
||||
name: str = format("Act Completion (%s)" % entrance.connected_region.name)
|
||||
name: str = f"Act Completion ({entrance.connected_region.name})"
|
||||
return world.multiworld.get_location(name, world.player).access_rule(state)
|
||||
|
||||
|
||||
@@ -153,6 +154,9 @@ def set_rules(world: World):
|
||||
if world.multiworld.EndGoal[world.player].value == 2:
|
||||
final_chapter = ChapterIndex.METRO
|
||||
chapter_list.append(ChapterIndex.FINALE)
|
||||
elif world.multiworld.EndGoal[world.player].value == 3:
|
||||
final_chapter = None
|
||||
chapter_list.append(ChapterIndex.FINALE)
|
||||
|
||||
if world.is_dlc1():
|
||||
chapter_list.append(ChapterIndex.CRUISE)
|
||||
@@ -161,7 +165,7 @@ def set_rules(world: World):
|
||||
chapter_list.append(ChapterIndex.METRO)
|
||||
|
||||
chapter_list.remove(starting_chapter)
|
||||
world.multiworld.random.shuffle(chapter_list)
|
||||
world.random.shuffle(chapter_list)
|
||||
|
||||
if starting_chapter is not ChapterIndex.ALPINE and (world.is_dlc1() or world.is_dlc2()):
|
||||
index1: int = 69
|
||||
@@ -180,7 +184,7 @@ def set_rules(world: World):
|
||||
if lowest_index == 0:
|
||||
pos = 0
|
||||
else:
|
||||
pos = world.multiworld.random.randint(0, lowest_index)
|
||||
pos = world.random.randint(0, lowest_index)
|
||||
|
||||
chapter_list.insert(pos, ChapterIndex.ALPINE)
|
||||
|
||||
@@ -190,7 +194,7 @@ def set_rules(world: World):
|
||||
if index >= len(chapter_list):
|
||||
chapter_list.append(ChapterIndex.METRO)
|
||||
else:
|
||||
chapter_list.insert(world.multiworld.random.randint(index+1, len(chapter_list)), ChapterIndex.METRO)
|
||||
chapter_list.insert(world.random.randint(index+1, len(chapter_list)), ChapterIndex.METRO)
|
||||
|
||||
lowest_cost: int = world.multiworld.LowestChapterCost[world.player].value
|
||||
highest_cost: int = world.multiworld.HighestChapterCost[world.player].value
|
||||
@@ -206,10 +210,9 @@ def set_rules(world: World):
|
||||
if min_range >= highest_cost:
|
||||
min_range = highest_cost-1
|
||||
|
||||
value: int = world.multiworld.random.randint(min_range, min(highest_cost,
|
||||
max(lowest_cost, last_cost + cost_increment)))
|
||||
value: int = world.random.randint(min_range, min(highest_cost, max(lowest_cost, last_cost + cost_increment)))
|
||||
|
||||
cost = world.multiworld.random.randint(value, min(value + cost_increment, highest_cost))
|
||||
cost = world.random.randint(value, min(value + cost_increment, highest_cost))
|
||||
if loop_count >= 1:
|
||||
if last_cost + min_difference > cost:
|
||||
cost = last_cost + min_difference
|
||||
@@ -219,9 +222,10 @@ def set_rules(world: World):
|
||||
last_cost = cost
|
||||
loop_count += 1
|
||||
|
||||
world.set_chapter_cost(final_chapter, world.multiworld.random.randint(
|
||||
world.multiworld.FinalChapterMinCost[world.player].value,
|
||||
world.multiworld.FinalChapterMaxCost[world.player].value))
|
||||
if final_chapter is not None:
|
||||
world.set_chapter_cost(final_chapter, world.random.randint(
|
||||
world.multiworld.FinalChapterMinCost[world.player].value,
|
||||
world.multiworld.FinalChapterMaxCost[world.player].value))
|
||||
|
||||
add_rule(world.multiworld.get_entrance("Telescope -> Mafia Town", world.player),
|
||||
lambda state: state.has("Time Piece", world.player, world.get_chapter_cost(ChapterIndex.MAFIA)))
|
||||
@@ -277,7 +281,7 @@ def set_rules(world: World):
|
||||
|
||||
for hat in data.required_hats:
|
||||
if hat is not HatType.NONE:
|
||||
add_rule(location, lambda state, hat=hat: can_use_hat(state, world, hat))
|
||||
add_rule(location, lambda state, h=hat: can_use_hat(state, world, h))
|
||||
|
||||
if data.hookshot:
|
||||
add_rule(location, lambda state: can_use_hookshot(state, world))
|
||||
@@ -294,6 +298,9 @@ def set_rules(world: World):
|
||||
elif data.hit_requirement == 2: # Can bypass with Dweller Mask (dweller bells)
|
||||
add_rule(location, lambda state: can_hit(state, world) or can_use_hat(state, world, HatType.DWELLER))
|
||||
|
||||
for misc in data.misc_required:
|
||||
add_rule(location, lambda state, item=misc: state.has(item, world.player))
|
||||
|
||||
if get_difficulty(world) >= 1:
|
||||
world.multiworld.KnowledgeChecks[world.player].value = 1
|
||||
|
||||
@@ -316,14 +323,14 @@ def set_rules(world: World):
|
||||
act_entrance: Entrance = world.multiworld.get_entrance(act, world.player)
|
||||
access_rules.append(act_entrance.access_rule)
|
||||
required_region = act_entrance.connected_region
|
||||
name: str = format("%s: Connection %i" % (key, i))
|
||||
name: str = f"{key}: Connection {i}"
|
||||
new_entrance: Entrance = connect_regions(required_region, region, name, world.player)
|
||||
entrances.append(new_entrance)
|
||||
|
||||
# Copy access rules from act completions
|
||||
if "Free Roam" not in required_region.name:
|
||||
rule: typing.Callable[[CollectionState], bool]
|
||||
name = format("Act Completion (%s)" % required_region.name)
|
||||
name = f"Act Completion ({required_region.name})"
|
||||
rule = world.multiworld.get_location(name, world.player).access_rule
|
||||
access_rules.append(rule)
|
||||
|
||||
@@ -558,9 +565,9 @@ def set_mafia_town_rules(world: World):
|
||||
|
||||
def set_subcon_rules(world: World):
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
lambda state: state.can_reach("Toilet of Doom", "Region", world.player)
|
||||
lambda state: state.has("TOD Access", world.player) and can_use_hookshot(state, world)
|
||||
and (not painting_logic(world) or has_paintings(state, world, 1))
|
||||
or state.can_reach("Your Contract has Expired", "Region", world.player))
|
||||
or state.has("YCHE Access", world.player))
|
||||
|
||||
if world.multiworld.UmbrellaLogic[world.player].value > 0:
|
||||
add_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
|
||||
@@ -583,6 +590,9 @@ def set_subcon_rules(world: World):
|
||||
lambda state: state.has("Snatcher's Contract - Mail Delivery Service", world.player))
|
||||
|
||||
if painting_logic(world):
|
||||
add_rule(world.multiworld.get_location("Act Completion (Contractual Obligations)", world.player),
|
||||
lambda state: state.has("Progressive Painting Unlock", world.player))
|
||||
|
||||
for key in contract_locations:
|
||||
if key == "Snatcher's Contract - The Subcon Well":
|
||||
continue
|
||||
@@ -679,7 +689,7 @@ def reg_act_connection(world: World, region: typing.Union[str, Region], unlocked
|
||||
|
||||
|
||||
# See randomize_act_entrances in Regions.py
|
||||
# Called BEFORE set_rules!
|
||||
# Called before set_rules
|
||||
def set_rift_rules(world: World, regions: typing.Dict[str, Region]):
|
||||
|
||||
# This is accessing the regions in place of these time rifts, so we can set the rules on all the entrances.
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
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, get_tasksanity_start_id
|
||||
|
||||
from .Types import HatDLC, HatType, ChapterIndex
|
||||
from .Options import ahit_options, slot_data_options, adjust_options
|
||||
from worlds.AutoWorld import World
|
||||
from BaseClasses import Item, ItemClassification, LocationProgressType, Tutorial
|
||||
from .Items import HatInTimeItem, item_table, create_item, relic_groups, act_contracts, create_itempool
|
||||
from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region
|
||||
from .Locations import location_table, contract_locations, is_location_valid, get_location_names, get_tasksanity_start_id
|
||||
from .Rules import set_rules
|
||||
import typing
|
||||
from .Options import ahit_options, slot_data_options, adjust_options
|
||||
from .Types import HatType, ChapterIndex
|
||||
from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes
|
||||
from .DeathWishRules import set_dw_rules
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from typing import List, Dict, TextIO
|
||||
|
||||
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]] = {}
|
||||
hat_craft_order: Dict[int, List[HatType]] = {}
|
||||
hat_yarn_costs: Dict[int, Dict[HatType, int]] = {}
|
||||
chapter_timepiece_costs: Dict[int, Dict[ChapterIndex, int]] = {}
|
||||
excluded_dws: Dict[int, List[str]] = {}
|
||||
excluded_bonuses: Dict[int, List[str]] = {}
|
||||
dw_shuffle: Dict[int, List[str]] = {}
|
||||
nyakuza_thug_items: Dict[int, Dict[str, int]] = {}
|
||||
badge_seller_count: Dict[int, int] = {}
|
||||
|
||||
|
||||
class AWebInTime(WebWorld):
|
||||
theme = "partyTime"
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide for setting up A Hat in Time to be played in Archipelago.",
|
||||
"English",
|
||||
"ahit_en.md",
|
||||
"setup/en",
|
||||
["CookieCat"]
|
||||
)]
|
||||
|
||||
|
||||
class HatInTimeWorld(World):
|
||||
"""
|
||||
A Hat in Time is a cute-as-heck 3D platformer featuring a little girl who stitches hats for wicked powers!
|
||||
A Hat in Time is a cute-as-peck 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!
|
||||
"""
|
||||
|
||||
@@ -34,37 +45,50 @@ class HatInTimeWorld(World):
|
||||
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] = []
|
||||
act_connections: Dict[str, str] = {}
|
||||
shop_locs: List[str] = []
|
||||
item_name_groups = relic_groups
|
||||
badge_seller_count: int = 0
|
||||
|
||||
def generate_early(self):
|
||||
adjust_options(self)
|
||||
|
||||
if self.multiworld.StartWithCompassBadge[self.player].value > 0:
|
||||
self.multiworld.push_precollected(self.create_item("Compass Badge"))
|
||||
|
||||
if self.is_dw_only():
|
||||
return
|
||||
|
||||
# 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 self.multiworld.ActRandomizer[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 = {}
|
||||
excluded_dws[self.player] = []
|
||||
excluded_bonuses[self.player] = []
|
||||
dw_shuffle[self.player] = []
|
||||
nyakuza_thug_items[self.player] = {}
|
||||
badge_seller_count[self.player] = 0
|
||||
self.shop_locs = []
|
||||
self.badge_seller_count = 0
|
||||
self.topology_present = self.multiworld.ActRandomizer[self.player].value
|
||||
|
||||
create_regions(self)
|
||||
|
||||
if self.multiworld.EnableDeathWish[self.player].value > 0:
|
||||
create_dw_regions(self)
|
||||
|
||||
if self.is_dw_only():
|
||||
return
|
||||
|
||||
create_events(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():
|
||||
@@ -82,69 +106,13 @@ class HatInTimeWorld(World):
|
||||
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)
|
||||
|
||||
for i in range(int(len(yarn_pool) * (0.01 * self.multiworld.YarnBalancePercent[self.player].value))):
|
||||
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])
|
||||
self.random.shuffle(hat_craft_order[self.player])
|
||||
if self.multiworld.RandomizeHatOrder[self.player].value == 2:
|
||||
hat_craft_order[self.player].remove(HatType.TIME_STOP)
|
||||
hat_craft_order[self.player].append(HatType.TIME_STOP)
|
||||
|
||||
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.is_dlc1():
|
||||
max_extra += 6
|
||||
|
||||
if self.is_dlc2():
|
||||
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)
|
||||
|
||||
for i in range(int(len(tp_list) * (0.01 * self.multiworld.TimePieceBalancePercent[self.player].value))):
|
||||
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
|
||||
self.multiworld.itempool += create_itempool(self)
|
||||
|
||||
def set_rules(self):
|
||||
self.act_connections = {}
|
||||
@@ -156,11 +124,37 @@ class HatInTimeWorld(World):
|
||||
ChapterIndex.CRUISE: -1,
|
||||
ChapterIndex.METRO: -1}
|
||||
|
||||
if self.is_dw_only():
|
||||
# we already have all items if this is the case, no need for rules
|
||||
self.multiworld.push_precollected(HatInTimeItem("Death Wish Only Mode", ItemClassification.progression,
|
||||
None, self.player))
|
||||
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Death Wish Only Mode",
|
||||
self.player)
|
||||
|
||||
if self.multiworld.DWEnableBonus[self.player].value == 0:
|
||||
for name in death_wishes:
|
||||
if name == "Snatcher Coins in Nyakuza Metro" and not self.is_dlc2():
|
||||
continue
|
||||
|
||||
if self.multiworld.DWShuffle[self.player].value > 0 and name not in self.get_dw_shuffle():
|
||||
continue
|
||||
|
||||
full_clear = self.multiworld.get_location(f"{name} - All Clear", self.player)
|
||||
full_clear.address = None
|
||||
full_clear.place_locked_item(HatInTimeItem("Nothing", ItemClassification.filler, None, self.player))
|
||||
full_clear.show_in_spoiler = False
|
||||
|
||||
return
|
||||
|
||||
if self.multiworld.ActRandomizer[self.player].value > 0:
|
||||
randomize_act_entrances(self)
|
||||
|
||||
set_rules(self)
|
||||
|
||||
if self.multiworld.EnableDeathWish[self.player].value > 0:
|
||||
set_dw_rules(self)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return create_item(self, name)
|
||||
|
||||
@@ -182,16 +176,39 @@ class HatInTimeWorld(World):
|
||||
"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,
|
||||
"BadgeSellerItemCount": badge_seller_count[self.player],
|
||||
"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.is_dlc2():
|
||||
for name in self.nyakuza_thug_items.keys():
|
||||
slot_data[name] = self.nyakuza_thug_items[name]
|
||||
if self.is_dlc2() and not self.is_dw_only():
|
||||
for name in nyakuza_thug_items[self.player].keys():
|
||||
slot_data[name] = nyakuza_thug_items[self.player][name]
|
||||
|
||||
if self.is_dw():
|
||||
i: int = 0
|
||||
for name in excluded_dws[self.player]:
|
||||
if self.multiworld.EndGoal[self.player].value == 3 and name == "Seal the Deal":
|
||||
continue
|
||||
|
||||
slot_data[f"excluded_dw{i}"] = dw_classes[name]
|
||||
i += 1
|
||||
|
||||
i = 0
|
||||
if self.multiworld.DWAutoCompleteBonuses[self.player].value == 0:
|
||||
for name in excluded_bonuses[self.player]:
|
||||
if name in excluded_dws[self.player]:
|
||||
continue
|
||||
|
||||
slot_data[f"excluded_bonus{i}"] = dw_classes[name]
|
||||
i += 1
|
||||
|
||||
if self.multiworld.DWShuffle[self.player].value > 0:
|
||||
shuffled_dws = self.get_dw_shuffle()
|
||||
for i in range(len(shuffled_dws)):
|
||||
slot_data[f"dw_{i}"] = dw_classes[shuffled_dws[i]]
|
||||
|
||||
for option_name in slot_data_options:
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
@@ -199,7 +216,10 @@ class HatInTimeWorld(World):
|
||||
|
||||
return slot_data
|
||||
|
||||
def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
|
||||
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]):
|
||||
if self.is_dw_only():
|
||||
return
|
||||
|
||||
new_hint_data = {}
|
||||
alpine_regions = ["The Birdhouse", "The Lava Cake", "The Windmill", "The Twilight Bell", "Alpine Skyline Area"]
|
||||
metro_regions = ["Yellow Overpass Station", "Green Clean Station", "Bluefin Tunnel", "Pink Paw Station"]
|
||||
@@ -220,63 +240,28 @@ class HatInTimeWorld(World):
|
||||
else:
|
||||
continue
|
||||
|
||||
new_hint_data[location.address] = self.get_shuffled_region(region_name)
|
||||
new_hint_data[location.address] = get_shuffled_region(self, region_name)
|
||||
|
||||
if self.is_dlc1() and self.multiworld.Tasksanity[self.player].value > 0:
|
||||
ship_shape_region = self.get_shuffled_region("Ship Shape")
|
||||
ship_shape_region = get_shuffled_region(self, "Ship Shape")
|
||||
id_start: int = get_tasksanity_start_id()
|
||||
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):
|
||||
def write_spoiler_header(self, spoiler_handle: 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
|
||||
return chapter_timepiece_costs[self.player][chapter]
|
||||
|
||||
def get_hat_craft_order(self):
|
||||
return hat_craft_order[self.player]
|
||||
@@ -295,3 +280,47 @@ class HatInTimeWorld(World):
|
||||
|
||||
def is_dw(self) -> bool:
|
||||
return self.multiworld.EnableDeathWish[self.player].value > 0
|
||||
|
||||
def is_dw_only(self) -> bool:
|
||||
return self.is_dw() and self.multiworld.DeathWishOnly[self.player].value > 0
|
||||
|
||||
def get_excluded_dws(self):
|
||||
return excluded_dws[self.player]
|
||||
|
||||
def get_excluded_bonuses(self):
|
||||
return excluded_bonuses[self.player]
|
||||
|
||||
def is_dw_excluded(self, name: str) -> bool:
|
||||
# don't exclude Seal the Deal if it's our goal
|
||||
if self.multiworld.EndGoal[self.player].value == 3 and name == "Seal the Deal" \
|
||||
and f"{name} - Main Objective" not in self.multiworld.exclude_locations[self.player]:
|
||||
return False
|
||||
|
||||
if name in excluded_dws[self.player]:
|
||||
return True
|
||||
|
||||
return f"{name} - Main Objective" in self.multiworld.exclude_locations[self.player]
|
||||
|
||||
def is_bonus_excluded(self, name: str) -> bool:
|
||||
if self.is_dw_excluded(name) or name in excluded_bonuses[self.player]:
|
||||
return True
|
||||
|
||||
return f"{name} - All Clear" in self.multiworld.exclude_locations[self.player]
|
||||
|
||||
def get_dw_shuffle(self):
|
||||
return dw_shuffle[self.player]
|
||||
|
||||
def set_dw_shuffle(self, shuffle: List[str]):
|
||||
dw_shuffle[self.player] = shuffle
|
||||
|
||||
def get_badge_seller_count(self) -> int:
|
||||
return badge_seller_count[self.player]
|
||||
|
||||
def set_badge_seller_count(self, value: int):
|
||||
badge_seller_count[self.player] = value
|
||||
|
||||
def get_nyakuza_thug_items(self):
|
||||
return nyakuza_thug_items[self.player]
|
||||
|
||||
def set_nyakuza_thug_items(self, items: Dict[str, int]):
|
||||
nyakuza_thug_items[self.player] = items
|
||||
|
||||
Reference in New Issue
Block a user