import typing from collections.abc import Callable from BaseClasses import CollectionState from worlds.generic.Rules import exclusion_rules from worlds.AutoWorld import World from . import Constants # Helper functions # moved from logicmixin def has_iron_ingots(state: CollectionState, player: int) -> bool: return state.has('Progressive Tools', player) and state.has('Progressive Resource Crafting', player) def has_copper_ingots(state: CollectionState, player: int) -> bool: return state.has('Progressive Tools', player) and state.has('Progressive Resource Crafting', player) def has_gold_ingots(state: CollectionState, player: int) -> bool: return state.has('Progressive Resource Crafting', player) and (state.has('Progressive Tools', player, 2) or state.can_reach('The Nether', 'Region', player)) def has_diamond_pickaxe(state: CollectionState, player: int) -> bool: return state.has('Progressive Tools', player, 3) and has_iron_ingots(state, player) def craft_crossbow(state: CollectionState, player: int) -> bool: return state.has('Archery', player) and has_iron_ingots(state, player) def has_bottle(state: CollectionState, player: int) -> bool: return state.has('Bottles', player) and state.has('Progressive Resource Crafting', player) def has_spyglass(state: CollectionState, player: int) -> bool: return has_copper_ingots(state, player) and state.has('Spyglass', player) and can_adventure(state, player) def can_enchant(state: CollectionState, player: int) -> bool: return state.has('Enchanting', player) and has_diamond_pickaxe(state, player) # mine obsidian and lapis def can_use_anvil(state: CollectionState, player: int) -> bool: return state.has('Enchanting', player) and state.has('Progressive Resource Crafting', player, 2) and has_iron_ingots(state, player) def fortress_loot(state: CollectionState, player: int) -> bool: # saddles, blaze rods, wither skulls return state.can_reach('Nether Fortress', 'Region', player) and basic_combat(state, player) def can_brew_potions(state: CollectionState, player: int) -> bool: return state.has('Blaze Rods', player) and state.has('Brewing', player) and has_bottle(state, player) def can_piglin_trade(state: CollectionState, player: int) -> bool: return has_gold_ingots(state, player) and ( state.can_reach('The Nether', 'Region', player) or state.can_reach('Bastion Remnant', 'Region', player)) def overworld_villager(state: CollectionState, player: int) -> bool: village_region = state.multiworld.get_region('Village', player).entrances[0].parent_region.name if village_region == 'The Nether': # 2 options: cure zombie villager or build portal in village return (state.can_reach('Zombie Doctor', 'Location', player) or (has_diamond_pickaxe(state, player) and state.can_reach('Village', 'Region', player))) elif village_region == 'The End': return state.can_reach('Zombie Doctor', 'Location', player) return state.can_reach('Village', 'Region', player) def enter_stronghold(state: CollectionState, player: int) -> bool: return state.has('Blaze Rods', player) and state.has('Brewing', player) and state.has('3 Ender Pearls', player) # Difficulty-dependent functions def combat_difficulty(state: CollectionState, player: int) -> bool: return state.multiworld.combat_difficulty[player].current_key def can_adventure(state: CollectionState, player: int) -> bool: death_link_check = not state.multiworld.death_link[player] or state.has('Bed', player) if combat_difficulty(state, player) == 'easy': return state.has('Progressive Weapons', player, 2) and has_iron_ingots(state, player) and death_link_check elif combat_difficulty(state, player) == 'hard': return True return (state.has('Progressive Weapons', player) and death_link_check and (state.has('Progressive Resource Crafting', player) or state.has('Campfire', player))) def basic_combat(state: CollectionState, player: int) -> bool: if combat_difficulty(state, player) == 'easy': return state.has('Progressive Weapons', player, 2) and state.has('Progressive Armor', player) and \ state.has('Shield', player) and has_iron_ingots(state, player) elif combat_difficulty(state, player) == 'hard': return True return state.has('Progressive Weapons', player) and (state.has('Progressive Armor', player) or state.has('Shield', player)) and has_iron_ingots(state, player) def complete_raid(state: CollectionState, player: int) -> bool: reach_regions = state.can_reach('Village', 'Region', player) and state.can_reach('Pillager Outpost', 'Region', player) if combat_difficulty(state, player) == 'easy': return reach_regions and \ state.has('Progressive Weapons', player, 3) and state.has('Progressive Armor', player, 2) and \ state.has('Shield', player) and state.has('Archery', player) and \ state.has('Progressive Tools', player, 2) and has_iron_ingots(state, player) elif combat_difficulty(state, player) == 'hard': # might be too hard? return reach_regions and state.has('Progressive Weapons', player, 2) and has_iron_ingots(state, player) and \ (state.has('Progressive Armor', player) or state.has('Shield', player)) return reach_regions and state.has('Progressive Weapons', player, 2) and has_iron_ingots(state, player) and \ state.has('Progressive Armor', player) and state.has('Shield', player) def can_kill_wither(state: CollectionState, player: int) -> bool: normal_kill = state.has("Progressive Weapons", player, 3) and state.has("Progressive Armor", player, 2) and can_brew_potions(state, player) and can_enchant(state, player) if combat_difficulty(state, player) == 'easy': return fortress_loot(state, player) and normal_kill and state.has('Archery', player) elif combat_difficulty(state, player) == 'hard': # cheese kill using bedrock ceilings return fortress_loot(state, player) and (normal_kill or state.can_reach('The Nether', 'Region', player) or state.can_reach('The End', 'Region', player)) return fortress_loot(state, player) and normal_kill def can_respawn_ender_dragon(state: CollectionState, player: int) -> bool: return state.can_reach('The Nether', 'Region', player) and state.can_reach('The End', 'Region', player) and \ state.has('Progressive Resource Crafting', player) # smelt sand into glass def can_kill_ender_dragon(state: CollectionState, player: int) -> bool: if combat_difficulty(state, player) == 'easy': return state.has("Progressive Weapons", player, 3) and state.has("Progressive Armor", player, 2) and \ state.has('Archery', player) and can_brew_potions(state, player) and can_enchant(state, player) if combat_difficulty(state, player) == 'hard': return (state.has('Progressive Weapons', player, 2) and state.has('Progressive Armor', player)) or \ (state.has('Progressive Weapons', player, 1) and state.has('Bed', player)) return state.has('Progressive Weapons', player, 2) and state.has('Progressive Armor', player) and state.has('Archery', player) def has_structure_compass(state: CollectionState, entrance_name: str, player: int) -> bool: if not state.multiworld.structure_compasses[player]: return True return state.has(f"Structure Compass ({state.multiworld.get_entrance(entrance_name, player).connected_region.name})", player) def get_rules_lookup(player: int): rules_lookup: typing.Dict[str, typing.List[Callable[[CollectionState], bool]]] = { "entrances": { "Nether Portal": lambda state: (state.has('Flint and Steel', player) and (state.has('Bucket', player) or state.has('Progressive Tools', player, 3)) and has_iron_ingots(state, player)), "End Portal": lambda state: enter_stronghold(state, player) and state.has('3 Ender Pearls', player, 4), "Overworld Structure 1": lambda state: (can_adventure(state, player) and has_structure_compass(state, "Overworld Structure 1", player)), "Overworld Structure 2": lambda state: (can_adventure(state, player) and has_structure_compass(state, "Overworld Structure 2", player)), "Nether Structure 1": lambda state: (can_adventure(state, player) and has_structure_compass(state, "Nether Structure 1", player)), "Nether Structure 2": lambda state: (can_adventure(state, player) and has_structure_compass(state, "Nether Structure 2", player)), "The End Structure": lambda state: (can_adventure(state, player) and has_structure_compass(state, "The End Structure", player)), }, "locations": { "Ender Dragon": lambda state: can_respawn_ender_dragon(state, player) and can_kill_ender_dragon(state, player), "Wither": lambda state: can_kill_wither(state, player), "Blaze Rods": lambda state: fortress_loot(state, player), "Who is Cutting Onions?": lambda state: can_piglin_trade(state, player), "Oh Shiny": lambda state: can_piglin_trade(state, player), "Suit Up": lambda state: state.has("Progressive Armor", player) and has_iron_ingots(state, player), "Very Very Frightening": lambda state: (state.has("Channeling Book", player) and can_use_anvil(state, player) and can_enchant(state, player) and overworld_villager(state, player)), "Hot Stuff": lambda state: state.has("Bucket", player) and has_iron_ingots(state, player), "Free the End": lambda state: can_respawn_ender_dragon(state, player) and can_kill_ender_dragon(state, player), "A Furious Cocktail": lambda state: (can_brew_potions(state, player) and state.has("Fishing Rod", player) and # Water Breathing state.can_reach("The Nether", "Region", player) and # Regeneration, Fire Resistance, gold nuggets state.can_reach("Village", "Region", player) and # Night Vision, Invisibility state.can_reach("Bring Home the Beacon", "Location", player)), # Resistance "Bring Home the Beacon": lambda state: (can_kill_wither(state, player) and has_diamond_pickaxe(state, player) and state.has("Progressive Resource Crafting", player, 2)), "Not Today, Thank You": lambda state: state.has("Shield", player) and has_iron_ingots(state, player), "Isn't It Iron Pick": lambda state: state.has("Progressive Tools", player, 2) and has_iron_ingots(state, player), "Local Brewery": lambda state: can_brew_potions(state, player), "The Next Generation": lambda state: can_respawn_ender_dragon(state, player) and can_kill_ender_dragon(state, player), "Fishy Business": lambda state: state.has("Fishing Rod", player), "This Boat Has Legs": lambda state: ((fortress_loot(state, player) or complete_raid(state, player)) and state.has("Saddle", player) and state.has("Fishing Rod", player)), "Sniper Duel": lambda state: state.has("Archery", player), "Great View From Up Here": lambda state: basic_combat(state, player), "How Did We Get Here?": lambda state: (can_brew_potions(state, player) and has_gold_ingots(state, player) and # Absorption state.can_reach('End City', 'Region', player) and # Levitation state.can_reach('The Nether', 'Region', player) and # potion ingredients state.has("Fishing Rod", player) and state.has("Archery",player) and # Pufferfish, Nautilus Shells; spectral arrows state.can_reach("Bring Home the Beacon", "Location", player) and # Haste state.can_reach("Hero of the Village", "Location", player)), # Bad Omen, Hero of the Village "Bullseye": lambda state: (state.has("Archery", player) and state.has("Progressive Tools", player, 2) and has_iron_ingots(state, player)), "Spooky Scary Skeleton": lambda state: basic_combat(state, player), "Two by Two": lambda state: has_iron_ingots(state, player) and state.has("Bucket", player) and can_adventure(state, player), "Two Birds, One Arrow": lambda state: craft_crossbow(state, player) and can_enchant(state, player), "Who's the Pillager Now?": lambda state: craft_crossbow(state, player), "Getting an Upgrade": lambda state: state.has("Progressive Tools", player), "Tactical Fishing": lambda state: state.has("Bucket", player) and has_iron_ingots(state, player), "Zombie Doctor": lambda state: can_brew_potions(state, player) and has_gold_ingots(state, player), "Ice Bucket Challenge": lambda state: has_diamond_pickaxe(state, player), "Into Fire": lambda state: basic_combat(state, player), "War Pigs": lambda state: basic_combat(state, player), "Take Aim": lambda state: state.has("Archery", player), "Total Beelocation": lambda state: state.has("Silk Touch Book", player) and can_use_anvil(state, player) and can_enchant(state, player), "Arbalistic": lambda state: (craft_crossbow(state, player) and state.has("Piercing IV Book", player) and can_use_anvil(state, player) and can_enchant(state, player)), "The End... Again...": lambda state: can_respawn_ender_dragon(state, player) and can_kill_ender_dragon(state, player), "Acquire Hardware": lambda state: has_iron_ingots(state, player), "Not Quite \"Nine\" Lives": lambda state: can_piglin_trade(state, player) and state.has("Progressive Resource Crafting", player, 2), "Cover Me With Diamonds": lambda state: (state.has("Progressive Armor", player, 2) and state.has("Progressive Tools", player, 2) and has_iron_ingots(state, player)), "Sky's the Limit": lambda state: basic_combat(state, player), "Hired Help": lambda state: state.has("Progressive Resource Crafting", player, 2) and has_iron_ingots(state, player), "Sweet Dreams": lambda state: state.has("Bed", player) or state.can_reach('Village', 'Region', player), "You Need a Mint": lambda state: can_respawn_ender_dragon(state, player) and has_bottle(state, player), "Monsters Hunted": lambda state: (can_respawn_ender_dragon(state, player) and can_kill_ender_dragon(state, player) and can_kill_wither(state, player) and state.has("Fishing Rod", player)), "Enchanter": lambda state: can_enchant(state, player), "Voluntary Exile": lambda state: basic_combat(state, player), "Eye Spy": lambda state: enter_stronghold(state, player), "Serious Dedication": lambda state: (can_brew_potions(state, player) and state.has("Bed", player) and has_diamond_pickaxe(state, player) and has_gold_ingots(state, player)), "Postmortal": lambda state: complete_raid(state, player), "Adventuring Time": lambda state: can_adventure(state, player), "Hero of the Village": lambda state: complete_raid(state, player), "Hidden in the Depths": lambda state: can_brew_potions(state, player) and state.has("Bed", player) and has_diamond_pickaxe(state, player), "Beaconator": lambda state: (can_kill_wither(state, player) and has_diamond_pickaxe(state, player) and state.has("Progressive Resource Crafting", player, 2)), "Withering Heights": lambda state: can_kill_wither(state, player), "A Balanced Diet": lambda state: (has_bottle(state, player) and has_gold_ingots(state, player) and # honey bottle; gapple state.has("Progressive Resource Crafting", player, 2) and state.can_reach('The End', 'Region', player)), # notch apple, chorus fruit "Subspace Bubble": lambda state: has_diamond_pickaxe(state, player), "Country Lode, Take Me Home": lambda state: state.can_reach("Hidden in the Depths", "Location", player) and has_gold_ingots(state, player), "Bee Our Guest": lambda state: state.has("Campfire", player) and has_bottle(state, player), "Uneasy Alliance": lambda state: has_diamond_pickaxe(state, player) and state.has('Fishing Rod', player), "Diamonds!": lambda state: state.has("Progressive Tools", player, 2) and has_iron_ingots(state, player), "A Throwaway Joke": lambda state: can_adventure(state, player), "Sticky Situation": lambda state: state.has("Campfire", player) and has_bottle(state, player), "Ol' Betsy": lambda state: craft_crossbow(state, player), "Cover Me in Debris": lambda state: (state.has("Progressive Armor", player, 2) and state.has("8 Netherite Scrap", player, 2) and state.has("Progressive Resource Crafting", player) and has_diamond_pickaxe(state, player) and has_iron_ingots(state, player) and can_brew_potions(state, player) and state.has("Bed", player)), "Hot Topic": lambda state: state.has("Progressive Resource Crafting", player), "The Lie": lambda state: has_iron_ingots(state, player) and state.has("Bucket", player), "On a Rail": lambda state: has_iron_ingots(state, player) and state.has('Progressive Tools', player, 2), "When Pigs Fly": lambda state: ((fortress_loot(state, player) or complete_raid(state, player)) and state.has("Saddle", player) and state.has("Fishing Rod", player) and can_adventure(state, player)), "Overkill": lambda state: (can_brew_potions(state, player) and (state.has("Progressive Weapons", player) or state.can_reach('The Nether', 'Region', player))), "Librarian": lambda state: state.has("Enchanting", player), "Overpowered": lambda state: (has_iron_ingots(state, player) and state.has('Progressive Tools', player, 2) and basic_combat(state, player)), "Wax On": lambda state: (has_copper_ingots(state, player) and state.has('Campfire', player) and state.has('Progressive Resource Crafting', player, 2)), "Wax Off": lambda state: (has_copper_ingots(state, player) and state.has('Campfire', player) and state.has('Progressive Resource Crafting', player, 2)), "The Cutest Predator": lambda state: has_iron_ingots(state, player) and state.has('Bucket', player), "The Healing Power of Friendship": lambda state: has_iron_ingots(state, player) and state.has('Bucket', player), "Is It a Bird?": lambda state: has_spyglass(state, player) and can_adventure(state, player), "Is It a Balloon?": lambda state: has_spyglass(state, player), "Is It a Plane?": lambda state: has_spyglass(state, player) and can_respawn_ender_dragon(state, player), "Surge Protector": lambda state: (state.has("Channeling Book", player) and can_use_anvil(state, player) and can_enchant(state, player) and overworld_villager(state, player)), "Light as a Rabbit": lambda state: can_adventure(state, player) and has_iron_ingots(state, player) and state.has('Bucket', player), "Glow and Behold!": lambda state: can_adventure(state, player), "Whatever Floats Your Goat!": lambda state: can_adventure(state, player), "Caves & Cliffs": lambda state: has_iron_ingots(state, player) and state.has('Bucket', player) and state.has('Progressive Tools', player, 2), "Feels like home": lambda state: (has_iron_ingots(state, player) and state.has('Bucket', player) and state.has('Fishing Rod', player) and (fortress_loot(state, player) or complete_raid(state, player)) and state.has("Saddle", player)), "Sound of Music": lambda state: state.has("Progressive Tools", player, 2) and has_iron_ingots(state, player) and basic_combat(state, player), "Star Trader": lambda state: (has_iron_ingots(state, player) and state.has('Bucket', player) and (state.can_reach("The Nether", 'Region', player) or state.can_reach("Nether Fortress", 'Region', player) or # soul sand for water elevator can_piglin_trade(state, player)) and overworld_villager(state, player)), "Birthday Song": lambda state: state.can_reach("The Lie", "Location", player) and state.has("Progressive Tools", player, 2) and has_iron_ingots(state, player), "Bukkit Bukkit": lambda state: state.has("Bucket", player) and has_iron_ingots(state, player) and can_adventure(state, player), "It Spreads": lambda state: can_adventure(state, player) and has_iron_ingots(state, player) and state.has("Progressive Tools", player, 2), "Sneak 100": lambda state: can_adventure(state, player) and has_iron_ingots(state, player) and state.has("Progressive Tools", player, 2), "When the Squad Hops into Town": lambda state: can_adventure(state, player) and state.has("Lead", player), "With Our Powers Combined!": lambda state: can_adventure(state, player) and state.has("Lead", player), } } return rules_lookup def set_rules(mc_world: World) -> None: multiworld = mc_world.multiworld player = mc_world.player rules_lookup = get_rules_lookup(player) # Set entrance rules for entrance_name, rule in rules_lookup["entrances"].items(): multiworld.get_entrance(entrance_name, player).access_rule = rule # Set location rules for location_name, rule in rules_lookup["locations"].items(): multiworld.get_location(location_name, player).access_rule = rule # Set rules surrounding completion bosses = multiworld.required_bosses[player] postgame_advancements = set() if bosses.dragon: postgame_advancements.update(Constants.exclusion_info["ender_dragon"]) if bosses.wither: postgame_advancements.update(Constants.exclusion_info["wither"]) def location_count(state: CollectionState) -> bool: return len([location for location in multiworld.get_locations(player) if location.address != None and location.can_reach(state)]) def defeated_bosses(state: CollectionState) -> bool: return ((not bosses.dragon or state.has("Ender Dragon", player)) and (not bosses.wither or state.has("Wither", player))) egg_shards = min(multiworld.egg_shards_required[player], multiworld.egg_shards_available[player]) completion_requirements = lambda state: (location_count(state) >= multiworld.advancement_goal[player] and state.has("Dragon Egg Shard", player, egg_shards)) multiworld.completion_condition[player] = lambda state: completion_requirements(state) and defeated_bosses(state) # Set exclusions on hard/unreasonable/postgame excluded_advancements = set() if not multiworld.include_hard_advancements[player]: excluded_advancements.update(Constants.exclusion_info["hard"]) if not multiworld.include_unreasonable_advancements[player]: excluded_advancements.update(Constants.exclusion_info["unreasonable"]) if not multiworld.include_postgame_advancements[player]: excluded_advancements.update(postgame_advancements) exclusion_rules(multiworld, player, excluded_advancements)