 16ae8449f4
			
		
	
	16ae8449f4
	
	
	
		
			
			* duh * Fuck it * Major fixes * a * b * Even more fixes * New option - NoFreeRoamFinale * a * Hat Logic Fix * Just to be safe * multiworld.random to world.random * KeyError fix * Update .gitignore * Update __init__.py * Zoinks Scoob * ffs * Ruh Roh Raggy, more r-r-r-random bugs! * 0.9b - cleanup + expanded logic difficulty * Update Rules.py * Update Regions.py * AttributeError fix * 0.10b - New Options * 1.0 Preparations * Docs * Docs 2 * Fixes * Update __init__.py * Fixes * variable capture my beloathed * Fixes * a * 10 Seconds logic fix * 1.1 * 1.2 * a * New client * More client changes * 1.3 * Final touch-ups for 1.3 * 1.3.1 * 1.3.3 * Zero Jumps gen error fix * more fixes * Formatting improvements * typo * Update __init__.py * Revert "Update __init__.py" This reverts commit e178a7c0a6904ace803241cab3021d7b97177e90. * init * Update to new options API * Missed some * Snatcher Coins fix * Missed some more * some slight touch ups * rewind * a * fix things * Revert "Merge branch 'main' of https://github.com/CookieCat45/Archipelago-ahit" This reverts commit a2360fe197e77a723bb70006c5eb5725c7ed3826, reversing changes made to b8948bc4958855c6e342e18bdb8dc81cfcf09455. * Update .gitignore * 1.3.6 * Final touch-ups * Fix client and leftover old options api * Delete setup-ahitclient.py * Update .gitignore * old python version fix * proper warnings for invalid act plandos * Update worlds/ahit/docs/en_A Hat in Time.md Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com> * Update worlds/ahit/docs/setup_en.md Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com> * 120 char per line * "settings" to "options" * Update DeathWishRules.py * Update worlds/ahit/docs/en_A Hat in Time.md Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * No more loading the data package * cleanup + act plando fixes * almost forgot * Update Rules.py * a * Update worlds/ahit/Options.py Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * Options stuff * oop * no unnecessary type hints * warn about depot download length in setup guide * Update worlds/ahit/Options.py Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * typo Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * Update worlds/ahit/Rules.py Co-authored-by: Ixrec <ericrhitchcock@gmail.com> * review stuff * More stuff from review * comment * 1.5 Update * link fix? * link fix 2 * Update setup_en.md * Update setup_en.md * Update setup_en.md * Evil * Good fucking lord * Review stuff again + Logic fixes * More review stuff * Even more review stuff - we're almost done * DW review stuff * Finish up review stuff * remove leftover stuff * a * assert item * add A Hat in Time to readme/codeowners files * Fix range options not being corrected properly * 120 chars per line in docs * Update worlds/ahit/Regions.py Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> * Update worlds/ahit/DeathWishLocations.py Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> * Remove some unnecessary option.class.value * Remove data_version and more option.class.value * Update worlds/ahit/Items.py Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> * Remove the rest of option.class.value * Update worlds/ahit/DeathWishLocations.py Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> * review stuff * Replace connect_regions with Region.connect * review stuff * Remove unnecessary Optional from LocData * Remove HatType.NONE * Update worlds/ahit/test/TestActs.py Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> * fix so default tests actually don't run * Improve performance for death wish rules * rename test file * change test imports * 1000 is probably unnecessary * a * change state.count to state.has * stuff * starting inventory hats fix * shouldn't have done this lol * make ship shape task goal equal to number of tasksanity checks if set to 0 * a * change act shuffle starting acts + logic updates * dumb * option groups + lambda capture cringe + typo * a * b * missing option in groups * c * Fix Your Contract Has Expired being placed on first level when it shouldn't * formatting * major logic bug fix for death wish --------- Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com> Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Co-authored-by: Ixrec <ericrhitchcock@gmail.com> Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com> Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
		
			
				
	
	
		
			463 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			463 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from worlds.AutoWorld import CollectionState
 | |
