From 714241b07af782586017249185a87b06da237de4 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Fri, 15 Sep 2023 18:38:43 -0400 Subject: [PATCH] 1.1 --- worlds/ahit/Items.py | 18 ++++++++++++++---- worlds/ahit/Options.py | 8 ++++++++ worlds/ahit/Regions.py | 10 +++++----- worlds/ahit/Rules.py | 23 ++++++++++++++++++++--- worlds/ahit/Types.py | 9 +++++++++ worlds/ahit/__init__.py | 27 ++++++++++++++------------- worlds/ahit/test/TestActs.py | 24 ++++++++++++++++++++++++ worlds/ahit/test/TestBase.py | 5 +++++ worlds/ahit/test/__init__.py | 0 9 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 worlds/ahit/test/TestActs.py create mode 100644 worlds/ahit/test/TestBase.py create mode 100644 worlds/ahit/test/__init__.py diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py index 4bf7157166..d987d4662b 100644 --- a/worlds/ahit/Items.py +++ b/worlds/ahit/Items.py @@ -1,6 +1,6 @@ from BaseClasses import Item, ItemClassification from worlds.AutoWorld import World -from .Types import HatDLC, HatType +from .Types import HatDLC, HatType, hat_type_to_item from .Locations import get_total_locations from .Rules import get_difficulty, is_player_knowledgeable from typing import Optional, NamedTuple, List, Dict @@ -18,7 +18,7 @@ class HatInTimeItem(Item): def create_itempool(world: World) -> List[Item]: itempool: List[Item] = [] - if not world.is_dw_only(): + if not world.is_dw_only() and world.multiworld.HatItems[world.player].value == 0: calculate_yarn_costs(world) yarn_pool: List[Item] = create_multiple_items(world, "Yarn", world.multiworld.YarnAvailable[world.player].value, @@ -36,6 +36,9 @@ 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(): + continue + item_type: ItemClassification = item_table.get(name).classification if world.is_dw_only(): @@ -181,7 +184,13 @@ def create_junk_items(world: World, count: int) -> List[Item]: ahit_items = { "Yarn": ItemData(300001, ItemClassification.progression_skip_balancing), "Time Piece": ItemData(300002, ItemClassification.progression_skip_balancing), - "Progressive Painting Unlock": ItemData(300003, ItemClassification.progression), + + # for HatItems option + "Sprint Hat": ItemData(300049, ItemClassification.progression), + "Brewing Hat": ItemData(300050, ItemClassification.progression), + "Ice Hat": ItemData(300051, ItemClassification.progression), + "Dweller Mask": ItemData(300052, ItemClassification.progression), + "Time Stop Hat": ItemData(300053, ItemClassification.progression), # Relics "Relic (Burger Patty)": ItemData(300006, ItemClassification.progression), @@ -210,8 +219,9 @@ ahit_items = { "Camera Badge": ItemData(300042, ItemClassification.progression, HatDLC.death_wish), # Other - "Umbrella": ItemData(300033, ItemClassification.progression), "Badge Pin": ItemData(300043, ItemClassification.useful), + "Umbrella": ItemData(300033, ItemClassification.progression), + "Progressive Painting Unlock": ItemData(300003, ItemClassification.progression), # Garbage items "25 Pons": ItemData(300034, ItemClassification.filler), diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index 9edb7e3ab5..39501f17b9 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -312,6 +312,12 @@ class MinExtraYarn(Range): default = 10 +class HatItems(Toggle): + """Removes all yarn from the pool and turns the hats into individual items instead.""" + display_name = "Hat Items" + default = 0 + + class MinPonCost(Range): """The minimum amount of Pons that any shop item can cost.""" display_name = "Minimum Shop Pon Cost" @@ -648,6 +654,7 @@ ahit_options: typing.Dict[str, type(Option)] = { "YarnCostMax": YarnCostMax, "YarnAvailable": YarnAvailable, "MinExtraYarn": MinExtraYarn, + "HatItems": HatItems, "MinPonCost": MinPonCost, "MaxPonCost": MaxPonCost, @@ -675,6 +682,7 @@ slot_data_options: typing.Dict[str, type(Option)] = { "ShuffleStorybookPages": ShuffleStorybookPages, "ShuffleActContracts": ShuffleActContracts, "ShuffleSubconPaintings": ShuffleSubconPaintings, + "HatItems": HatItems, "EnableDLC1": EnableDLC1, "Tasksanity": Tasksanity, diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py index 62e089e3cc..5c7beec2b8 100644 --- a/worlds/ahit/Regions.py +++ b/worlds/ahit/Regions.py @@ -433,6 +433,11 @@ def create_rift_connections(world: World, region: Region): connect_regions(act_region, region, entrance_name, world.player) i += 1 + # fix for some weird keyerror from tests + if region.name == "Time Rift - Rumbi Factory": + for entrance in region.entrances: + world.multiworld.get_entrance(entrance.name, world.player) + def create_tasksanity_locations(world: World): ship_shape: Region = world.multiworld.get_region("Ship Shape", world.player) @@ -873,11 +878,6 @@ def create_events(world: World) -> int: event: Location = create_event(name, world.multiworld.get_region(data.region, world.player), world) event.show_in_spoiler = False - - if data.act_complete_event: - act_completion: str = f"Act Completion ({data.region})" - event.access_rule = world.multiworld.get_location(act_completion, world.player).access_rule - count += 1 return count diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py index c9bda9abb8..b6e3ef50bf 100644 --- a/worlds/ahit/Rules.py +++ b/worlds/ahit/Rules.py @@ -1,8 +1,8 @@ from worlds.AutoWorld import World, CollectionState from worlds.generic.Rules import add_rule, set_rule from .Locations import location_table, tihs_locations, zipline_unlocks, is_location_valid, contract_locations, \ - shop_locations -from .Types import HatType, ChapterIndex + shop_locations, event_locs +from .Types import HatType, ChapterIndex, hat_type_to_item from BaseClasses import Location, Entrance, Region import typing @@ -32,6 +32,9 @@ act_connections = { def can_use_hat(state: CollectionState, world: World, hat: HatType) -> bool: + if world.multiworld.HatItems[world.player].value > 0: + return state.has(hat_type_to_item[hat], world.player) + return state.count("Yarn", world.player) >= get_hat_cost(world, hat) @@ -257,8 +260,9 @@ def set_rules(world: World): if world.multiworld.ActRandomizer[world.player].value == 0: set_default_rift_rules(world) + table = location_table | event_locs location: Location - for (key, data) in location_table.items(): + for (key, data) in table.items(): if not is_location_valid(world, key): continue @@ -340,6 +344,8 @@ def set_rules(world: World): for rules in access_rules: add_rule(e, rules) + set_event_rules(world) + for entrance in world.multiworld.get_region("Alpine Free Roam", world.player).entrances: add_rule(entrance, lambda state: can_use_hookshot(state, world)) if world.multiworld.UmbrellaLogic[world.player].value > 0: @@ -851,6 +857,17 @@ def set_default_rift_rules(world: World): add_rule(entrance, lambda state: has_relic_combo(state, world, "Necklace")) +def set_event_rules(world: World): + for (name, data) in event_locs.items(): + if not is_location_valid(world, name): + continue + + event: Location = world.multiworld.get_location(name, world.player) + + if data.act_complete_event: + add_rule(event, world.multiworld.get_location(f"Act Completion ({data.region})", world.player).access_rule) + + def connect_regions(start_region: Region, exit_region: Region, entrancename: str, player: int) -> Entrance: entrance = Entrance(player, entrancename, start_region) start_region.exits.append(entrance) diff --git a/worlds/ahit/Types.py b/worlds/ahit/Types.py index 45f5535b58..2915512f57 100644 --- a/worlds/ahit/Types.py +++ b/worlds/ahit/Types.py @@ -26,3 +26,12 @@ class ChapterIndex(IntEnum): FINALE = 5 CRUISE = 6 METRO = 7 + + +hat_type_to_item = { + HatType.SPRINT: "Sprint Hat", + HatType.BREWING: "Brewing Hat", + HatType.ICE: "Ice Hat", + HatType.DWELLER: "Dweller Mask", + HatType.TIME_STOP: "Time Stop Hat", +} diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py index 9c251e902c..5b2b902770 100644 --- a/worlds/ahit/__init__.py +++ b/worlds/ahit/__init__.py @@ -18,7 +18,6 @@ excluded_bonuses: Dict[int, List[str]] = {} dw_shuffle: Dict[int, List[str]] = {} nyakuza_thug_items: Dict[int, Dict[str, int]] = {} badge_seller_count: Dict[int, int] = {} -badge_seller_count: Dict[int, int] = {} class AWebInTime(WebWorld): @@ -112,7 +111,7 @@ class HatInTimeWorld(World): hat_craft_order[self.player] = [HatType.SPRINT, HatType.BREWING, HatType.ICE, HatType.DWELLER, HatType.TIME_STOP] - if self.multiworld.RandomizeHatOrder[self.player].value > 0: + if self.multiworld.HatItems[self.player].value == 0 and self.multiworld.RandomizeHatOrder[self.player].value > 0: self.random.shuffle(hat_craft_order[self.player]) if self.multiworld.RandomizeHatOrder[self.player].value == 2: hat_craft_order[self.player].remove(HatType.TIME_STOP) @@ -165,26 +164,28 @@ class HatInTimeWorld(World): return create_item(self, name) def fill_slot_data(self) -> dict: - slot_data: dict = {"SprintYarnCost": hat_yarn_costs[self.player][HatType.SPRINT], - "BrewingYarnCost": hat_yarn_costs[self.player][HatType.BREWING], - "IceYarnCost": hat_yarn_costs[self.player][HatType.ICE], - "DwellerYarnCost": hat_yarn_costs[self.player][HatType.DWELLER], - "TimeStopYarnCost": hat_yarn_costs[self.player][HatType.TIME_STOP], - "Chapter1Cost": chapter_timepiece_costs[self.player][ChapterIndex.MAFIA], + slot_data: dict = {"Chapter1Cost": chapter_timepiece_costs[self.player][ChapterIndex.MAFIA], "Chapter2Cost": chapter_timepiece_costs[self.player][ChapterIndex.BIRDS], "Chapter3Cost": chapter_timepiece_costs[self.player][ChapterIndex.SUBCON], "Chapter4Cost": chapter_timepiece_costs[self.player][ChapterIndex.ALPINE], "Chapter5Cost": chapter_timepiece_costs[self.player][ChapterIndex.FINALE], "Chapter6Cost": chapter_timepiece_costs[self.player][ChapterIndex.CRUISE], "Chapter7Cost": chapter_timepiece_costs[self.player][ChapterIndex.METRO], - "Hat1": int(hat_craft_order[self.player][0]), - "Hat2": int(hat_craft_order[self.player][1]), - "Hat3": int(hat_craft_order[self.player][2]), - "Hat4": int(hat_craft_order[self.player][3]), - "Hat5": int(hat_craft_order[self.player][4]), "BadgeSellerItemCount": badge_seller_count[self.player], "SeedNumber": self.multiworld.seed} # For shop prices + if self.multiworld.HatItems[self.player].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]) + slot_data.setdefault("DwellerYarnCost", hat_yarn_costs[self.player][HatType.DWELLER]) + slot_data.setdefault("TimeStopYarnCost", hat_yarn_costs[self.player][HatType.TIME_STOP]) + slot_data.setdefault("Hat1", int(hat_craft_order[self.player][0])) + slot_data.setdefault("Hat2", int(hat_craft_order[self.player][1])) + slot_data.setdefault("Hat3", int(hat_craft_order[self.player][2])) + 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: for name in self.act_connections.keys(): slot_data[name] = self.act_connections[name] diff --git a/worlds/ahit/test/TestActs.py b/worlds/ahit/test/TestActs.py new file mode 100644 index 0000000000..0b9983ca15 --- /dev/null +++ b/worlds/ahit/test/TestActs.py @@ -0,0 +1,24 @@ +from worlds.ahit.Regions import act_chapters +from worlds.ahit.test.TestBase import HatInTimeTestBase + + +class TestActs(HatInTimeTestBase): + options = { + "ActRandomizer": 2, + "EnableDLC1": 1, + "EnableDLC2": 1, + } + + def test_act_shuffle(self): + for i in range(1000): + self.world_setup() + self.collect_all_but([""]) + + for name in act_chapters.keys(): + region = self.multiworld.get_region(name, 1) + for entrance in region.entrances: + self.assertTrue(self.can_reach_entrance(entrance.name), + f"Can't reach {name} from {entrance}\n" + f"{entrance.parent_region.entrances[0]} -> {entrance.parent_region} " + f"-> {entrance} -> {name}" + f" (expected method of access)") diff --git a/worlds/ahit/test/TestBase.py b/worlds/ahit/test/TestBase.py new file mode 100644 index 0000000000..1eb4dd6555 --- /dev/null +++ b/worlds/ahit/test/TestBase.py @@ -0,0 +1,5 @@ +from test.TestBase import WorldTestBase + + +class HatInTimeTestBase(WorldTestBase): + game = "A Hat in Time" diff --git a/worlds/ahit/test/__init__.py b/worlds/ahit/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2