| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld | 
					
						
							|  |  |  | from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \ | 
					
						
							| 
									
										
										
										
											2025-04-08 12:26:30 -04:00
										 |  |  |     calculate_yarn_costs, alps_hooks, junk_weights | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 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, TASKSANITY_START_ID, \ | 
					
						
							|  |  |  |     get_total_locations | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  | from .Rules import set_rules, has_paintings | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | from .Options import AHITOptions, slot_data_options, adjust_options, RandomizeHatOrder, EndGoal, create_option_groups | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  | from .Types import HatType, ChapterIndex, HatInTimeItem, hat_type_to_item, Difficulty | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes | 
					
						
							|  |  |  | from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses | 
					
						
							|  |  |  | from worlds.AutoWorld import World, WebWorld, CollectionState | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  | from worlds.generic.Rules import add_rule | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | from typing import List, Dict, TextIO | 
					
						
							| 
									
										
										
										
											2025-02-10 13:34:27 -05:00
										 |  |  | from worlds.LauncherComponents import Component, components, icon_paths, launch as launch_component, Type | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | from Utils import local_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-14 05:18:36 -06:00
										 |  |  | def launch_client(*args: str): | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  |     from .Client import launch | 
					
						
							| 
									
										
										
										
											2025-05-14 05:18:36 -06:00
										 |  |  |     launch_component(launch, name="AHITClient", args=args) | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | components.append(Component("A Hat in Time Client", "AHITClient", func=launch_client, | 
					
						
							|  |  |  |                             component_type=Type.CLIENT, icon='yatta')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | icon_paths['yatta'] = local_path('data', 'yatta.png') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class AWebInTime(WebWorld): | 
					
						
							|  |  |  |     theme = "partyTime" | 
					
						
							|  |  |  |     option_groups = create_option_groups() | 
					
						
							|  |  |  |     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-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! | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     game = "A Hat in Time" | 
					
						
							|  |  |  |     item_name_to_id = {name: data.code for name, data in item_table.items()} | 
					
						
							|  |  |  |     location_name_to_id = get_location_names() | 
					
						
							|  |  |  |     options_dataclass = AHITOptions | 
					
						
							|  |  |  |     options: AHITOptions | 
					
						
							|  |  |  |     item_name_groups = relic_groups | 
					
						
							|  |  |  |     web = AWebInTime() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, multiworld: "MultiWorld", player: int): | 
					
						
							|  |  |  |         super().__init__(multiworld, player) | 
					
						
							|  |  |  |         self.act_connections: Dict[str, str] = {} | 
					
						
							|  |  |  |         self.shop_locs: List[str] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.hat_craft_order: List[HatType] = [HatType.SPRINT, HatType.BREWING, HatType.ICE, | 
					
						
							|  |  |  |                                                HatType.DWELLER, HatType.TIME_STOP] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.hat_yarn_costs: Dict[HatType, int] = {HatType.SPRINT: -1, HatType.BREWING: -1, HatType.ICE: -1, | 
					
						
							|  |  |  |                                                    HatType.DWELLER: -1, HatType.TIME_STOP: -1} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.chapter_timepiece_costs: Dict[ChapterIndex, int] = {ChapterIndex.MAFIA: -1, | 
					
						
							|  |  |  |                                                                  ChapterIndex.BIRDS: -1, | 
					
						
							|  |  |  |                                                                  ChapterIndex.SUBCON: -1, | 
					
						
							|  |  |  |                                                                  ChapterIndex.ALPINE: -1, | 
					
						
							|  |  |  |                                                                  ChapterIndex.FINALE: -1, | 
					
						
							|  |  |  |                                                                  ChapterIndex.CRUISE: -1, | 
					
						
							|  |  |  |                                                                  ChapterIndex.METRO: -1} | 
					
						
							|  |  |  |         self.excluded_dws: List[str] = [] | 
					
						
							|  |  |  |         self.excluded_bonuses: List[str] = [] | 
					
						
							|  |  |  |         self.dw_shuffle: List[str] = [] | 
					
						
							|  |  |  |         self.nyakuza_thug_items: Dict[str, int] = {} | 
					
						
							|  |  |  |         self.badge_seller_count: int = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:26:30 -04:00
										 |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |         return self.random.choices(list(junk_weights.keys()), weights=junk_weights.values(), k=1)[0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  |     def generate_early(self): | 
					
						
							|  |  |  |         adjust_options(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.StartWithCompassBadge: | 
					
						
							|  |  |  |             self.multiworld.push_precollected(self.create_item("Compass Badge")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.is_dw_only(): | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  |         # Take care of some extremely restrictive starts in other chapters with act shuffle off | 
					
						
							|  |  |  |         if not self.options.ActRandomizer: | 
					
						
							|  |  |  |             start_chapter = self.options.StartingChapter | 
					
						
							|  |  |  |             if start_chapter == ChapterIndex.ALPINE: | 
					
						
							|  |  |  |                 self.multiworld.push_precollected(self.create_item("Hookshot Badge")) | 
					
						
							|  |  |  |                 if self.options.UmbrellaLogic: | 
					
						
							|  |  |  |                     self.multiworld.push_precollected(self.create_item("Umbrella")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if self.options.ShuffleAlpineZiplines: | 
					
						
							|  |  |  |                     ziplines = list(alps_hooks.keys()) | 
					
						
							|  |  |  |                     ziplines.remove("Zipline Unlock - The Twilight Bell Path")  # not enough checks from this one | 
					
						
							|  |  |  |                     self.multiworld.push_precollected(self.create_item(self.random.choice(ziplines))) | 
					
						
							|  |  |  |             elif start_chapter == ChapterIndex.SUBCON: | 
					
						
							|  |  |  |                 if self.options.ShuffleSubconPaintings: | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  |                     self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock")) | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  |             elif start_chapter == ChapterIndex.BIRDS: | 
					
						
							|  |  |  |                 if self.options.UmbrellaLogic: | 
					
						
							|  |  |  |                     if self.options.LogicDifficulty < Difficulty.EXPERT: | 
					
						
							|  |  |  |                         self.multiworld.push_precollected(self.create_item("Umbrella")) | 
					
						
							|  |  |  |                 elif self.options.LogicDifficulty < Difficulty.MODERATE: | 
					
						
							|  |  |  |                     self.multiworld.push_precollected(self.create_item("Umbrella")) | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def create_regions(self): | 
					
						
							|  |  |  |         # noinspection PyClassVar | 
					
						
							|  |  |  |         self.topology_present = bool(self.options.ActRandomizer) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         create_regions(self) | 
					
						
							|  |  |  |         if self.options.EnableDeathWish: | 
					
						
							|  |  |  |             create_dw_regions(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.is_dw_only(): | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         create_events(self) | 
					
						
							|  |  |  |         if self.is_dw(): | 
					
						
							|  |  |  |             if "Snatcher's Hit List" not in self.excluded_dws or "Camera Tourist" not in self.excluded_dws: | 
					
						
							|  |  |  |                 create_enemy_events(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # place vanilla contract locations if contract shuffle is off | 
					
						
							|  |  |  |         if not self.options.ShuffleActContracts: | 
					
						
							|  |  |  |             for name in contract_locations.keys(): | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  |                 loc = self.get_location(name) | 
					
						
							|  |  |  |                 loc.place_locked_item(create_item(self, name)) | 
					
						
							|  |  |  |                 if self.options.ShuffleSubconPaintings and loc.name != "Snatcher's Contract - The Subcon Well": | 
					
						
							|  |  |  |                     add_rule(loc, lambda state: has_paintings(state, self, 1)) | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def create_items(self): | 
					
						
							|  |  |  |         if self.has_yarn(): | 
					
						
							|  |  |  |             calculate_yarn_costs(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if self.options.RandomizeHatOrder: | 
					
						
							|  |  |  |                 self.random.shuffle(self.hat_craft_order) | 
					
						
							|  |  |  |                 if self.options.RandomizeHatOrder == RandomizeHatOrder.option_time_stop_last: | 
					
						
							|  |  |  |                     self.hat_craft_order.remove(HatType.TIME_STOP) | 
					
						
							|  |  |  |                     self.hat_craft_order.append(HatType.TIME_STOP) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # move precollected hats to the start of the list | 
					
						
							|  |  |  |             for i in range(5): | 
					
						
							|  |  |  |                 hat = HatType(i) | 
					
						
							|  |  |  |                 if self.is_hat_precollected(hat): | 
					
						
							|  |  |  |                     self.hat_craft_order.remove(hat) | 
					
						
							|  |  |  |                     self.hat_craft_order.insert(0, hat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.multiworld.itempool += create_itempool(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def set_rules(self): | 
					
						
							|  |  |  |         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 not self.options.DWEnableBonus: | 
					
						
							|  |  |  |                 for name in death_wishes: | 
					
						
							|  |  |  |                     if name == "Snatcher Coins in Nyakuza Metro" and not self.is_dlc2(): | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if self.options.DWShuffle and name not in self.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.options.ActRandomizer: | 
					
						
							|  |  |  |             randomize_act_entrances(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         set_rules(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.is_dw(): | 
					
						
							|  |  |  |             set_dw_rules(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def create_item(self, name: str) -> Item: | 
					
						
							|  |  |  |         return create_item(self, name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def fill_slot_data(self) -> dict: | 
					
						
							|  |  |  |         slot_data: dict = {"Chapter1Cost": self.chapter_timepiece_costs[ChapterIndex.MAFIA], | 
					
						
							|  |  |  |                            "Chapter2Cost": self.chapter_timepiece_costs[ChapterIndex.BIRDS], | 
					
						
							|  |  |  |                            "Chapter3Cost": self.chapter_timepiece_costs[ChapterIndex.SUBCON], | 
					
						
							|  |  |  |                            "Chapter4Cost": self.chapter_timepiece_costs[ChapterIndex.ALPINE], | 
					
						
							|  |  |  |                            "Chapter5Cost": self.chapter_timepiece_costs[ChapterIndex.FINALE], | 
					
						
							|  |  |  |                            "Chapter6Cost": self.chapter_timepiece_costs[ChapterIndex.CRUISE], | 
					
						
							|  |  |  |                            "Chapter7Cost": self.chapter_timepiece_costs[ChapterIndex.METRO], | 
					
						
							|  |  |  |                            "BadgeSellerItemCount": self.badge_seller_count, | 
					
						
							|  |  |  |                            "SeedNumber": str(self.multiworld.seed),  # For shop prices | 
					
						
							|  |  |  |                            "SeedName": self.multiworld.seed_name, | 
					
						
							|  |  |  |                            "TotalLocations": get_total_locations(self)} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.has_yarn(): | 
					
						
							|  |  |  |             slot_data.setdefault("SprintYarnCost", self.hat_yarn_costs[HatType.SPRINT]) | 
					
						
							|  |  |  |             slot_data.setdefault("BrewingYarnCost", self.hat_yarn_costs[HatType.BREWING]) | 
					
						
							|  |  |  |             slot_data.setdefault("IceYarnCost", self.hat_yarn_costs[HatType.ICE]) | 
					
						
							|  |  |  |             slot_data.setdefault("DwellerYarnCost", self.hat_yarn_costs[HatType.DWELLER]) | 
					
						
							|  |  |  |             slot_data.setdefault("TimeStopYarnCost", self.hat_yarn_costs[HatType.TIME_STOP]) | 
					
						
							|  |  |  |             slot_data.setdefault("Hat1", int(self.hat_craft_order[0])) | 
					
						
							|  |  |  |             slot_data.setdefault("Hat2", int(self.hat_craft_order[1])) | 
					
						
							|  |  |  |             slot_data.setdefault("Hat3", int(self.hat_craft_order[2])) | 
					
						
							|  |  |  |             slot_data.setdefault("Hat4", int(self.hat_craft_order[3])) | 
					
						
							|  |  |  |             slot_data.setdefault("Hat5", int(self.hat_craft_order[4])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.options.ActRandomizer: | 
					
						
							|  |  |  |             for name in self.act_connections.keys(): | 
					
						
							|  |  |  |                 slot_data[name] = self.act_connections[name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.is_dlc2() and not self.is_dw_only(): | 
					
						
							|  |  |  |             for name in self.nyakuza_thug_items.keys(): | 
					
						
							|  |  |  |                 slot_data[name] = self.nyakuza_thug_items[name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.is_dw(): | 
					
						
							|  |  |  |             i = 0 | 
					
						
							|  |  |  |             for name in self.excluded_dws: | 
					
						
							|  |  |  |                 if self.options.EndGoal.value == EndGoal.option_seal_the_deal and name == "Seal the Deal": | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 slot_data[f"excluded_dw{i}"] = dw_classes[name] | 
					
						
							|  |  |  |                 i += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             i = 0 | 
					
						
							|  |  |  |             if not self.options.DWAutoCompleteBonuses: | 
					
						
							|  |  |  |                 for name in self.excluded_bonuses: | 
					
						
							|  |  |  |                     if name in self.excluded_dws: | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     slot_data[f"excluded_bonus{i}"] = dw_classes[name] | 
					
						
							|  |  |  |                     i += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if self.options.DWShuffle: | 
					
						
							|  |  |  |                 shuffled_dws = self.dw_shuffle | 
					
						
							|  |  |  |                 for i in range(len(shuffled_dws)): | 
					
						
							|  |  |  |                     slot_data[f"dw_{i}"] = dw_classes[shuffled_dws[i]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         shop_item_names: Dict[str, str] = {} | 
					
						
							|  |  |  |         for name in self.shop_locs: | 
					
						
							|  |  |  |             loc: Location = self.multiworld.get_location(name, self.player) | 
					
						
							|  |  |  |             assert loc.item | 
					
						
							|  |  |  |             item_name: str | 
					
						
							|  |  |  |             if loc.item.classification is ItemClassification.trap and loc.item.game == "A Hat in Time": | 
					
						
							|  |  |  |                 item_name = get_shop_trap_name(self) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 item_name = loc.item.name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-21 17:10:18 -04:00
										 |  |  |             shop_item_names.setdefault(str(loc.address), | 
					
						
							|  |  |  |                                        f"{item_name} ({self.multiworld.get_player_name(loc.item.player)})") | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         slot_data["ShopItemNames"] = shop_item_names | 
					
						
							| 
									
										
										
										
											2025-07-16 12:19:35 -04:00
										 |  |  |         slot_data.update(self.options.as_dict(*slot_data_options)) | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  |         return slot_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): | 
					
						
							|  |  |  |         if self.is_dw_only() or not self.options.ActRandomizer: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         new_hint_data = {} | 
					
						
							|  |  |  |         alpine_regions = ["The Birdhouse", "The Lava Cake", "The Windmill", | 
					
						
							|  |  |  |                           "The Twilight Bell", "Alpine Skyline Area", "Alpine Skyline Area (TIHS)"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         metro_regions = ["Yellow Overpass Station", "Green Clean Station", "Bluefin Tunnel", "Pink Paw Station"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for key, data in location_table.items(): | 
					
						
							|  |  |  |             if not is_location_valid(self, key): | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             location = self.multiworld.get_location(key, self.player) | 
					
						
							|  |  |  |             region_name: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if data.region in alpine_regions: | 
					
						
							|  |  |  |                 region_name = "Alpine Free Roam" | 
					
						
							|  |  |  |             elif data.region in metro_regions: | 
					
						
							|  |  |  |                 region_name = "Nyakuza Free Roam" | 
					
						
							|  |  |  |             elif "Dead Bird Studio - " in data.region: | 
					
						
							|  |  |  |                 region_name = "Dead Bird Studio" | 
					
						
							|  |  |  |             elif data.region in chapter_act_info.keys(): | 
					
						
							|  |  |  |                 region_name = location.parent_region.name | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             new_hint_data[location.address] = get_shuffled_region(self, region_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.is_dlc1() and self.options.Tasksanity: | 
					
						
							|  |  |  |             ship_shape_region = get_shuffled_region(self, "Ship Shape") | 
					
						
							|  |  |  |             id_start: int = TASKSANITY_START_ID | 
					
						
							|  |  |  |             for i in range(self.options.TasksanityCheckCount): | 
					
						
							|  |  |  |                 new_hint_data[id_start+i] = ship_shape_region | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         hint_data[self.player] = new_hint_data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write_spoiler_header(self, spoiler_handle: TextIO): | 
					
						
							|  |  |  |         for i in self.chapter_timepiece_costs: | 
					
						
							|  |  |  |             spoiler_handle.write("Chapter %i Cost: %i\n" % (i, self.chapter_timepiece_costs[ChapterIndex(i)])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for hat in self.hat_craft_order: | 
					
						
							|  |  |  |             spoiler_handle.write("Hat Cost: %s: %i\n" % (hat, self.hat_yarn_costs[hat])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def collect(self, state: "CollectionState", item: "Item") -> bool: | 
					
						
							|  |  |  |         old_count: int = state.count(item.name, self.player) | 
					
						
							|  |  |  |         change = super().collect(state, item) | 
					
						
							|  |  |  |         if change and old_count == 0: | 
					
						
							|  |  |  |             if "Stamp" in item.name: | 
					
						
							|  |  |  |                 if "2 Stamp" in item.name: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Stamps"] += 2 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Stamps"] += 1 | 
					
						
							|  |  |  |             elif "(Zero Jumps)" in item.name: | 
					
						
							|  |  |  |                 state.prog_items[self.player]["Zero Jumps"] += 1 | 
					
						
							|  |  |  |             elif item.name in hit_list.keys(): | 
					
						
							|  |  |  |                 if item.name not in bosses: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Enemy"] += 1 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Boss"] += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return change | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def remove(self, state: "CollectionState", item: "Item") -> bool: | 
					
						
							|  |  |  |         old_count: int = state.count(item.name, self.player) | 
					
						
							| 
									
										
										
										
											2024-07-27 13:16:52 -04:00
										 |  |  |         change = super().remove(state, item) | 
					
						
							| 
									
										
										
										
											2024-05-20 03:04:06 -04:00
										 |  |  |         if change and old_count == 1: | 
					
						
							|  |  |  |             if "Stamp" in item.name: | 
					
						
							|  |  |  |                 if "2 Stamp" in item.name: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Stamps"] -= 2 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Stamps"] -= 1 | 
					
						
							|  |  |  |             elif "(Zero Jumps)" in item.name: | 
					
						
							|  |  |  |                 state.prog_items[self.player]["Zero Jumps"] -= 1 | 
					
						
							|  |  |  |             elif item.name in hit_list.keys(): | 
					
						
							|  |  |  |                 if item.name not in bosses: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Enemy"] -= 1 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     state.prog_items[self.player]["Boss"] -= 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return change | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def has_yarn(self) -> bool: | 
					
						
							|  |  |  |         return not self.is_dw_only() and not self.options.HatItems | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_hat_precollected(self, hat: HatType) -> bool: | 
					
						
							|  |  |  |         for item in self.multiworld.precollected_items[self.player]: | 
					
						
							|  |  |  |             if item.name == hat_type_to_item[hat]: | 
					
						
							|  |  |  |                 return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_dlc1(self) -> bool: | 
					
						
							|  |  |  |         return bool(self.options.EnableDLC1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_dlc2(self) -> bool: | 
					
						
							|  |  |  |         return bool(self.options.EnableDLC2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_dw(self) -> bool: | 
					
						
							|  |  |  |         return bool(self.options.EnableDeathWish) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_dw_only(self) -> bool: | 
					
						
							|  |  |  |         return self.is_dw() and bool(self.options.DeathWishOnly) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_dw_excluded(self, name: str) -> bool: | 
					
						
							|  |  |  |         # don't exclude Seal the Deal if it's our goal | 
					
						
							|  |  |  |         if self.options.EndGoal.value == EndGoal.option_seal_the_deal and name == "Seal the Deal" \ | 
					
						
							|  |  |  |            and f"{name} - Main Objective" not in self.options.exclude_locations: | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if name in self.excluded_dws: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return f"{name} - Main Objective" in self.options.exclude_locations | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def is_bonus_excluded(self, name: str) -> bool: | 
					
						
							|  |  |  |         if self.is_dw_excluded(name) or name in self.excluded_bonuses: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return f"{name} - All Clear" in self.options.exclude_locations |