| from .Rules import can_use_hat, can_use_hookshot, can_hit, zipline_logic, get_difficulty, has_paintings
 | |
| from .Types import HatType, Difficulty, HatInTimeLocation, HatInTimeItem, LocData, HitType
 | |
| from .DeathWishLocations import dw_prereqs, dw_candles
 | |
| from BaseClasses import Entrance, Location, ItemClassification
 | |
| from worlds.generic.Rules import add_rule, set_rule
 | |
| from typing import List, Callable, TYPE_CHECKING
 | |
| from .Locations import death_wishes
 | |
| from .Options import EndGoal
 | |
| 
 | |
| if TYPE_CHECKING:
 | |
|     from . import HatInTimeWorld
 | |
| 
 | |
| 
 | |
| # Any speedruns expect the player to have Sprint Hat
 | |
| dw_requirements = {
 | |
|     "Beat the Heat": LocData(hit_type=HitType.umbrella),
 | |
|     "So You're Back From Outer Space": LocData(hookshot=True),
 | |
|     "Mafia's Jumps": LocData(required_hats=[HatType.ICE]),
 | |
|     "Vault Codes in the Wind": LocData(required_hats=[HatType.SPRINT]),
 | |
| 
 | |
|     "Security Breach": LocData(hit_type=HitType.umbrella_or_brewing),
 | |
|     "10 Seconds until Self-Destruct": LocData(hookshot=True),
 | |
|     "Community Rift: Rhythm Jump Studio": LocData(required_hats=[HatType.ICE]),
 | |
| 
 | |
|     "Speedrun Well": LocData(hookshot=True, hit_type=HitType.umbrella_or_brewing),
 | |
|     "Boss Rush": LocData(hit_type=HitType.umbrella, hookshot=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),
 | |
|     "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),
 | |
| }
 | |
| 
 | |
| # 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", "Badge Pin"]),
 | |
|     "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 Illness has Speedrun": LocData(required_hats=[HatType.SPRINT]),
 | |
| 
 | |
|     "The Mustache Gauntlet": LocData(required_hats=[HatType.ICE]),
 | |
| 
 | |
|     "Rift Collapse: Deep Sea": LocData(required_hats=[HatType.DWELLER]),
 | |
| }
 | |
| 
 | |
| 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,
 | |
| }
 | |
| 
 | |
| required_snatcher_coins = {
 | |
|     "Snatcher Coins in Mafia Town": ["Snatcher Coin - Top of HQ", "Snatcher Coin - Top of Tower",
 | |
|                                      "Snatcher Coin - Under Ruined Tower"],
 | |
| 
 | |
|     "Snatcher Coins in Battle of the Birds": ["Snatcher Coin - Top of Red House", "Snatcher Coin - Train Rush",
 | |
|                                               "Snatcher Coin - Picture Perfect"],
 | |
| 
 | |
|     "Snatcher Coins in Subcon Forest": ["Snatcher Coin - Swamp Tree", "Snatcher Coin - Manor Roof",
 | |
|                                         "Snatcher Coin - Giant Time Piece"],
 | |
| 
 | |
|     "Snatcher Coins in Alpine Skyline": ["Snatcher Coin - Goat Village Top", "Snatcher Coin - Lava Cake",
 | |
|                                          "Snatcher Coin - Windmill"],
 | |
| 
 | |
|     "Snatcher Coins in Nyakuza Metro": ["Snatcher Coin - Green Clean Tower", "Snatcher Coin - Bluefin Cat Train",
 | |
|                                         "Snatcher Coin - Pink Paw Fence"],
 | |
| }
 | |
| 
 | |
| 
 | |
| def set_dw_rules(world: "HatInTimeWorld"):
 | |
|     if "Snatcher's Hit List" not in world.excluded_dws or "Camera Tourist" not in world.excluded_dws:
 | |
|         set_enemy_rules(world)
 | |
| 
 | |
|     dw_list: List[str] = []
 | |
|     if world.options.DWShuffle:
 | |
