From 25a39a46ad79bea04b8b98bdf857e1f5b9539873 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Thu, 26 Oct 2023 22:10:30 -0400 Subject: [PATCH 1/9] init --- worlds/ahit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py index 64b3febc3e..047b788535 100644 --- a/worlds/ahit/__init__.py +++ b/worlds/ahit/__init__.py @@ -49,7 +49,7 @@ class HatInTimeWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = get_location_names() - option_definitions = ahit_options + # option_definitions = ahit_options act_connections: Dict[str, str] = {} shop_locs: List[str] = [] item_name_groups = relic_groups From 2ce7b526fd50958be0e0e560942f90bb4f424a4f Mon Sep 17 00:00:00 2001 From: CookieCat Date: Thu, 26 Oct 2023 23:02:34 -0400 Subject: [PATCH 2/9] Update to new options API --- worlds/ahit/DeathWishLocations.py | 20 +-- worlds/ahit/DeathWishRules.py | 34 ++--- worlds/ahit/Items.py | 42 +++--- worlds/ahit/Locations.py | 24 ++-- worlds/ahit/Options.py | 206 +++++++++++++++--------------- worlds/ahit/Regions.py | 69 +++++----- worlds/ahit/Rules.py | 48 +++---- worlds/ahit/__init__.py | 61 ++++----- 8 files changed, 252 insertions(+), 252 deletions(-) diff --git a/worlds/ahit/DeathWishLocations.py b/worlds/ahit/DeathWishLocations.py index f51d4948ee..f3e546f6c9 100644 --- a/worlds/ahit/DeathWishLocations.py +++ b/worlds/ahit/DeathWishLocations.py @@ -139,19 +139,19 @@ dw_classes = { def create_dw_regions(world: World): - if world.multiworld.DWExcludeAnnoyingContracts[world.player].value > 0: + if world.options.DWExcludeAnnoyingContracts.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: + if world.options.DWEnableBonus.value == 0 \ + or world.options.DWAutoCompleteBonuses.value > 0: for name in death_wishes: world.get_excluded_bonuses().append(name) - elif world.multiworld.DWExcludeAnnoyingBonuses[world.player].value > 0: + elif world.options.DWExcludeAnnoyingBonuses.value > 0: for name in annoying_bonuses: world.get_excluded_bonuses().append(name) - if world.multiworld.DWExcludeCandles[world.player].value > 0: + if world.options.DWExcludeCandles.value > 0: for name in dw_candles: if name in world.get_excluded_dws(): continue @@ -162,9 +162,9 @@ def create_dw_regions(world: World): 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)) + world.options.DWTimePieceRequirement.value)) - if world.multiworld.DWShuffle[world.player].value > 0: + if world.options.DWShuffle.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): @@ -173,8 +173,8 @@ def create_dw_regions(world: World): 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) + count = world.random.randint(world.options.DWShuffleCountMin.value, + world.options.DWShuffleCountMax.value) dw_shuffle: List[str] = [] total = min(len(dw_list), count) @@ -182,7 +182,7 @@ def create_dw_regions(world: World): 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 world.options.EndGoal.value == 3: if "Seal the Deal" in dw_shuffle: dw_shuffle.remove("Seal the Deal") diff --git a/worlds/ahit/DeathWishRules.py b/worlds/ahit/DeathWishRules.py index 5a97518499..648549cee0 100644 --- a/worlds/ahit/DeathWishRules.py +++ b/worlds/ahit/DeathWishRules.py @@ -105,7 +105,7 @@ def set_dw_rules(world: World): set_enemy_rules(world) dw_list: List[str] = [] - if world.multiworld.DWShuffle[world.player].value > 0: + if world.options.DWShuffle.value > 0: dw_list = world.get_dw_shuffle() else: for name in death_wishes.keys(): @@ -124,12 +124,12 @@ def set_dw_rules(world: World): temp_list.append(main_objective) temp_list.append(full_clear) - if world.multiworld.DWShuffle[world.player].value == 0: + if world.options.DWShuffle.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: + if world.options.DWEnableBonus.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)) @@ -165,10 +165,10 @@ def set_dw_rules(world: World): 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: + if data.umbrella and world.options.UmbrellaLogic.value > 0: add_rule(loc, lambda state: state.has("Umbrella", world.player)) - if data.paintings > 0 and world.multiworld.ShuffleSubconPaintings[world.player].value > 0: + if data.paintings > 0 and world.options.ShuffleSubconPaintings.value > 0: add_rule(loc, lambda state, paintings=data.paintings: has_paintings(state, world, paintings)) if data.hit_requirement > 0: @@ -184,11 +184,11 @@ def set_dw_rules(world: World): 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 \ + if world.options.DWAutoCompleteBonuses.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: + if world.options.DWShuffle.value > 0: dw_shuffle = world.get_dw_shuffle() for i in range(len(dw_shuffle)): if i == 0: @@ -217,7 +217,7 @@ def set_dw_rules(world: World): for rule in access_rules: add_rule(entrance, rule) - if world.multiworld.EndGoal[world.player].value == 3: + if world.options.EndGoal.value == 3: world.multiworld.completion_condition[world.player] = lambda state: state.has("1 Stamp - Seal the Deal", world.player) @@ -269,7 +269,7 @@ def modify_dw_rules(world: World, name: str): def get_total_dw_stamps(state: CollectionState, world: World) -> int: - if world.multiworld.DWShuffle[world.player].value > 0: + if world.options.DWShuffle.value > 0: return 999 # no stamp costs in death wish shuffle count: int = 0 @@ -303,7 +303,7 @@ def set_candle_dw_rules(name: str, world: World): # 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.multiworld.NoPaintingSkips[world.player].value == 1: + if get_difficulty(world) < Difficulty.EXPERT or world.options.NoPaintingSkips.value == 1: 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)) @@ -383,13 +383,13 @@ def create_enemy_events(world: World): continue if area == "Time Rift - Tour" and (not world.is_dlc1() - or world.multiworld.ExcludeTour[world.player].value > 0): + or world.options.ExcludeTour.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.keys() \ + if world.options.DWShuffle.value > 0 and area in death_wishes.keys() \ and area not in world.get_dw_shuffle(): continue @@ -400,14 +400,14 @@ def create_enemy_events(world: World): event.show_in_spoiler = False for name in triple_enemy_locations: - if name == "Time Rift - Tour" and (not world.is_dlc1() or world.multiworld.ExcludeTour[world.player].value > 0): + if name == "Time Rift - Tour" and (not world.is_dlc1() or world.options.ExcludeTour.value > 0): continue - if world.multiworld.DWShuffle[world.player].value > 0 and name in death_wishes.keys() \ + if world.options.DWShuffle.value > 0 and name in death_wishes.keys() \ and name not in world.get_dw_shuffle(): continue - region = world.multiworld.get_region(name, world.player) + region = world.options.get_region(name, world.player) event = HatInTimeLocation(world.player, f"Triple Enemy Picture - {name}", None, region) event.place_locked_item(HatInTimeItem("Triple Enemy Picture", ItemClassification.progression, None, world.player)) region.locations.append(event) @@ -428,13 +428,13 @@ def set_enemy_rules(world: World): continue if area == "Time Rift - Tour" and (not world.is_dlc1() - or world.multiworld.ExcludeTour[world.player].value > 0): + or world.options.ExcludeTour.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 \ + if world.options.DWShuffle.value > 0 and area in death_wishes \ and area not in world.get_dw_shuffle(): continue diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py index 869f998a9d..3a13b3e3c8 100644 --- a/worlds/ahit/Items.py +++ b/worlds/ahit/Items.py @@ -8,13 +8,13 @@ from typing import Optional, List, Dict def create_itempool(world: World) -> List[Item]: itempool: List[Item] = [] - if not world.is_dw_only() and world.multiworld.HatItems[world.player].value == 0: + if not world.is_dw_only() and world.options.HatItems.value == 0: calculate_yarn_costs(world) yarn_pool: List[Item] = create_multiple_items(world, "Yarn", - world.multiworld.YarnAvailable[world.player].value, + world.options.YarnAvailable.value, ItemClassification.progression_skip_balancing) - for i in range(int(len(yarn_pool) * (0.01 * world.multiworld.YarnBalancePercent[world.player].value))): + for i in range(int(len(yarn_pool) * (0.01 * world.options.YarnBalancePercent.value))): yarn_pool[i].classification = ItemClassification.progression itempool += yarn_pool @@ -26,7 +26,7 @@ def create_itempool(world: World) -> List[Item]: if not item_dlc_enabled(world, name): continue - if world.multiworld.HatItems[world.player].value == 0 and name in hat_type_to_item.values(): + if world.options.HatItems.value == 0 and name in hat_type_to_item.values(): continue item_type: ItemClassification = item_table.get(name).classification @@ -37,7 +37,7 @@ def create_itempool(world: World) -> List[Item]: continue else: if name == "Scooter Badge": - if world.multiworld.CTRLogic[world.player].value >= 1 or get_difficulty(world) >= Difficulty.MODERATE: + if world.options.CTRLogic.value >= 1 or get_difficulty(world) >= Difficulty.MODERATE: item_type = ItemClassification.progression elif name == "No Bonk Badge": if get_difficulty(world) >= Difficulty.MODERATE: @@ -50,17 +50,17 @@ def create_itempool(world: World) -> List[Item]: 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: + if name in act_contracts.keys() and world.options.ShuffleActContracts.value == 0: continue - if name in alps_hooks.keys() and world.multiworld.ShuffleAlpineZiplines[world.player].value == 0: + if name in alps_hooks.keys() and world.options.ShuffleAlpineZiplines.value == 0: continue if name == "Progressive Painting Unlock" \ - and world.multiworld.ShuffleSubconPaintings[world.player].value == 0: + and world.options.ShuffleSubconPaintings.value == 0: continue - if world.multiworld.StartWithCompassBadge[world.player].value > 0 and name == "Compass Badge": + if world.options.StartWithCompassBadge.value > 0 and name == "Compass Badge": continue if name == "Time Piece": @@ -72,10 +72,10 @@ def create_itempool(world: World) -> List[Item]: if world.is_dlc2(): max_extra += 10 - tp_count += min(max_extra, world.multiworld.MaxExtraTimePieces[world.player].value) + tp_count += min(max_extra, world.options.MaxExtraTimePieces.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))): + for i in range(int(len(tp_list) * (0.01 * world.options.TimePieceBalancePercent.value))): tp_list[i].classification = ItemClassification.progression itempool += tp_list @@ -90,8 +90,8 @@ def create_itempool(world: World) -> List[Item]: 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)) + min_yarn_cost = int(min(world.options.YarnCostMin.value, world.options.YarnCostMax.value)) + max_yarn_cost = int(max(world.options.YarnCostMin.value, world.options.YarnCostMax.value)) max_cost: int = 0 for i in range(5): @@ -99,13 +99,13 @@ def calculate_yarn_costs(world: World): world.get_hat_yarn_costs()[HatType(i)] = cost max_cost += cost - available_yarn: int = mw.YarnAvailable[p].value + available_yarn: int = world.options.YarnAvailable.value if max_cost > available_yarn: - mw.YarnAvailable[p].value = max_cost + world.options.YarnAvailable.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 + if max_cost + world.options.MinExtraYarn.value > available_yarn: + world.options.YarnAvailable.value += (max_cost + world.options.MinExtraYarn.value) - available_yarn def item_dlc_enabled(world: World, name: str) -> bool: @@ -141,7 +141,7 @@ def create_multiple_items(world: World, name: str, count: int = 1, def create_junk_items(world: World, count: int) -> List[Item]: - trap_chance = world.multiworld.TrapChance[world.player].value + trap_chance = world.options.TrapChance.value junk_pool: List[Item] = [] junk_list: Dict[str, int] = {} trap_list: Dict[str, int] = {} @@ -157,11 +157,11 @@ def create_junk_items(world: World, count: int) -> List[Item]: elif trap_chance > 0 and ic == ItemClassification.trap: if name == "Baby Trap": - trap_list[name] = world.multiworld.BabyTrapWeight[world.player].value + trap_list[name] = world.options.BabyTrapWeight.value elif name == "Laser Trap": - trap_list[name] = world.multiworld.LaserTrapWeight[world.player].value + trap_list[name] = world.options.LaserTrapWeight.value elif name == "Parade Trap": - trap_list[name] = world.multiworld.ParadeTrapWeight[world.player].value + trap_list[name] = world.options.ParadeTrapWeight.value for i in range(count): if trap_chance > 0 and world.random.randint(1, 100) <= trap_chance: diff --git a/worlds/ahit/Locations.py b/worlds/ahit/Locations.py index 00ec578624..2bc1c27080 100644 --- a/worlds/ahit/Locations.py +++ b/worlds/ahit/Locations.py @@ -12,20 +12,20 @@ def get_total_locations(world: World) -> int: 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.options.Tasksanity.value > 0: + total += world.options.TasksanityCheckCount.value if world.is_dw(): - if world.multiworld.DWShuffle[world.player].value > 0: + if world.options.DWShuffle.value > 0: total += len(world.get_dw_shuffle()) - if world.multiworld.DWEnableBonus[world.player].value > 0: + if world.options.DWEnableBonus.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: + if world.options.DWEnableBonus.value > 0: total += 37 if world.is_dlc2(): total += 1 @@ -56,11 +56,11 @@ def is_location_valid(world: World, location: str) -> bool: if not location_dlc_enabled(world, location): return False - if world.multiworld.ShuffleStorybookPages[world.player].value == 0 \ + if world.options.ShuffleStorybookPages.value == 0 \ and location in storybook_pages.keys(): return False - if world.multiworld.ShuffleActContracts[world.player].value == 0 \ + if world.options.ShuffleActContracts.value == 0 \ and location in contract_locations.keys(): return False @@ -68,23 +68,23 @@ def is_location_valid(world: World, location: str) -> bool: return False data = location_table.get(location) or event_locs.get(location) - if world.multiworld.ExcludeTour[world.player].value > 0 and data.region == "Time Rift - Tour": + if world.options.ExcludeTour.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 & HatDLC.death_wish: - if world.multiworld.DWExcludeCandles[world.player].value > 0 and location in event_locs.keys(): + if world.options.DWExcludeCandles.value > 0 and location in event_locs.keys(): return False - if world.multiworld.DWShuffle[world.player].value > 0 \ + if world.options.DWShuffle.value > 0 \ and data.region in death_wishes and data.region not in world.get_dw_shuffle(): return False if location in zero_jumps: - if world.multiworld.DWShuffle[world.player].value > 0 and "Zero Jumps" not in world.get_dw_shuffle(): + if world.options.DWShuffle.value > 0 and "Zero Jumps" not in world.get_dw_shuffle(): return False - difficulty: int = world.multiworld.LogicDifficulty[world.player].value + difficulty: int = world.options.LogicDifficulty.value if location in zero_jumps_hard and difficulty < int(Difficulty.HARD): return False diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index 863f940f0a..67bc5b56e6 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -1,6 +1,7 @@ -import typing -from worlds.AutoWorld import World -from Options import Option, Range, Toggle, DeathLink, Choice, OptionDict +from typing import List +from dataclasses import dataclass +from worlds.AutoWorld import World, PerGameCommonOptions +from Options import Range, Toggle, DeathLink, Choice, OptionDict def adjust_options(world: World): @@ -594,119 +595,118 @@ class ParadeTrapWeight(Range): default = 20 -ahit_options: typing.Dict[str, type(Option)] = { +@dataclass +class AHITOptions(PerGameCommonOptions): + EndGoal: EndGoal + ActRandomizer: ActRandomizer + ActPlando: ActPlando + ShuffleAlpineZiplines: ShuffleAlpineZiplines + FinaleShuffle: FinaleShuffle + LogicDifficulty: LogicDifficulty + YarnBalancePercent: YarnBalancePercent + TimePieceBalancePercent: TimePieceBalancePercent + RandomizeHatOrder: RandomizeHatOrder + UmbrellaLogic: UmbrellaLogic + StartWithCompassBadge: StartWithCompassBadge + CompassBadgeMode: CompassBadgeMode + ShuffleStorybookPages: ShuffleStorybookPages + ShuffleActContracts: ShuffleActContracts + ShuffleSubconPaintings: ShuffleSubconPaintings + NoPaintingSkips: NoPaintingSkips + StartingChapter: StartingChapter + CTRLogic: CTRLogic - "EndGoal": EndGoal, - "ActRandomizer": ActRandomizer, - "ActPlando": ActPlando, - "ShuffleAlpineZiplines": ShuffleAlpineZiplines, - "FinaleShuffle": FinaleShuffle, - "LogicDifficulty": LogicDifficulty, - "YarnBalancePercent": YarnBalancePercent, - "TimePieceBalancePercent": TimePieceBalancePercent, - "RandomizeHatOrder": RandomizeHatOrder, - "UmbrellaLogic": UmbrellaLogic, - "StartWithCompassBadge": StartWithCompassBadge, - "CompassBadgeMode": CompassBadgeMode, - "ShuffleStorybookPages": ShuffleStorybookPages, - "ShuffleActContracts": ShuffleActContracts, - "ShuffleSubconPaintings": ShuffleSubconPaintings, - "NoPaintingSkips": NoPaintingSkips, - "StartingChapter": StartingChapter, - "CTRLogic": CTRLogic, + EnableDLC1: EnableDLC1 + Tasksanity: Tasksanity + TasksanityTaskStep: TasksanityTaskStep + TasksanityCheckCount: TasksanityCheckCount + ExcludeTour: ExcludeTour + ShipShapeCustomTaskGoal: ShipShapeCustomTaskGoal - "EnableDLC1": EnableDLC1, - "Tasksanity": Tasksanity, - "TasksanityTaskStep": TasksanityTaskStep, - "TasksanityCheckCount": TasksanityCheckCount, - "ExcludeTour": ExcludeTour, - "ShipShapeCustomTaskGoal": ShipShapeCustomTaskGoal, + EnableDeathWish: EnableDeathWish + DWShuffle: DWShuffle + DWShuffleCountMin: DWShuffleCountMin + DWShuffleCountMax: DWShuffleCountMax + DeathWishOnly: DeathWishOnly + DWEnableBonus: DWEnableBonus + DWAutoCompleteBonuses: DWAutoCompleteBonuses + DWExcludeAnnoyingContracts: DWExcludeAnnoyingContracts + DWExcludeAnnoyingBonuses: DWExcludeAnnoyingBonuses + DWExcludeCandles: DWExcludeCandles + DWTimePieceRequirement: DWTimePieceRequirement - "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 + MetroMaxPonCost: MetroMaxPonCost + NyakuzaThugMinShopItems: NyakuzaThugMinShopItems + NyakuzaThugMaxShopItems: NyakuzaThugMaxShopItems - "EnableDLC2": EnableDLC2, - "BaseballBat": BaseballBat, - "MetroMinPonCost": MetroMinPonCost, - "MetroMaxPonCost": MetroMaxPonCost, - "NyakuzaThugMinShopItems": NyakuzaThugMinShopItems, - "NyakuzaThugMaxShopItems": NyakuzaThugMaxShopItems, + LowestChapterCost: LowestChapterCost + HighestChapterCost: HighestChapterCost + ChapterCostIncrement: ChapterCostIncrement + ChapterCostMinDifference: ChapterCostMinDifference + MaxExtraTimePieces: MaxExtraTimePieces - "LowestChapterCost": LowestChapterCost, - "HighestChapterCost": HighestChapterCost, - "ChapterCostIncrement": ChapterCostIncrement, - "ChapterCostMinDifference": ChapterCostMinDifference, - "MaxExtraTimePieces": MaxExtraTimePieces, + FinalChapterMinCost: FinalChapterMinCost + FinalChapterMaxCost: FinalChapterMaxCost - "FinalChapterMinCost": FinalChapterMinCost, - "FinalChapterMaxCost": FinalChapterMaxCost, + YarnCostMin: YarnCostMin + YarnCostMax: YarnCostMax + YarnAvailable: YarnAvailable + MinExtraYarn: MinExtraYarn + HatItems: HatItems - "YarnCostMin": YarnCostMin, - "YarnCostMax": YarnCostMax, - "YarnAvailable": YarnAvailable, - "MinExtraYarn": MinExtraYarn, - "HatItems": HatItems, + MinPonCost: MinPonCost + MaxPonCost: MaxPonCost + BadgeSellerMinItems: BadgeSellerMinItems + BadgeSellerMaxItems: BadgeSellerMaxItems - "MinPonCost": MinPonCost, - "MaxPonCost": MaxPonCost, - "BadgeSellerMinItems": BadgeSellerMinItems, - "BadgeSellerMaxItems": BadgeSellerMaxItems, + TrapChance: TrapChance + BabyTrapWeight: BabyTrapWeight + LaserTrapWeight: LaserTrapWeight + ParadeTrapWeight: ParadeTrapWeight - "TrapChance": TrapChance, - "BabyTrapWeight": BabyTrapWeight, - "LaserTrapWeight": LaserTrapWeight, - "ParadeTrapWeight": ParadeTrapWeight, + death_link: DeathLink - "death_link": DeathLink, -} -slot_data_options: typing.Dict[str, type(Option)] = { +slot_data_options: List[str] = [ + "EndGoal", + "ActRandomizer", + "ShuffleAlpineZiplines", + "LogicDifficulty", + "CTRLogic", + "RandomizeHatOrder", + "UmbrellaLogic", + "StartWithCompassBadge", + "CompassBadgeMode", + "ShuffleStorybookPages", + "ShuffleActContracts", + "ShuffleSubconPaintings", + "NoPaintingSkips", + "HatItems", - "EndGoal": EndGoal, - "ActRandomizer": ActRandomizer, - "ShuffleAlpineZiplines": ShuffleAlpineZiplines, - "LogicDifficulty": LogicDifficulty, - "CTRLogic": CTRLogic, - "RandomizeHatOrder": RandomizeHatOrder, - "UmbrellaLogic": UmbrellaLogic, - "StartWithCompassBadge": StartWithCompassBadge, - "CompassBadgeMode": CompassBadgeMode, - "ShuffleStorybookPages": ShuffleStorybookPages, - "ShuffleActContracts": ShuffleActContracts, - "ShuffleSubconPaintings": ShuffleSubconPaintings, - "NoPaintingSkips": NoPaintingSkips, - "HatItems": HatItems, + "EnableDLC1", + "Tasksanity", + "TasksanityTaskStep", + "TasksanityCheckCount", + "ShipShapeCustomTaskGoal", + "ExcludeTour", - "EnableDLC1": EnableDLC1, - "Tasksanity": Tasksanity, - "TasksanityTaskStep": TasksanityTaskStep, - "TasksanityCheckCount": TasksanityCheckCount, - "ShipShapeCustomTaskGoal": ShipShapeCustomTaskGoal, - "ExcludeTour": ExcludeTour, + "EnableDeathWish", + "DWShuffle", + "DeathWishOnly", + "DWEnableBonus", + "DWAutoCompleteBonuses", + "DWTimePieceRequirement", - "EnableDeathWish": EnableDeathWish, - "DWShuffle": DWShuffle, - "DeathWishOnly": DeathWishOnly, - "DWEnableBonus": DWEnableBonus, - "DWAutoCompleteBonuses": DWAutoCompleteBonuses, - "DWTimePieceRequirement": DWTimePieceRequirement, + "EnableDLC2", + "MetroMinPonCost", + "MetroMaxPonCost", + "BaseballBat", - "EnableDLC2": EnableDLC2, - "MetroMinPonCost": MetroMinPonCost, - "MetroMaxPonCost": MetroMaxPonCost, - "BaseballBat": BaseballBat, + "MinPonCost", + "MaxPonCost", - "MinPonCost": MinPonCost, - "MaxPonCost": MaxPonCost, - - "death_link": DeathLink, -} + "death_link", +] diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py index 59186d68ff..70f7ede3b7 100644 --- a/worlds/ahit/Regions.py +++ b/worlds/ahit/Regions.py @@ -270,7 +270,6 @@ blacklisted_combos = { def create_regions(world: World): w = world - mw = world.multiworld p = world.player # ------------------------------------------- HUB -------------------------------------------------- # @@ -311,7 +310,7 @@ def create_regions(world: World): ev_area = create_region_and_connect(w, "Dead Bird Studio - Elevator Area", "DBS -> Elevator Area", dbs) post_ev_area = create_region_and_connect(w, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs) connect_regions(basement, ev_area, "DBS Basement -> Elevator Area", p) - if world.multiworld.LogicDifficulty[world.player].value >= int(Difficulty.EXPERT): + if world.options.LogicDifficulty.value >= int(Difficulty.EXPERT): connect_regions(basement, post_ev_area, "DBS Basement -> Post Elevator Area", p) # ------------------------------------------- SUBCON FOREST --------------------------------------- # @@ -397,10 +396,10 @@ def create_regions(world: World): create_rift_connections(w, create_region(w, "Time Rift - Balcony")) create_rift_connections(w, create_region(w, "Time Rift - Deep Sea")) - if mw.ExcludeTour[world.player].value == 0: + if w.options.ExcludeTour.value == 0: create_rift_connections(w, create_region(w, "Time Rift - Tour")) - if mw.Tasksanity[p].value > 0: + if w.options.Tasksanity.value > 0: create_tasksanity_locations(w) connect_regions(cruise_ship, badge_seller, "CS -> Badge Seller", p) @@ -440,7 +439,7 @@ def create_rift_connections(world: World, region: Region): 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): + for i in range(world.options.TasksanityCheckCount.value): location = HatInTimeLocation(world.player, f"Tasksanity Check {i+1}", id_start+i, ship_shape) ship_shape.locations.append(location) @@ -449,10 +448,10 @@ def is_valid_plando(world: World, region: str) -> bool: if region in blacklisted_acts.values(): return False - if region not in world.multiworld.ActPlando[world.player].keys(): + if region not in world.options.ActPlando.keys(): return False - act = world.multiworld.ActPlando[world.player].get(region) + act = world.options.ActPlando.get(region) if act in blacklisted_acts.values(): return False @@ -461,10 +460,10 @@ def is_valid_plando(world: World, region: str) -> bool: and region in act_entrances.keys() and ("Act 1" in act_entrances[region] or "Free Roam" in act_entrances[region]) if is_first_act: - if act_chapters[act] == "Subcon Forest" and world.multiworld.ShuffleSubconPaintings[world.player].value > 0: + if act_chapters[act] == "Subcon Forest" and world.options.ShuffleSubconPaintings.value > 0: return False - if world.multiworld.UmbrellaLogic[world.player].value > 0 \ + if world.options.UmbrellaLogic.value > 0 \ and (act == "Heating Up Mafia Town" or act == "Queen Vanessa's Manor"): return False @@ -484,7 +483,7 @@ def is_valid_plando(world: World, region: str) -> bool: if region == "Time Rift - The Owl Express" and act == "Murder on the Owl Express": return False - return any(a.name == world.multiworld.ActPlando[world.player].get(region) for a in + return any(a.name == world.options.ActPlando.get(region) for a in world.multiworld.get_regions(world.player)) @@ -492,7 +491,7 @@ def randomize_act_entrances(world: World): region_list: typing.List[Region] = get_act_regions(world) world.random.shuffle(region_list) - separate_rifts: bool = bool(world.multiworld.ActRandomizer[world.player].value == 1) + separate_rifts: bool = bool(world.options.ActRandomizer.value == 1) for region in region_list.copy(): if (act_chapters[region.name] == "Alpine Skyline" or act_chapters[region.name] == "Nyakuza Metro") \ @@ -511,14 +510,14 @@ def randomize_act_entrances(world: World): region_list.append(region) for region in region_list.copy(): - if region.name in world.multiworld.ActPlando[world.player].keys(): + if region.name in world.options.ActPlando.keys(): if is_valid_plando(world, region.name): region_list.remove(region) region_list.append(region) else: print("Disallowing act plando for", world.multiworld.player_name[world.player], - "-", region.name, ":", world.multiworld.ActPlando[world.player].get(region.name)) + "-", region.name, ":", world.options.ActPlando.get(region.name)) # Reverse the list, so we can do what we want to do first region_list.reverse() @@ -543,7 +542,7 @@ def randomize_act_entrances(world: World): and "Free Roam" not in act_entrances[region.name]: continue - if region.name in world.multiworld.ActPlando[world.player].keys() and is_valid_plando(world, region.name): + if region.name in world.options.ActPlando.keys() and is_valid_plando(world, region.name): has_guaranteed = True i = 0 @@ -562,16 +561,16 @@ def randomize_act_entrances(world: World): if candidate.name not in guaranteed_first_acts: continue - if candidate.name in world.multiworld.ActPlando[world.player].values(): + if candidate.name in world.options.ActPlando.values(): continue # Not completable without Umbrella - if world.multiworld.UmbrellaLogic[world.player].value > 0 \ + if world.options.UmbrellaLogic.value > 0 \ and (candidate.name == "Heating Up Mafia Town" or candidate.name == "Queen Vanessa's Manor"): continue # Subcon sphere 1 is too small without painting unlocks, and no acts are completable either - if world.multiworld.ShuffleSubconPaintings[world.player].value > 0 \ + if world.options.ShuffleSubconPaintings.value > 0 \ and "Subcon Forest" in act_entrances[candidate.name]: continue @@ -579,10 +578,10 @@ def randomize_act_entrances(world: World): has_guaranteed = True break - if region.name in world.multiworld.ActPlando[world.player].keys() and is_valid_plando(world, region.name): + if region.name in world.options.ActPlando.keys() and is_valid_plando(world, region.name): candidate_list.clear() candidate_list.append( - world.multiworld.get_region(world.multiworld.ActPlando[world.player].get(region.name), world.player)) + world.multiworld.get_region(world.options.ActPlando.get(region.name), world.player)) break # Already mapped onto something else @@ -607,12 +606,12 @@ def randomize_act_entrances(world: World): continue # Prevent Contractual Obligations from being inaccessible if contracts are not shuffled - if world.multiworld.ShuffleActContracts[world.player].value == 0: + if world.options.ShuffleActContracts.value == 0: if (region.name == "Your Contract has Expired" or region.name == "The Subcon Well") \ and candidate.name == "Contractual Obligations": continue - if world.multiworld.FinaleShuffle[world.player].value > 0 and region.name in chapter_finales: + if world.options.FinaleShuffle.value > 0 and region.name in chapter_finales: if candidate.name not in chapter_finales: continue @@ -687,17 +686,17 @@ def get_act_regions(world: World) -> typing.List[Region]: def is_act_blacklisted(world: World, name: str) -> bool: - plando: bool = name in world.multiworld.ActPlando[world.player].keys() \ - or name in world.multiworld.ActPlando[world.player].values() + plando: bool = name in world.options.ActPlando.keys() \ + or name in world.options.ActPlando.values() if name == "The Finale": - return not plando and world.multiworld.EndGoal[world.player].value == 1 + return not plando and world.options.EndGoal.value == 1 if name == "Rush Hour": - return not plando and world.multiworld.EndGoal[world.player].value == 2 + return not plando and world.options.EndGoal.value == 2 if name == "Time Rift - Tour": - return world.multiworld.ExcludeTour[world.player].value > 0 + return world.options.ExcludeTour.value > 0 return name in blacklisted_acts.values() @@ -714,7 +713,7 @@ def create_region(world: World, name: str) -> Region: if data.region == name: if key in storybook_pages.keys() \ - and world.multiworld.ShuffleStorybookPages[world.player].value == 0: + and world.options.ShuffleStorybookPages.value == 0: continue location = HatInTimeLocation(world.player, key, data.id, reg) @@ -732,9 +731,9 @@ def create_badge_seller(world: World) -> Region: count: int = 0 max_items: int = 0 - if world.multiworld.BadgeSellerMaxItems[world.player].value > 0: - max_items = world.random.randint(world.multiworld.BadgeSellerMinItems[world.player].value, - world.multiworld.BadgeSellerMaxItems[world.player].value) + if world.options.BadgeSellerMaxItems.value > 0: + max_items = world.random.randint(world.options.BadgeSellerMinItems.value, + world.options.BadgeSellerMaxItems.value) if max_items <= 0: world.set_badge_seller_count(0) @@ -801,7 +800,7 @@ def create_region_and_connect(world: World, def get_first_chapter_region(world: World) -> Region: - start_chapter: ChapterIndex = world.multiworld.StartingChapter[world.player] + start_chapter: ChapterIndex = world.options.StartingChapter.value return world.multiworld.get_region(chapter_regions.get(start_chapter), world.player) @@ -826,11 +825,11 @@ def get_shuffled_region(self, region: str) -> str: def create_thug_shops(world: World): - min_items: int = min(world.multiworld.NyakuzaThugMinShopItems[world.player].value, - world.multiworld.NyakuzaThugMaxShopItems[world.player].value) + min_items: int = min(world.options.NyakuzaThugMinShopItems.value, + world.options.NyakuzaThugMaxShopItems.value) - max_items: int = max(world.multiworld.NyakuzaThugMaxShopItems[world.player].value, - world.multiworld.NyakuzaThugMinShopItems[world.player].value) + max_items: int = max(world.options.NyakuzaThugMaxShopItems.value, + world.options.NyakuzaThugMinShopItems.value) count: int = -1 step: int = 0 old_name: str = "" diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py index 7eb09bedfc..f971526482 100644 --- a/worlds/ahit/Rules.py +++ b/worlds/ahit/Rules.py @@ -32,7 +32,7 @@ act_connections = { def can_use_hat(state: CollectionState, world: World, hat: HatType) -> bool: - if world.multiworld.HatItems[world.player].value > 0: + if world.options.HatItems.value > 0: return state.has(hat_type_to_item[hat], world.player) return state.count("Yarn", world.player) >= get_hat_cost(world, hat) @@ -54,19 +54,19 @@ def can_sdj(state: CollectionState, world: World): def painting_logic(world: World) -> bool: - return world.multiworld.ShuffleSubconPaintings[world.player].value > 0 + return world.options.ShuffleSubconPaintings.value > 0 # -1 = Normal, 0 = Moderate, 1 = Hard, 2 = Expert def get_difficulty(world: World) -> Difficulty: - return Difficulty(world.multiworld.LogicDifficulty[world.player].value) + return Difficulty(world.options.LogicDifficulty.value) def has_paintings(state: CollectionState, world: World, count: int, allow_skip: bool = True) -> bool: if not painting_logic(world): return True - if world.multiworld.NoPaintingSkips[world.player].value == 0 and allow_skip: + if world.options.NoPaintingSkips.value == 0 and allow_skip: # In Moderate there is a very easy trick to skip all the walls, except for the one guarding the boss arena if get_difficulty(world) >= Difficulty.MODERATE: return True @@ -75,7 +75,7 @@ def has_paintings(state: CollectionState, world: World, count: int, allow_skip: def zipline_logic(world: World) -> bool: - return world.multiworld.ShuffleAlpineZiplines[world.player].value > 0 + return world.options.ShuffleAlpineZiplines.value > 0 def can_use_hookshot(state: CollectionState, world: World): @@ -83,7 +83,7 @@ def can_use_hookshot(state: CollectionState, world: World): def can_hit(state: CollectionState, world: World, umbrella_only: bool = False): - if world.multiworld.UmbrellaLogic[world.player].value == 0: + if world.options.UmbrellaLogic.value == 0: return True return state.has("Umbrella", world.player) or not umbrella_only and can_use_hat(state, world, HatType.BREWING) @@ -132,7 +132,7 @@ def can_clear_metro(state: CollectionState, world: World) -> bool: def set_rules(world: World): # First, chapter access - starting_chapter = ChapterIndex(world.multiworld.StartingChapter[world.player].value) + starting_chapter = ChapterIndex(world.options.StartingChapter.value) world.set_chapter_cost(starting_chapter, 0) # Chapter costs increase progressively. Randomly decide the chapter order, except for Finale @@ -140,10 +140,10 @@ def set_rules(world: World): ChapterIndex.SUBCON, ChapterIndex.ALPINE] final_chapter = ChapterIndex.FINALE - if world.multiworld.EndGoal[world.player].value == 2: + if world.options.EndGoal.value == 2: final_chapter = ChapterIndex.METRO chapter_list.append(ChapterIndex.FINALE) - elif world.multiworld.EndGoal[world.player].value == 3: + elif world.options.EndGoal.value == 3: final_chapter = None chapter_list.append(ChapterIndex.FINALE) @@ -185,11 +185,11 @@ def set_rules(world: World): else: 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 + lowest_cost: int = world.options.LowestChapterCost.value + highest_cost: int = world.options.HighestChapterCost.value - cost_increment: int = world.multiworld.ChapterCostIncrement[world.player].value - min_difference: int = world.multiworld.ChapterCostMinDifference[world.player].value + cost_increment: int = world.options.ChapterCostIncrement.value + min_difference: int = world.options.ChapterCostMinDifference.value last_cost: int = 0 cost: int loop_count: int = 0 @@ -213,8 +213,8 @@ def set_rules(world: World): 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)) + world.options.FinalChapterMinCost.value, + world.options.FinalChapterMaxCost.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))) @@ -243,7 +243,7 @@ def set_rules(world: World): and state.has("Time Piece", world.player, world.get_chapter_cost(ChapterIndex.METRO)) and can_use_hat(state, world, HatType.DWELLER) and can_use_hat(state, world, HatType.ICE)) - if world.multiworld.ActRandomizer[world.player].value == 0: + if world.options.ActRandomizer.value == 0: set_default_rift_rules(world) table = location_table | event_locs @@ -267,10 +267,10 @@ def set_rules(world: World): if data.hookshot: add_rule(location, lambda state: can_use_hookshot(state, world)) - if data.umbrella and world.multiworld.UmbrellaLogic[world.player].value > 0: + if data.umbrella and world.options.UmbrellaLogic.value > 0: add_rule(location, lambda state: state.has("Umbrella", world.player)) - if data.paintings > 0 and world.multiworld.ShuffleSubconPaintings[world.player].value > 0: + if data.paintings > 0 and world.options.ShuffleSubconPaintings.value > 0: add_rule(location, lambda state, paintings=data.paintings: has_paintings(state, world, paintings)) if data.hit_requirement > 0: @@ -288,7 +288,7 @@ def set_rules(world: World): # Illness starts the player past the intro alpine_entrance = world.multiworld.get_entrance("AFR -> Alpine Skyline Area", world.player) add_rule(alpine_entrance, lambda state: can_use_hookshot(state, world)) - if world.multiworld.UmbrellaLogic[world.player].value > 0: + if world.options.UmbrellaLogic.value > 0: add_rule(alpine_entrance, lambda state: state.has("Umbrella", world.player)) if zipline_logic(world): @@ -356,9 +356,9 @@ def set_rules(world: World): set_event_rules(world) - if world.multiworld.EndGoal[world.player].value == 1: + if world.options.EndGoal.value == 1: world.multiworld.completion_condition[world.player] = lambda state: state.has("Time Piece Cluster", world.player) - elif world.multiworld.EndGoal[world.player].value == 2: + elif world.options.EndGoal.value == 2: world.multiworld.completion_condition[world.player] = lambda state: state.has("Rush Hour Cleared", world.player) @@ -551,7 +551,7 @@ def set_expert_rules(world: World): world.multiworld.get_region("Subcon Forest Area", world.player), "Subcon Forest Entrance YCHE", world.player) - if world.multiworld.NoPaintingSkips[world.player].value > 0: + if world.options.NoPaintingSkips.value > 0: add_rule(entrance, lambda state: has_paintings(state, world, 1)) set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player), @@ -630,7 +630,7 @@ def set_mafia_town_rules(world: World): add_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: state.has("HUMT Access", world.player), "or") - ctr_logic: int = world.multiworld.CTRLogic[world.player].value + ctr_logic: int = world.options.CTRLogic.value if ctr_logic == 3: set_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), lambda state: True) elif ctr_logic == 2: @@ -643,7 +643,7 @@ def set_mafia_town_rules(world: World): def set_botb_rules(world: World): - if world.multiworld.UmbrellaLogic[world.player].value == 0 and get_difficulty(world) < Difficulty.MODERATE: + if world.options.UmbrellaLogic.value == 0 and get_difficulty(world) < Difficulty.MODERATE: set_rule(world.multiworld.get_location("Dead Bird Studio - DJ Grooves Sign Chest", world.player), lambda state: state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING)) set_rule(world.multiworld.get_location("Dead Bird Studio - Tepee Chest", world.player), diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py index 047b788535..8dd16d4a90 100644 --- a/worlds/ahit/__init__.py +++ b/worlds/ahit/__init__.py @@ -3,7 +3,7 @@ from .Items import item_table, create_item, relic_groups, act_contracts, create_ 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 -from .Options import ahit_options, slot_data_options, adjust_options +from .Options import AHITOptions, slot_data_options, adjust_options from .Types import HatType, ChapterIndex, HatInTimeItem from .DeathWishLocations import create_dw_regions, dw_classes, death_wishes from .DeathWishRules import set_dw_rules, create_enemy_events @@ -49,7 +49,8 @@ class HatInTimeWorld(World): item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = get_location_names() - # option_definitions = ahit_options + options_dataclass = AHITOptions + options: AHITOptions act_connections: Dict[str, str] = {} shop_locs: List[str] = [] item_name_groups = relic_groups @@ -58,7 +59,7 @@ class HatInTimeWorld(World): def generate_early(self): adjust_options(self) - if self.multiworld.StartWithCompassBadge[self.player].value > 0: + if self.options.StartWithCompassBadge.value > 0: self.multiworld.push_precollected(self.create_item("Compass Badge")) if self.is_dw_only(): @@ -66,14 +67,14 @@ class HatInTimeWorld(World): # 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 + start_chapter: int = self.options.StartingChapter.value if start_chapter == 4 or start_chapter == 3: - if self.multiworld.ActRandomizer[self.player].value == 0: + if self.options.ActRandomizer.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: + if start_chapter == 3 and self.options.ShuffleSubconPaintings.value > 0: self.multiworld.push_precollected(self.create_item("Progressive Painting Unlock")) def create_regions(self): @@ -83,11 +84,11 @@ class HatInTimeWorld(World): nyakuza_thug_items[self.player] = {} badge_seller_count[self.player] = 0 self.shop_locs = [] - self.topology_present = self.multiworld.ActRandomizer[self.player].value + self.topology_present = self.options.ActRandomizer.value create_regions(self) - if self.multiworld.EnableDeathWish[self.player].value > 0: + if self.options.EnableDeathWish.value > 0: create_dw_regions(self) if self.is_dw_only(): @@ -100,7 +101,7 @@ class HatInTimeWorld(World): create_enemy_events(self) # place vanilla contract locations if contract shuffle is off - if self.multiworld.ShuffleActContracts[self.player].value == 0: + if self.options.ShuffleActContracts.value == 0: for name in contract_locations.keys(): self.multiworld.get_location(name, self.player).place_locked_item(create_item(self, name)) @@ -111,9 +112,9 @@ class HatInTimeWorld(World): hat_craft_order[self.player] = [HatType.SPRINT, HatType.BREWING, HatType.ICE, HatType.DWELLER, HatType.TIME_STOP] - if self.multiworld.HatItems[self.player].value == 0 and self.multiworld.RandomizeHatOrder[self.player].value > 0: + if self.options.HatItems.value == 0 and self.options.RandomizeHatOrder.value > 0: self.random.shuffle(hat_craft_order[self.player]) - if self.multiworld.RandomizeHatOrder[self.player].value == 2: + if self.options.RandomizeHatOrder.value == 2: hat_craft_order[self.player].remove(HatType.TIME_STOP) hat_craft_order[self.player].append(HatType.TIME_STOP) @@ -137,12 +138,12 @@ class HatInTimeWorld(World): self.multiworld.completion_condition[self.player] = lambda state: state.has("Death Wish Only Mode", self.player) - if self.multiworld.DWEnableBonus[self.player].value == 0: + if self.options.DWEnableBonus.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(): + if self.options.DWShuffle.value > 0 and name not in self.get_dw_shuffle(): continue full_clear = self.multiworld.get_location(f"{name} - All Clear", self.player) @@ -152,7 +153,7 @@ class HatInTimeWorld(World): return - if self.multiworld.ActRandomizer[self.player].value > 0: + if self.options.ActRandomizer.value > 0: randomize_act_entrances(self) set_rules(self) @@ -175,7 +176,7 @@ class HatInTimeWorld(World): "SeedNumber": str(self.multiworld.seed), # For shop prices "SeedName": self.multiworld.seed_name} - if self.multiworld.HatItems[self.player].value == 0: + if self.options.HatItems.value == 0: slot_data.setdefault("SprintYarnCost", hat_yarn_costs[self.player][HatType.SPRINT]) slot_data.setdefault("BrewingYarnCost", hat_yarn_costs[self.player][HatType.BREWING]) slot_data.setdefault("IceYarnCost", hat_yarn_costs[self.player][HatType.ICE]) @@ -187,7 +188,7 @@ class HatInTimeWorld(World): slot_data.setdefault("Hat4", int(hat_craft_order[self.player][3])) slot_data.setdefault("Hat5", int(hat_craft_order[self.player][4])) - if self.multiworld.ActRandomizer[self.player].value > 0: + if self.options.ActRandomizer.value > 0: for name in self.act_connections.keys(): slot_data[name] = self.act_connections[name] @@ -198,14 +199,14 @@ class HatInTimeWorld(World): 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": + if self.options.EndGoal.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: + if self.options.DWAutoCompleteBonuses.value == 0: for name in excluded_bonuses[self.player]: if name in excluded_dws[self.player]: continue @@ -213,19 +214,19 @@ class HatInTimeWorld(World): slot_data[f"excluded_bonus{i}"] = dw_classes[name] i += 1 - if self.multiworld.DWShuffle[self.player].value > 0: + if self.options.DWShuffle.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] - slot_data[option_name] = option.value + for name, value in self.options.as_dict(*self.options_dataclass.type_hints).items(): + if name in slot_data_options: + slot_data[name] = value return slot_data def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): - if self.is_dw_only() or self.multiworld.ActRandomizer[self.player].value == 0: + if self.is_dw_only() or self.options.ActRandomizer.value == 0: return new_hint_data = {} @@ -250,10 +251,10 @@ class HatInTimeWorld(World): new_hint_data[location.address] = get_shuffled_region(self, region_name) - if self.is_dlc1() and self.multiworld.Tasksanity[self.player].value > 0: + if self.is_dlc1() and self.options.Tasksanity.value > 0: 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): + for i in range(self.options.TasksanityCheckCount.value): new_hint_data[id_start+i] = ship_shape_region hint_data[self.player] = new_hint_data @@ -281,16 +282,16 @@ class HatInTimeWorld(World): return chapter_timepiece_costs[self.player] def is_dlc1(self) -> bool: - return self.multiworld.EnableDLC1[self.player].value > 0 + return self.options.EnableDLC1.value > 0 def is_dlc2(self) -> bool: - return self.multiworld.EnableDLC2[self.player].value > 0 + return self.options.EnableDLC2.value > 0 def is_dw(self) -> bool: - return self.multiworld.EnableDeathWish[self.player].value > 0 + return self.options.EnableDeathWish.value > 0 def is_dw_only(self) -> bool: - return self.is_dw() and self.multiworld.DeathWishOnly[self.player].value > 0 + return self.is_dw() and self.options.DeathWishOnly.value > 0 def get_excluded_dws(self): return excluded_dws[self.player] @@ -300,7 +301,7 @@ class HatInTimeWorld(World): 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" \ + if self.options.EndGoal.value == 3 and name == "Seal the Deal" \ and f"{name} - Main Objective" not in self.multiworld.exclude_locations[self.player]: return False From eb6f373e01cbceb17b50c1c293bd06af2ea12739 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Fri, 27 Oct 2023 13:36:49 -0400 Subject: [PATCH 3/9] Missed some --- worlds/ahit/Options.py | 84 +++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index 67bc5b56e6..9c19ae1fa3 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -5,64 +5,64 @@ from Options import Range, Toggle, DeathLink, Choice, OptionDict def adjust_options(world: World): - world.multiworld.HighestChapterCost[world.player].value = max( - world.multiworld.HighestChapterCost[world.player].value, - world.multiworld.LowestChapterCost[world.player].value) + world.options.HighestChapterCost.value = max( + world.options.HighestChapterCost.value, + world.options.LowestChapterCost.value) - world.multiworld.LowestChapterCost[world.player].value = min( - world.multiworld.LowestChapterCost[world.player].value, - world.multiworld.HighestChapterCost[world.player].value) + world.options.LowestChapterCost.value = min( + world.options.LowestChapterCost.value, + world.options.HighestChapterCost.value) - world.multiworld.FinalChapterMinCost[world.player].value = min( - world.multiworld.FinalChapterMinCost[world.player].value, - world.multiworld.FinalChapterMaxCost[world.player].value) + world.options.FinalChapterMinCost.value = min( + world.options.FinalChapterMinCost.value, + world.options.FinalChapterMaxCost.value) - world.multiworld.FinalChapterMaxCost[world.player].value = max( - world.multiworld.FinalChapterMaxCost[world.player].value, - world.multiworld.FinalChapterMinCost[world.player].value) + world.options.FinalChapterMaxCost.value = max( + world.options.FinalChapterMaxCost.value, + world.options.FinalChapterMinCost.value) - world.multiworld.BadgeSellerMinItems[world.player].value = min( - world.multiworld.BadgeSellerMinItems[world.player].value, - world.multiworld.BadgeSellerMaxItems[world.player].value) + world.options.BadgeSellerMinItems.value = min( + world.options.BadgeSellerMinItems.value, + world.options.BadgeSellerMaxItems.value) - world.multiworld.BadgeSellerMaxItems[world.player].value = max( - world.multiworld.BadgeSellerMinItems[world.player].value, - world.multiworld.BadgeSellerMaxItems[world.player].value) + world.options.BadgeSellerMaxItems.value = max( + world.options.BadgeSellerMinItems.value, + world.options.BadgeSellerMaxItems.value) total_tps: int = get_total_time_pieces(world) - if world.multiworld.HighestChapterCost[world.player].value > total_tps-5: - world.multiworld.HighestChapterCost[world.player].value = min(45, total_tps-5) + if world.options.HighestChapterCost.value > total_tps-5: + world.options.HighestChapterCost.value = min(45, total_tps-5) - if world.multiworld.LowestChapterCost[world.player].value > total_tps-5: - world.multiworld.LowestChapterCost[world.player].value = min(45, total_tps-5) + if world.options.LowestChapterCost.value > total_tps-5: + world.options.LowestChapterCost.value = min(45, total_tps-5) - if world.multiworld.FinalChapterMaxCost[world.player].value > total_tps: - world.multiworld.FinalChapterMaxCost[world.player].value = min(50, total_tps) + if world.options.FinalChapterMaxCost.value > total_tps: + world.options.FinalChapterMaxCost.value = min(50, total_tps) - if world.multiworld.FinalChapterMinCost[world.player].value > total_tps: - world.multiworld.FinalChapterMinCost[world.player].value = min(50, total_tps-5) + if world.options.FinalChapterMinCost.value > total_tps: + world.options.FinalChapterMinCost.value = min(50, total_tps-5) # Don't allow Rush Hour goal if DLC2 content is disabled - if world.multiworld.EndGoal[world.player].value == 2 and world.multiworld.EnableDLC2[world.player].value == 0: - world.multiworld.EndGoal[world.player].value = 1 + if world.options.EndGoal.value == 2 and world.options.EnableDLC2.value == 0: + world.options.EndGoal.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.options.EndGoal.value == 3 and not world.is_dw(): + world.options.EndGoal.value = 1 - if world.multiworld.DWEnableBonus[world.player].value > 0: - world.multiworld.DWAutoCompleteBonuses[world.player].value = 0 + if world.options.DWEnableBonus.value > 0: + world.options.DWAutoCompleteBonuses.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 = -1 - world.multiworld.DWTimePieceRequirement[world.player].value = 0 + world.options.EndGoal.value = 3 + world.options.ActRandomizer.value = 0 + world.options.ShuffleAlpineZiplines.value = 0 + world.options.ShuffleSubconPaintings.value = 0 + world.options.ShuffleStorybookPages.value = 0 + world.options.ShuffleActContracts.value = 0 + world.options.EnableDLC1.value = 0 + world.options.LogicDifficulty.value = -1 + world.options.DWTimePieceRequirement.value = 0 def get_total_time_pieces(world: World) -> int: @@ -73,7 +73,7 @@ def get_total_time_pieces(world: World) -> int: if world.is_dlc2(): count += 10 - return min(40+world.multiworld.MaxExtraTimePieces[world.player].value, count) + return min(40+world.options.MaxExtraTimePieces.value, count) class EndGoal(Choice): From eb2bd1c4d70dbd104344e2d7f0ee11e60f879977 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Fri, 27 Oct 2023 14:20:08 -0400 Subject: [PATCH 4/9] Missed some more --- worlds/ahit/Options.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index 9c19ae1fa3..4e8738b5ce 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -29,6 +29,22 @@ def adjust_options(world: World): world.options.BadgeSellerMinItems.value, world.options.BadgeSellerMaxItems.value) + world.options.NyakuzaThugMinShopItems.value = min( + world.options.NyakuzaThugMinShopItems.value, + world.options.NyakuzaThugMaxShopItems.value) + + world.options.NyakuzaThugMaxShopItems.value = max( + world.options.NyakuzaThugMinShopItems.value, + world.options.NyakuzaThugMaxShopItems.value) + + world.options.DWShuffleCountMin.value = min( + world.options.DWShuffleCountMin.value, + world.options.DWShuffleCountMax.value) + + world.options.DWShuffleCountMax.value = max( + world.options.DWShuffleCountMin.value, + world.options.DWShuffleCountMax.value) + total_tps: int = get_total_time_pieces(world) if world.options.HighestChapterCost.value > total_tps-5: world.options.HighestChapterCost.value = min(45, total_tps-5) From 5bd022138bff13347452016e30dd95996b7ea08e Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Thu, 7 Dec 2023 11:15:38 -0800 Subject: [PATCH 5/9] Pokemon Emerald: Fix missing rule for 2 items on Route 120 (#2570) Two items on Route 120 are on the other side of a pond but were considered accessible in logic without Surf. Creates a new separate region for these two items and adds a rule for being able to Surf to get to this region. Also adds the items to the existing surf test. --- worlds/pokemon_emerald/data/regions/routes.json | 13 +++++++++++-- worlds/pokemon_emerald/rules.py | 4 ++++ worlds/pokemon_emerald/test/test_accessibility.py | 8 +++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/worlds/pokemon_emerald/data/regions/routes.json b/worlds/pokemon_emerald/data/regions/routes.json index 029aa85c3c..f4b8d935c3 100644 --- a/worlds/pokemon_emerald/data/regions/routes.json +++ b/worlds/pokemon_emerald/data/regions/routes.json @@ -1106,21 +1106,30 @@ "parent_map": "MAP_ROUTE120", "locations": [ "ITEM_ROUTE_120_NUGGET", - "ITEM_ROUTE_120_FULL_HEAL", "ITEM_ROUTE_120_REVIVE", "ITEM_ROUTE_120_HYPER_POTION", - "HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2", "HIDDEN_ITEM_ROUTE_120_ZINC" ], "events": [], "exits": [ "REGION_ROUTE120/NORTH", + "REGION_ROUTE120/SOUTH_PONDS", "REGION_ROUTE121/WEST" ], "warps": [ "MAP_ROUTE120:0/MAP_ANCIENT_TOMB:0" ] }, + "REGION_ROUTE120/SOUTH_PONDS": { + "parent_map": "MAP_ROUTE120", + "locations": [ + "HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2", + "ITEM_ROUTE_120_FULL_HEAL" + ], + "events": [], + "exits": [], + "warps": [] + }, "REGION_ROUTE121/WEST": { "parent_map": "MAP_ROUTE121", "locations": [ diff --git a/worlds/pokemon_emerald/rules.py b/worlds/pokemon_emerald/rules.py index 97110746fb..564bf5af8d 100644 --- a/worlds/pokemon_emerald/rules.py +++ b/worlds/pokemon_emerald/rules.py @@ -626,6 +626,10 @@ def set_rules(world: "PokemonEmeraldWorld") -> None: get_entrance("REGION_ROUTE120/NORTH_POND_SHORE -> REGION_ROUTE120/NORTH_POND"), can_surf ) + set_rule( + get_entrance("REGION_ROUTE120/SOUTH -> REGION_ROUTE120/SOUTH_PONDS"), + can_surf + ) # Route 121 set_rule( diff --git a/worlds/pokemon_emerald/test/test_accessibility.py b/worlds/pokemon_emerald/test/test_accessibility.py index da3ca058be..853a92ffb8 100644 --- a/worlds/pokemon_emerald/test/test_accessibility.py +++ b/worlds/pokemon_emerald/test/test_accessibility.py @@ -44,13 +44,17 @@ class TestScorchedSlabPond(PokemonEmeraldTestBase): class TestSurf(PokemonEmeraldTestBase): options = { - "npc_gifts": Toggle.option_true + "npc_gifts": Toggle.option_true, + "hidden_items": Toggle.option_true, + "require_itemfinder": Toggle.option_false } def test_inaccessible_with_no_surf(self) -> None: self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER"))) self.assertFalse(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL"))) self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL"))) + self.assertFalse(self.can_reach_location(location_name_to_label("HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2"))) + self.assertFalse(self.can_reach_location(location_name_to_label("ITEM_ROUTE_120_FULL_HEAL"))) self.assertFalse(self.can_reach_entrance("REGION_ROUTE118/WATER -> REGION_ROUTE118/EAST")) self.assertFalse(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN")) self.assertFalse(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0")) @@ -60,6 +64,8 @@ class TestSurf(PokemonEmeraldTestBase): self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_PETALBURG_CITY_ETHER"))) self.assertTrue(self.can_reach_location(location_name_to_label("NPC_GIFT_RECEIVED_SOOTHE_BELL"))) self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_LILYCOVE_CITY_MAX_REPEL"))) + self.assertTrue(self.can_reach_location(location_name_to_label("HIDDEN_ITEM_ROUTE_120_RARE_CANDY_2"))) + self.assertTrue(self.can_reach_location(location_name_to_label("ITEM_ROUTE_120_FULL_HEAL"))) self.assertTrue(self.can_reach_entrance("REGION_ROUTE118/WATER -> REGION_ROUTE118/EAST")) self.assertTrue(self.can_reach_entrance("REGION_ROUTE119/UPPER -> REGION_FORTREE_CITY/MAIN")) self.assertTrue(self.can_reach_entrance("MAP_FORTREE_CITY:3/MAP_FORTREE_CITY_MART:0")) From bf801a1efe83cc894acb5e140a24dc962c02a3d9 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:40:44 +0100 Subject: [PATCH 6/9] The Witness: Fix Symmetry Island Upper Panel logic (2nd try) I got lazy and didn't properly test the last fix. Big apologies, I got a bit panicked with all the logic errors that were being found. --- worlds/witness/player_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 73253efc6e..e1ef1ae431 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -77,7 +77,7 @@ class WitnessPlayerLogic: these_items = all_options # Another dependency that is not power-based: The Symmetry Island Upper Panel latches - elif panel_hex == 0x18269: + elif panel_hex == "0x1C349": these_items = all_options # For any other door entity, we just return a set with the item that opens it & disregard power dependencies From abfc2ddfed783f30d3cf7880d6db8881d1de2432 Mon Sep 17 00:00:00 2001 From: beauxq Date: Thu, 7 Dec 2023 13:17:07 -0800 Subject: [PATCH 7/9] Zillion: fix retrieved packet processing --- ZillionClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ZillionClient.py b/ZillionClient.py index 30f4f600a6..5f3cbb943f 100644 --- a/ZillionClient.py +++ b/ZillionClient.py @@ -278,7 +278,7 @@ class ZillionContext(CommonContext): logger.warning(f"invalid Retrieved packet to ZillionClient: {args}") return keys = cast(Dict[str, Optional[str]], args["keys"]) - doors_b64 = keys[f"zillion-{self.auth}-doors"] + doors_b64 = keys.get(f"zillion-{self.auth}-doors", None) if doors_b64: logger.info("received door data from server") doors = base64.b64decode(doors_b64) From 9351fb45caab39a76e2773258a0816ce59aeb9a7 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Fri, 8 Dec 2023 01:17:12 -0500 Subject: [PATCH 8/9] SA2B: Fix KeyError on Unexpected Characters in Slot Names (#2571) There were no safeguards on characters being used as keys into a conversion dict. Now there are. --- worlds/sa2b/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/sa2b/__init__.py b/worlds/sa2b/__init__.py index 4ee03dce9d..7d77aebc4c 100644 --- a/worlds/sa2b/__init__.py +++ b/worlds/sa2b/__init__.py @@ -619,7 +619,7 @@ class SA2BWorld(World): for name in name_list_base: for char_idx in range(7): if char_idx < len(name): - name_list_s.append(chao_name_conversion[name[char_idx]]) + name_list_s.append(chao_name_conversion.get(name[char_idx], 0x5F)) else: name_list_s.append(0x00) From 98c29b77f393add0308c0b13e9efad919256e6f6 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Fri, 8 Dec 2023 11:07:30 -0500 Subject: [PATCH 9/9] Final touch-ups --- worlds/ahit/DeathWishRules.py | 2 +- worlds/ahit/Options.py | 2 ++ worlds/ahit/Rules.py | 2 +- worlds/ahit/docs/en_A Hat in Time.md | 8 ++++++-- worlds/ahit/docs/setup_en.md | 7 +++---- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/worlds/ahit/DeathWishRules.py b/worlds/ahit/DeathWishRules.py index da8d639a92..3eb92b3dfe 100644 --- a/worlds/ahit/DeathWishRules.py +++ b/worlds/ahit/DeathWishRules.py @@ -406,7 +406,7 @@ def create_enemy_events(world: World): and name not in world.get_dw_shuffle(): continue - region = world.options.get_region(name, world.player) + region = world.multiworld.get_region(name, world.player) event = HatInTimeLocation(world.player, f"Triple Enemy Picture - {name}", None, region) event.place_locked_item(HatInTimeItem("Triple Enemy Picture", ItemClassification.progression, None, world.player)) region.locations.append(event) diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index 626b4671c3..af21dae4c9 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -666,6 +666,7 @@ class AHITOptions(PerGameCommonOptions): MetroMaxPonCost: MetroMaxPonCost NyakuzaThugMinShopItems: NyakuzaThugMinShopItems NyakuzaThugMaxShopItems: NyakuzaThugMaxShopItems + NoTicketSkips: NoTicketSkips LowestChapterCost: LowestChapterCost HighestChapterCost: HighestChapterCost @@ -729,6 +730,7 @@ slot_data_options: List[str] = [ "MetroMinPonCost", "MetroMaxPonCost", "BaseballBat", + "NoTicketSkips", "MinPonCost", "MaxPonCost", diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py index 44a815d00d..1001645284 100644 --- a/worlds/ahit/Rules.py +++ b/worlds/ahit/Rules.py @@ -483,7 +483,7 @@ def set_moderate_rules(world: World): and can_use_hat(state, world, HatType.BREWING)) # Moderate: Bluefin Tunnel + Pink Paw Station without tickets - if world.multiworld.NoTicketSkips[world.player].value == 0: + if world.options.NoTicketSkips.value == 0: set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True) set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True) diff --git a/worlds/ahit/docs/en_A Hat in Time.md b/worlds/ahit/docs/en_A Hat in Time.md index c4a4341763..6bfb2196cf 100644 --- a/worlds/ahit/docs/en_A Hat in Time.md +++ b/worlds/ahit/docs/en_A Hat in Time.md @@ -9,11 +9,11 @@ config file. Items which the player would normally acquire throughout the game have been moved around. Chapter costs are randomized in a progressive order based on your settings, so for example you could go to Subcon Forest -> Battle of the Birds -> Alpine Skyline, etc. in that order. If act shuffle is turned on, the levels and Time Rifts in these chapters will be randomized as well. -To unlock and access a chapter's Time Rift in act shuffle, the levels in place of the original acts required to unlock the Time Rift in the vanilla game must be completed, and then you must enter a level that allows you to enter that Time Rift. For example, Time Rift: Bazaar requires Heating Up Mafia Town to be completed in the vanilla game. To unlock this Time Rift in act shuffle (and therefore the level it contains) you must complete the level that was shuffled in place of Heating Up Mafia Town and then enter the Time Rift through a Mafia Town level. +To unlock and access a chapter's Time Rift in act shuffle, the levels in place of the original acts required to unlock the Time Rift in the vanilla game must be completed, and then you must enter a level that allows you to access that Time Rift. For example, Time Rift: Bazaar requires Heating Up Mafia Town to be completed in the vanilla game. To unlock this Time Rift in act shuffle (and therefore the level it contains) you must complete the level that was shuffled in place of Heating Up Mafia Town and then enter the Time Rift through a Mafia Town level. ## What items and locations get shuffled? -Time Pieces, Relics, Yarn, Badges, and most other items are shuffled. Unlike in the vanilla game, yarn is typeless, and will be automatically crafted in a set order once you gather enough yarn for each hat. Any items in the world, shops, act completions, and optionally storybook pages or Death Wish contracts are the locations. +Time Pieces, Relics, Yarn, Badges, and most other items are shuffled. Unlike in the vanilla game, yarn is typeless, and will be automatically crafted in a set order once you gather enough yarn for each hat. Hats can also optionally be shuffled as individual items instead. Any items in the world, shops, act completions, and optionally storybook pages or Death Wish contracts are locations. Any freestanding items that are considered to be progression or useful will have a rainbow streak particle attached to them. Filler items will have a white glow attached to them instead. @@ -29,3 +29,7 @@ Items belonging to other worlds are represented by a badge with the Archipelago ## When the player receives an item, what happens? When the player receives an item, it will play the item collect effect and information about the item will be printed on the screen and in the in-game developer console. + +## Is the DLC required to play A Hat in Time in Archipelago? + +No, the DLC expansions are not required to play. Their content can be enabled through certain options that are disabled by default, but please don't turn them on if you don't own the respective DLC. \ No newline at end of file diff --git a/worlds/ahit/docs/setup_en.md b/worlds/ahit/docs/setup_en.md index d2db2fe47f..cea74380bd 100644 --- a/worlds/ahit/docs/setup_en.md +++ b/worlds/ahit/docs/setup_en.md @@ -11,7 +11,7 @@ 1. Have Steam running. Open the Steam console with [this link.](steam://open/console) 2. In the Steam console, enter the following command: -`download_depot 253230 253232 7770543545116491859`. Wait for the console to say the download is finished. +`download_depot 253230 253232 7770543545116491859`. ***Wait for the console to say the download is finished!*** 3. Once the download finishes, go to `steamapps/content/app_253230` in Steam's program folder. @@ -24,10 +24,9 @@ 7. Start up the game using your new shortcut. To confirm if you are on the correct version, go to Settings -> Game Settings. If you don't see an option labelled ***Live Game Events*** you should be running the correct version of the game. In Game Settings, make sure ***Enable Developer Console*** is checked. - ## Connecting to the Archipelago server -When you create a new save file, you should be prompted to enter your slot name, password, and Archipelago server address:port after loading into the Spaceship. Once that's done, the game will automatically connect to the multiserver using the info you entered whenever that save file is loaded. If you must change the IP or port for the save file, use the `ap_set_connection_info` console command. +To connect to the multiworld server, simply run the **ArchipelagoAHITClient** and connect it to the Archipelago server. The game will connect to the client automatically when you create a new save file. ## Console Commands @@ -38,6 +37,6 @@ Commands will not work on the title screen, you must be in-game to use them. To `ap_deathlink` - Toggle Death Link. -`ap_set_connection_info ` - Set the connection info for the save file. The IP address MUST BE IN QUOTES! +`ap_set_connection_info ` - Usually not necessary. Set the connection info for the save file. **The IP address MUST be in double quotes!** `ap_show_connection_info` - Show the connection info for the save file. \ No newline at end of file