diff --git a/worlds/yooka_laylee/Options.py b/worlds/yooka_laylee/Options.py index 5168d115265..5de13240cff 100644 --- a/worlds/yooka_laylee/Options.py +++ b/worlds/yooka_laylee/Options.py @@ -24,6 +24,10 @@ class PagiesRequiredForCapitalB(Range): range_end = 145 default = 100 +class GrandTomeRandomization(Toggle): + """If enabled, randomizes the order of the Grand Tome worlds. This does not change the location of the Grand Tomes within Hivory Towers or the cost of the Grand Tomes, only the world you go to when entering a Grand Tome. Galleon Galaxy will never be randomized in place of Tribalstack Tropics.""" + display_name = "Randomize Grand Tome World Order" + class DisableQuizzes(Toggle): """If enabled, causes the quizzes to be completely skipped.""" display_name = "Disable Quizzes" @@ -33,6 +37,7 @@ class DisableQuizzes(Toggle): "prevent_tropics_bk": PreventTropicsBK, "flappy_flight_location": FlappyFlightLocation, "capital_b_pagie_count": PagiesRequiredForCapitalB, + "randomize_grand_tomes": GrandTomeRandomization, "disable_quizzes": DisableQuizzes, "death_link": DeathLink } diff --git a/worlds/yooka_laylee/Rules.py b/worlds/yooka_laylee/Rules.py index 8f98d966ed1..7eb8cac6763 100644 --- a/worlds/yooka_laylee/Rules.py +++ b/worlds/yooka_laylee/Rules.py @@ -6,6 +6,14 @@ import pkgutil cashinotokens_table = json.loads(pkgutil.get_data(__name__, "cashinotokens.json").decode()) +requiredPagiesPerTomeVanillaOrder = [ + [1, 4], + [7, 12], + [19, 27], + [37, 48], + [60, 75] +] +randomizedWorldOrder = [] class YookaLayleeLogic(LogicMixin): yookaLaylee_specialRequirements = { @@ -35,6 +43,19 @@ class YookaLayleeLogic(LogicMixin): "": lambda state, player: state.has("Flappy Flight", player) or state.has("Glide", player) or state.has("Health Booster", player, 5) } + yookaLaylee_tomeRequirementsVanillaOrder = [ + lambda self, player: self.yookaLaylee_can_access_HT_hub_entrance(player) and (self.has("Reptile Roll", player) or self.has("Flappy Flight", player)), + lambda self, player: self.yookaLaylee_can_access_HT_hub_B(player) and (self.has("Glide", player) or self.has("Flappy Flight", player)), + lambda self, player: self.yookaLaylee_can_access_HT_waterworks(player) and (self.has("Buddy Bubble", player) or self.has("Lizard Lash", player) or self.has("Flappy Flight", player)), + lambda self, player: self.yookaLaylee_can_access_HT_outside(player) and self.has("Camo Cloak", player), + lambda self, player: self.yookaLaylee_can_access_HT_finalArea(player) + ] + + def yookaLaylee_checkRequirementsForWorld(self, player, worldIdentifier, expanded = False): + worldOrderIndex = randomizedWorldOrder.index(worldIdentifier) + return (self.has("Pagie", player, requiredPagiesPerTomeVanillaOrder[worldOrderIndex][0 if expanded else 1]) + and self.yookaLaylee_tomeRequirementsVanillaOrder[worldOrderIndex](self, player)) + def yookaLaylee_can_access_HT_hub_entrance(self, player): return self.yookaLaylee_has_requirements("", player) @@ -55,45 +76,37 @@ def yookaLaylee_can_access_HT_finalArea(self, player): return self.yookaLaylee_can_access_HT_outside(player) and self.has("Flappy Flight", player) def yookaLaylee_can_access_tropics(self, player): - return (self.has("Pagie", player, 1) - and self.yookaLaylee_can_access_HT_hub_entrance(player) - and (self.has("Reptile Roll", player) or self.has("Flappy Flight", player))) + return self.yookaLaylee_checkRequirementsForWorld(player, "TT") def yookaLaylee_can_access_tropics_exp(self, player): - return self.has("Pagie", player, 4) and self.yookaLaylee_can_access_tropics(player) + return self.yookaLaylee_checkRequirementsForWorld(player, "TT", True) def yookaLaylee_can_access_glacier(self, player): - return (self.has("Pagie", player, 7) - and self.yookaLaylee_can_access_HT_hub_B(player) - and (self.has("Glide", player) or self.has("Flappy Flight", player))) + return self.yookaLaylee_checkRequirementsForWorld(player, "GG") def yookaLaylee_can_access_glacier_exp(self, player): - return self.has("Pagie", player, 12) and self.yookaLaylee_can_access_glacier(player) + return self.yookaLaylee_checkRequirementsForWorld(player, "GG", True) def yookaLaylee_can_access_marsh(self, player): - return (self.has("Pagie", player, 19) - and self.yookaLaylee_can_access_HT_waterworks(player) - and (self.has("Buddy Bubble", player) or self.has("Lizard Lash", player) or self.has("Flappy Flight", player))) + return self.yookaLaylee_checkRequirementsForWorld(player, "MM") def yookaLaylee_can_access_marsh_exp(self, player): - return self.has("Pagie", player, 27) and self.yookaLaylee_can_access_marsh(player) + return self.yookaLaylee_checkRequirementsForWorld(player, "MM", True) def yookaLaylee_can_access_cashino(self, player): - return (self.has("Pagie", player, 37) - and self.yookaLaylee_can_access_HT_outside(player) - and self.has("Camo Cloak", player)) + return self.yookaLaylee_checkRequirementsForWorld(player, "CC") def yookaLaylee_can_access_cashino_exp(self, player): - return self.has("Pagie", player, 48) and self.yookaLaylee_can_access_cashino(player) + return self.yookaLaylee_checkRequirementsForWorld(player, "CC", True) def yookaLaylee_can_access_galaxy(self, player): - return self.has("Pagie", player, 60) and self.yookaLaylee_can_access_HT_finalArea(player) + return self.yookaLaylee_checkRequirementsForWorld(player, "GY") def yookaLaylee_can_access_galaxy_exp(self, player): - return self.has("Pagie", player, 75) and self.yookaLaylee_can_access_galaxy(player) + return self.yookaLaylee_checkRequirementsForWorld(player, "GY", True) def yookaLaylee_can_access_end(self, player): # Technically don't need Tail Twirl and Sonar Shield, but the final boss is pretty miserable without it - return (self.has("Pagie", player, 100) + return (self.has("Pagie", player, 100) # Pagie count should not affect item placement, since endgame is locked. TODO Verify this. and self.yookaLaylee_can_access_HT_finalArea(player) and self.yookaLaylee_has_requirements("Sonar Shield", player) and self.has("Tail Twirl", player)) @@ -141,7 +154,9 @@ def yookaLaylee_has_requirements(self, requirements, player, searchMode = 0): else: raise Exception(f"Invalid requirements: {str(requirements)}") -def set_rules(world, player): +def set_rules(world, player, regionOrder): + global randomizedWorldOrder + randomizedWorldOrder = regionOrder regionChecks = { "Shipwreck Creek": lambda state: True, "Hivory Towers Entrance": lambda state: state.yookaLaylee_can_access_HT_hub_entrance(player), diff --git a/worlds/yooka_laylee/__init__.py b/worlds/yooka_laylee/__init__.py index 12b29a37d39..fb90aa0f052 100644 --- a/worlds/yooka_laylee/__init__.py +++ b/worlds/yooka_laylee/__init__.py @@ -8,7 +8,6 @@ from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, Tutorial from ..AutoWorld import World, WebWorld - class YookaWeb(WebWorld): tutorials = [Tutorial( "Multiworld Setup Guide", @@ -35,10 +34,17 @@ class YookaWorld(World): location_name_to_id = locations_lookup_name_to_id option_definitions = yooka_options + grandTomeOrder = ["TT", "GG", "MM", "CC", "GY"] - data_version = 2 + data_version = 3 required_client_version = (1, 0, 0) + def generate_early(self): + if self.options.randomize_grand_tomes: + self.random.shuffle(self.grandTomeOrder) + while self.grandTomeOrder[0] is "GY": + self.random.shuffle(self.grandTomeOrder) + def create_items(self): # Set up prefill data for later if not hasattr(self.multiworld, "yookaLaylee_prefillItems"): @@ -102,7 +108,7 @@ def create_items(self): self.multiworld.itempool += pool def set_rules(self): - set_rules(self.multiworld, self.player) + set_rules(self.multiworld, self.player, self.grandTomeOrder) def create_regions(self): create_regions(self.multiworld, self.player) @@ -135,6 +141,7 @@ def pre_fill(self): def fill_slot_data(self): return { "CapitalBPagieCount": self.options.capital_b_pagie_count.value, + "WorldOrder": self.grandTomeOrder, "DisableQuizzes": bool(self.options.disable_quizzes.value), "DeathLink": bool(self.options.death_link.value) }