|         dw_list = world.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)
 | |
|         if not world.options.DWShuffle and name in dw_stamp_costs.keys():
 | |
|             for entrance in dw.entrances:
 | |
|                 add_rule(entrance, lambda state, n=name: state.has("Stamps", world.player, dw_stamp_costs[n]))
 | |
| 
 | |
|         main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player)
 | |
|         all_clear = world.multiworld.get_location(f"{name} - All Clear", world.player)
 | |
|         main_stamp = world.multiworld.get_location(f"Main Stamp - {name}", world.player)
 | |
|         bonus_stamps = world.multiworld.get_location(f"Bonus Stamps - {name}", world.player)
 | |
|         if not world.options.DWEnableBonus:
 | |
|             # place nothing, but let the locations exist still, so we can use them for bonus stamp rules
 | |
|             all_clear.address = None
 | |
|             all_clear.place_locked_item(HatInTimeItem("Nothing", ItemClassification.filler, None, world.player))
 | |
|             all_clear.show_in_spoiler = False
 | |
| 
 | |
|         # No need for rules if excluded - stamps will be auto-granted
 | |
|         if world.is_dw_excluded(name):
 | |
|             continue
 | |
| 
 | |
|         modify_dw_rules(world, name)
 | |
|         add_dw_rules(world, main_objective)
 | |
|         add_dw_rules(world, all_clear)
 | |
|         add_rule(main_stamp, main_objective.access_rule)
 | |
|         add_rule(all_clear, main_objective.access_rule)
 | |
|         # Only set bonus stamp rules if we don't auto complete bonuses
 | |
|         if not world.options.DWAutoCompleteBonuses and not world.is_bonus_excluded(all_clear.name):
 | |
|             add_rule(bonus_stamps, all_clear.access_rule)
 | |
| 
 | |
|     if world.options.DWShuffle:
 | |
|         for i in range(len(world.dw_shuffle)-1):
 | |
|             name = world.dw_shuffle[i+1]
 | |
|             prev_dw = world.multiworld.get_region(world.dw_shuffle[i], 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.options.EndGoal == EndGoal.option_seal_the_deal:
 | |
|         world.multiworld.completion_condition[world.player] = lambda state: \
 | |
|             state.has("1 Stamp - Seal the Deal", world.player)
 | |
| 
 | |
| 
 | |
| def add_dw_rules(world: "HatInTimeWorld", loc: Location):
 | |
|     bonus: bool = "All Clear" in loc.name
 | |
|     if not bonus:
 | |
|         data = dw_requirements.get(loc.parent_region.name)
 | |
|     else:
 | |
|         data = dw_bonus_requirements.get(loc.parent_region.name)
 | |
| 
 | |
|     if data is None:
 | |
|         return
 | |
| 
 | |
|     if data.hookshot:
 | |
|         add_rule(loc, lambda state: can_use_hookshot(state, world))
 | |
| 
 | |
|     for hat in data.required_hats:
 | |
|         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.paintings > 0 and world.options.ShuffleSubconPaintings:
 | |
|         add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings))
 | |
| 
 | |
|     if data.hit_type is not HitType.none and world.options.UmbrellaLogic:
 | |
|         if data.hit_type == HitType.umbrella:
 | |
|             add_rule(loc, lambda state: state.has("Umbrella", world.player))
 | |
| 
 | |
|         elif data.hit_type == HitType.umbrella_or_brewing:
 | |
|             add_rule(loc, lambda state: state.has("Umbrella", world.player)
 | |
|                      or can_use_hat(state, world, HatType.BREWING))
 | |
| 
 | |
|         elif data.hit_type == HitType.dweller_bell:
 | |
|             add_rule(loc, lambda state: state.has("Umbrella", world.player)
 | |
|                      or can_use_hat(state, world, HatType.BREWING)
 | |
|                      or can_use_hat(state, world, HatType.DWELLER))
 | |
| 
 | |
| 
 | |
| def modify_dw_rules(world: "HatInTimeWorld", name: str):
 | |
|     difficulty: Difficulty = get_difficulty(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 == "The Illness has Speedrun":
 | |
|         # All stamps with hookshot only in Expert
 | |
|         if difficulty >= Difficulty.EXPERT:
 | |
|             set_rule(full_clear, lambda state: True)
 | |
|         else:
 | |
|             add_rule(main_objective, lambda state: state.has("Umbrella", world.player))
 | |
| 
 | |
|     elif name == "The Mustache Gauntlet":
 | |
|         add_rule(main_objective, lambda state: state.has("Umbrella", world.player)
 | |
|                  or can_use_hat(state, world, HatType.ICE) or can_use_hat(state, world, HatType.BREWING))
 | |
| 
 | |
|     elif name == "Vault Codes in the Wind":
 | |
|         # Sprint is normally expected here
 | |
|         if difficulty >= Difficulty.HARD:
 | |
|             set_rule(main_objective, lambda state: True)
 | |
| 
 | |
|     elif name == "Speedrun Well":
 | |
|         # All stamps with nothing :)
 | |
|         if difficulty >= Difficulty.EXPERT:
 | |
|             set_rule(main_objective, lambda state: True)
 | |
| 
 | |
|     elif name == "Mafia's Jumps":
 | |
|         if difficulty >= Difficulty.HARD:
 | |
|             set_rule(main_objective, lambda state: True)
 | |
|             set_rule(full_clear, lambda state: True)
 | |
| 
 | |
|     elif name == "So You're Back from Outer Space":
 | |
|         # Without Hookshot
 | |
|         if difficulty >= Difficulty.HARD:
 | |
|             set_rule(main_objective, lambda state: True)
 | |
| 
 | |
|     elif name == "Wound-Up Windmill":
 | |
|         # No badge pin required. Player can switch to One Hit Hero after the checkpoint and do level without it.
 | |
|         if difficulty >= Difficulty.MODERATE:
 | |
|             set_rule(full_clear, lambda state: can_use_hookshot(state, world)
 | |
|                      and state.has("One-Hit Hero Badge", world.player))
 | |
| 
 | |
|     if name in dw_candles:
 | |
|         set_candle_dw_rules(name, world)
 | |
| 
 | |
| 
 | |
| def set_candle_dw_rules(name: str, world: "HatInTimeWorld"):
 | |
|     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: state.has("Zero Jumps", world.player))
 | |
|         add_rule(full_clear, lambda state: state.has("Zero Jumps", world.player, 4)
 | |
|                  and state.has("Train Rush (Zero Jumps)", world.player) and can_use_hat(state, world, HatType.ICE))
 | |
| 
 | |
|         # No Ice Hat/painting required in Expert for Toilet Zero Jumps
 | |
|         # This painting wall can only be skipped via cherry hover.
 | |
|         if get_difficulty(world) < Difficulty.EXPERT or world.options.NoPaintingSkips:
 | |
|             set_rule(world.multiworld.get_location("Toilet of Doom (Zero Jumps)", world.player),
 | |
|                      lambda state: can_use_hookshot(state, world) and can_hit(state, world)
 | |
|                      and has_paintings(state, world, 1, False))
 | |
|         else:
 | |
|             set_rule(world.multiworld.get_location("Toilet of Doom (Zero Jumps)", world.player),
 | |
|                      lambda state: can_use_hookshot(state, world) and can_hit(state, world))
 | |
| 
 | |
|         set_rule(world.multiworld.get_location("Contractual Obligations (Zero Jumps)", world.player),
 | |
|                  lambda state: has_paintings(state, world, 1, False))
 | |
| 
 | |
|     elif name == "Snatcher's Hit List":
 | |
|         add_rule(main_objective, lambda state: state.has("Mafia Goon", world.player))
 | |
|         add_rule(full_clear, lambda state: state.has("Enemy", world.player, 12))
 | |
| 
 | |
|     elif name == "Camera Tourist":
 | |
|         add_rule(main_objective, lambda state: state.has("Enemy", world.player, 8))
 | |
|         add_rule(full_clear, lambda state: state.has("Boss", world.player, 6)
 | |
|                  and state.has("Triple Enemy Photo", world.player))
 | |
| 
 | |
|     elif "Snatcher Coins" in name:
 | |
|         coins: List[str] = []
 | |
|         for coin in required_snatcher_coins[name]:
 | |
|             coins.append(coin)
 | |
|             add_rule(full_clear, lambda state, c=coin: state.has(c, world.player))
 | |
| 
 | |
|         # any coin works for the main objective
 | |
|         add_rule(main_objective, lambda state: state.has(coins[0], world.player)
 | |
|                  or state.has(coins[1], world.player)
 | |
|                  or state.has(coins[2], world.player))
 | |
| 
 | |
| 
 | |
| def create_enemy_events(world: "HatInTimeWorld"):
 | |
|     no_tourist = "Camera Tourist" in world.excluded_dws
 | |
|     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.options.ExcludeTour):
 | |
|                 continue
 | |
| 
 | |
|             if area == "Bluefin Tunnel" and not world.is_dlc2():
 | |
|                 continue
 | |
| 
 | |
|             if world.options.DWShuffle and area in death_wishes.keys() and area not in world.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
 | |
| 
 | |
|     for name in triple_enemy_locations:
 | |
|         if name == "Time Rift - Tour" and (not world.is_dlc1() or world.options.ExcludeTour):
 | |
|             continue
 | |
| 
 | |
|         if world.options.DWShuffle and name in death_wishes.keys() and name not in world.dw_shuffle:
 | |
|             continue
 | |
| 
 | |
|         region = world.multiworld.get_region(name, world.player)
 | |
|         event = HatInTimeLocation(world.player, f"Triple Enemy Photo - {name}", None, region)
 | |
|         event.place_locked_item(HatInTimeItem("Triple Enemy Photo", ItemClassification.progression, None, world.player))
 | |
|         region.locations.append(event)
 | |
|         event.show_in_spoiler = False
 | |
|         if name == "The Mustache Gauntlet":
 | |
|             add_rule(event, lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER))
 | |
| 
 | |
| 
 | |
| def set_enemy_rules(world: "HatInTimeWorld"):
 | |
|     no_tourist = "Camera Tourist" in world.excluded_dws or "Camera Tourist" in world.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.options.ExcludeTour):
 | |
|                 continue
 | |
| 
 | |
|             if area == "Bluefin Tunnel" and not world.is_dlc2():
 | |
|                 continue
 | |
| 
 | |
|             if world.options.DWShuffle and area in death_wishes and area not in world.dw_shuffle:
 | |
|                 continue
 | |
| 
 | |
|             event = world.multiworld.get_location(f"{enemy} - {area}", world.player)
 | |
| 
 | |
|             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 and snatcher
 | |
|                     add_rule(event, lambda state: can_hit(state, world) and can_use_hookshot(state, world))
 | |
|                     if enemy == "Mustache Girl":
 | |
|                         add_rule(event, lambda state: can_hit(state, world, True) and can_use_hookshot(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))
 | |
| 
 | |
| 
 | |
| # 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"],
 | |
| }
 | |
| 
 | |
| # Camera Tourist has a bonus that requires getting three different types of enemies in one photo.
 | |
| triple_enemy_locations = [
 | |
|     "She Came from Outer Space",
 | |
|     "She Speedran from Outer Space",
 | |
|     "Mafia's Jumps",
 | |
|     "The Mustache Gauntlet",
 | |
|     "The Birdhouse",
 | |
|     "Bird Sanctuary",
 | |
|     "Time Rift - Tour",
 | |
| ]
 | |
| 
 | |
| bosses = [
 | |
|     "Mafia Boss",
 | |
|     "Conductor",
 | |
|     "Toilet",
 | |
|     "Snatcher",
 | |
|     "Toxic Flower",
 | |
|     "Mustache Girl",
 | |
| ]
 |