import collections
import logging
from collections import deque

import OverworldGlitchRules
from BaseClasses import (
    CollectionState,
    CrystalBarrier,
    DoorType,
    Entrance,
    KeyRuleType,
    LocationType,
    PotFlags,
    RegionType,
    Terrain,
)
from Dungeons import dungeon_table
from OverworldGlitchRules import overworld_glitches_rules
from OWEdges import OWExitTypes
from RoomData import DoorKind
from source.dungeon.EnemyList import EnemySprite, Sprite
from source.enemizer.EnemyLogic import and_rule as and_rule_new
from source.enemizer.EnemyLogic import defeat_rule_multiple, defeat_rule_single
from source.enemizer.EnemyLogic import or_rule as or_rule_new
from source.enemizer.EnemyLogic import special_rules_check, special_rules_for_region
from source.logic.Rule import RuleFactory
from UnderworldGlitchRules import underworld_glitches_rules


def set_rules(world, player):
    if world.logic[player] == 'nologic':
        logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
        world.get_region('Menu', player).can_reach_private = lambda state: True
        for exit in world.get_region('Menu', player).exits:
            exit.hide_path = True
        return

    global_rules(world, player)
    ow_inverted_rules(world, player)

    if world.swords[player] == 'swordless':
        swordless_rules(world, player)

    if world.logic[player] == 'noglitches':
        no_glitches_rules(world, player)
    elif world.logic[player] == 'minorglitches':
        logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.')
        no_glitches_rules(world, player)
        fake_flipper_rules(world, player)
    elif world.logic[player] in ['owglitches', 'hybridglitches']:
        logging.getLogger('').info('There is a chance OWG has bugged edge case rulesets, especially in inverted. Definitely file a report on GitHub if you see anything strange.')
        # Initially setting no_glitches_rules to set the baseline rules for some
        # entrances. The overworld_glitches_rules set is primarily additive.
        no_glitches_rules(world, player)
        fake_flipper_rules(world, player)
        overworld_glitches_rules(world, player)
    else:
        raise NotImplementedError('Not implemented yet')

    ow_bunny_rules(world, player)
    ow_terrain_rules(world, player)

    if world.is_premature_copied_world:
        return

    if world.mode[player] == 'standard':
        standard_rules(world, player)
    else:
        misc_key_rules(world, player)

    bomb_rules(world, player)
    pot_rules(world, player)
    drop_rules(world, player)
    challenge_room_rules(world, player)

    if world.custom_goals[player]['ganongoal'] and 'requirements' in world.custom_goals[player]['ganongoal']:
        rule = get_goal_rule('ganongoal', world, player)
        add_rule(world.get_location('Ganon', player), rule)
    else:
        if world.goal[player] == 'dungeons':
            # require all dungeons to beat ganon
            add_rule(world.get_location('Ganon', player), lambda state: state.has_beaten_aga(player) and state.has('Beat Agahnim 2', player) and state.has('Beat Boss', player, 10))
        elif world.goal[player] in ['crystals', 'ganon']:
            add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player))
            if world.goal[player] == 'ganon':
                # require aga2 to beat ganon
                add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
        elif world.goal[player] in ['triforcehunt', 'trinity']:
            if world.goal[player] == 'trinity':
                add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player))
        elif world.goal[player] == 'ganonhunt':
            add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
        elif world.goal[player] == 'bosshunt':
            if world.bosshunt_include_agas[player]:
                add_rule(world.get_location('Ganon', player), lambda state: 
                         state.item_count('Beat Agahnim 1', player) +
                         state.item_count('Beat Agahnim 2', player) +
                         state.item_count('Beat Boss', player) >= world.bosses_ganon[player])
            else:
                add_rule(world.get_location('Ganon', player), lambda state: 
                         state.item_count('Beat Boss', player) >= world.bosses_ganon[player])
        elif world.goal[player] == 'completionist':
            add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))

    for location in world.get_region('Hyrule Castle Courtyard', player).locations:
        if location.name == 'Murahdahla':
            if world.custom_goals[player]['murahgoal'] and 'requirements' in world.custom_goals[player]['murahgoal']:
                rule = get_goal_rule('murahgoal', world, player)
                add_rule(location, rule)
            else:
                add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))

    if (world.flute_mode[player] not in ['active', 'pseudo'] and not world.is_tile_swapped(0x18, player)
            and 'Ocarina (Activated)' not in list(map(str, [i for i in world.precollected_items if i.player == player]))):
        # Commented out below, this would be needed for rando implementations where Inverted requires flute activation in bunny territory
        # kak_region = self.world.get_region('Kakariko Village', player)
        # add_rule(world.get_location('Flute Activation', player), lambda state: state.has('Ocarina', player) and state.is_not_bunny(kak_region, player))
        add_rule(world.get_location('Flute Activation', player), lambda state: state.has('Ocarina', player))

    # if swamp and dam have not been moved we require mirror for swamp palace
    if not world.swamp_patch_required[player]:
        add_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has_Mirror(player))

    set_bunny_rules(world, player, world.mode[player] == 'inverted')

    # These rules go here because they overwrite/add to some of the above rules
    if world.logic[player] == 'hybridglitches':
        underworld_glitches_rules(world, player)

def mirrorless_path_to_location(world, startName, targetName, player):
    # If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
    # Only considering the secret passage for now (in non-insanity shuffle).  Basically, if it's Ganon you need the master sword.
    start = world.get_region(startName, player)
    target = world.get_region(targetName, player)
    is_same_world = start.type == target.type or target.type.is_indoors
    seen = {start}
    queue = collections.deque([(start, [])])
    while queue:
        (current, path) = queue.popleft()
        for entrance in current.exits:
            if (entrance.connected_region is not None and entrance.connected_region not in seen
                and (entrance.connected_region.type == start.type or entrance.connected_region.type.is_indoors
                    or (not is_same_world and ((world.mode[player] != 'inverted' and start.type == RegionType.LightWorld)
                        or (world.mode[player] == 'inverted' and start.type == RegionType.DarkWorld))))):
                new_path = path + [entrance.access_rule]
                if entrance.connected_region == target:
                    return new_path
                else:
                    queue.append((entrance.connected_region, new_path))
                    seen.add(entrance.connected_region)

def mirrorless_path_to_castle_courtyard(world, player):
    # If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
    # Only considering the secret passage for now (in non-insanity shuffle).  Basically, if it's Ganon you need the master sword.
    start = world.get_entrance('Hyrule Castle Secret Entrance Drop', player)
    if start.connected_region == world.get_region('Sewer Drop', player):
        return [lambda state: False]  # not handling dungeons for now
    target = world.get_region('Hyrule Castle Courtyard', player)
    seen = {start.parent_region, start.connected_region}
    queue = collections.deque([(start.connected_region, [])])
    while queue:
        (current, path) = queue.popleft()
        if current:
            for entrance in current.exits:
                if entrance.connected_region not in seen:
                    new_path = path + [entrance.access_rule]
                    if entrance.connected_region == target:
                        return new_path
                    else:
                        queue.append((entrance.connected_region, new_path))
                        seen.add(entrance.connected_region)


def set_rule(spot, rule):
    spot.access_rule = rule


def set_defeat_dungeon_boss_rule(entrance):
    # Lambda required to defer evaluation of dungeon.boss since it will change later if boss shuffle is used
    set_rule(entrance, lambda state: entrance.parent_region.dungeon.boss.can_defeat(state))
    if entrance.parent_region.dungeon.name == 'Thieves Town':
        add_rule(entrance, lambda state: entrance.parent_region.dungeon.boss.name != 'Blind' or state.has('Maiden Unmasked', entrance.player))


def set_always_allow(spot, rule):
    spot.always_allow = rule


def add_rule_new(spot, rule, combine='and'):
    if combine == 'and':
        spot.verbose_rule = and_rule_new(*[spot.verbose_rule, rule])
    else:
        spot.verbose_rule = or_rule_new(*[spot.verbose_rule, rule])
    add_rule(spot, rule.rule_lambda, combine)


def add_rule(spot, rule, combine='and'):
    old_rule = spot.access_rule
    if combine == 'or':
        spot.access_rule = lambda state: rule(state) or old_rule(state)
    else:
        spot.access_rule = lambda state: rule(state) and old_rule(state)


def get_goal_rule(goal_type, world, player):
    goal_data = world.custom_goals[player][goal_type]
    if goal_data['requirements'][0]['condition'] == 0x00:
        return lambda state: False
    rule = None
    def add_to_rule(new_rule):
        nonlocal rule
        if rule is None:
            rule = new_rule
        else:
            rule = and_rule(rule, new_rule)
    if 'logic' in goal_data:
        for logic, data in goal_data['logic'].items():
            if logic == 'pendants':
                pendants = int(data)
                add_to_rule(lambda state: state.has_pendants(pendants, player))
            elif logic == 'crystals':
                crystals = int(data)
                add_to_rule(lambda state: state.has_crystals(crystals, player))
            elif logic == 'pendant_bosses':
                pendant_bosses = int(data)
                add_to_rule(lambda state: state.has_pendant_bosses(pendant_bosses, player))
            elif logic == 'crystal_bosses':
                crystal_bosses = int(data)
                add_to_rule(lambda state: state.has_crystal_bosses(crystal_bosses, player))
            elif logic == 'bosses':
                bosses = int(data)
                add_to_rule(lambda state: state.has('Beat Boss', player, bosses))
            elif logic == 'aga1':
                add_to_rule(lambda state: state.has('Beat Agahnim 1', player))
            elif logic == 'aga2':
                add_to_rule(lambda state: state.has('Beat Agahnim 2', player))
            elif logic == 'goal_items':
                if data is not None:
                    goal_items = int(data)
                    add_to_rule(lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= goal_items)
                else:
                    add_to_rule(lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
            elif logic == 'collection':
                if data is not None:
                    all_locations = [x for x in world.get_filled_locations(player) if not x.locked]
                    collection = int(data) - len(all_locations)
                    add_to_rule(lambda state: state.everything(player, collection))
                else:
                    add_to_rule(lambda state: state.everything(player))
            elif logic == 'item':
                for item in data:
                    item_name = item
                    if '(' in item_name:
                        item_name, region_name = item_name.rsplit(' (', 1)
                        region_name = region_name.rstrip(')')
                        region = world.get_region(region_name, player)
                        if region and region.dungeon:
                            region_name = region.dungeon.name
                        else:
                            try:
                                if world.get_dungeon(region_name, player):
                                    pass
                            except:
                                raise Exception(f'Invalid dungeon/region name in custom goal logic for item {item}')
                        item_name = f'{item_name} ({region_name})'
                    if '=' in item_name:
                        item_name, count = item_name.rsplit('=', 1)
                        count = int(count)
                        add_to_rule(lambda state: state.has(item_name, player, count))
                    else:
                        add_to_rule(lambda state: state.has(item_name, player))
            elif logic == 'access':
                for region_name in data:
                    region = world.get_region(region_name, player)
                    if not region:
                        raise Exception(f'Invalid region name in custom goal logic for region: {region_name}')
                    add_to_rule(lambda state: state.can_reach(region, None, player))
            elif logic == 'ability':
                for ability in data:
                    param = None
                    if '(' in ability:
                        ability, param = ability.split('(', 1)
                        param = param.rstrip(')')
                    if ability == 'FarmBombs':
                        add_to_rule(lambda state: state.can_farm_bombs(player))
                    elif ability == 'FarmRupees':
                        add_to_rule(lambda state: state.can_farm_rupees(player))
                    elif ability == 'NoBunny':
                        if not param:
                            raise Exception(f'NoBunny ability requires a region argument in custom goal logic')
                        bunny_region = param
                        region = world.get_region(bunny_region, player)
                        if region:
                            add_to_rule(lambda state: state.is_not_bunny(bunny_region, player))
                        else:
                            raise Exception(f'Invalid region name in custom goal logic for NoBunny ability: {param}')
                    elif ability == 'CanUseBombs':
                        add_to_rule(lambda state: state.can_use_bombs(player))
                    elif ability == 'CanBonkDrop':
                        add_to_rule(lambda state: state.can_collect_bonkdrops(player))
                    elif ability == 'CanLift':
                        add_to_rule(lambda state: state.can_lift_rocks(player))
                    elif ability == 'MagicExtension':
                        magic_count = 16
                        if param:
                            magic_count = int(param)
                        add_to_rule(lambda state: state.can_extend_magic(player, magic_count))
                    elif ability == 'CanStun':
                        add_to_rule(lambda state: state.can_stun_enemies(player))
                    elif ability == 'CanKill':
                        if param:
                            enemy_count = int(param)
                            add_to_rule(lambda state: state.can_kill_most_things(player, enemy_count))
                        else:
                            add_to_rule(lambda state: state.can_kill_most_things(player))
                    elif ability == 'CanShootArrows':
                        add_to_rule(lambda state: state.can_shoot_arrows(player))
                    elif ability == 'CanFlute':
                        add_to_rule(lambda state: state.can_flute(player))
                    elif ability == 'HasFire':
                        add_to_rule(lambda state: state.has_fire_source(player))
                    elif ability == 'CanMelt':
                        add_to_rule(lambda state: state.can_melt_things(player))
                    elif ability == 'HasMMMedallion':
                        add_to_rule(lambda state: state.has_misery_mire_medallion(player))
                    elif ability == 'HasTRMedallion':
                        add_to_rule(lambda state: state.has_turtle_rock_medallion(player))
    return rule if rule is not None else lambda state: True

def add_bunny_rule(spot, player):
    if spot.can_cause_bunny(player):
        add_rule(spot, lambda state: state.has_Pearl(player))


def or_rule(rule1, rule2):
    return lambda state: rule1(state) or rule2(state)


def and_rule(rule1, rule2):
    return lambda state: rule1(state) and rule2(state)


def add_lamp_requirement(spot, player):
    add_rule(spot, lambda state: state.world.dark_rooms[player] not in ['require_lamp'] or state.has('Lamp', player))


def forbid_item(location, item, player):
    old_rule = location.item_rule
    location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i)


def add_item_rule(location, rule):
    old_rule = location.item_rule
    location.item_rule = lambda item: rule(item) and old_rule(item)


def item_in_locations(state, item, player, locations):
    for location in locations:
        if item_name(state, location[0], location[1]) == (item, player):
            return True
    return False


def item_name(state, location, player):
    location = state.world.get_location(location, player)
    if location.item is None:
        return None
    return (location.item.name, location.item.player)


def global_rules(world, player):
    # ganon can only carry triforce
    add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)

    # we can s&q to the old man house after we rescue him. This may be somewhere completely different if caves are shuffled!
    world.get_region('Menu', player).can_reach_private = lambda state: True
    for exit in world.get_region('Menu', player).exits:
        exit.hide_path = True
    
    world.get_region('Flute Sky', player).can_reach_private = lambda state: True
    #for exit in world.get_region('Flute Sky', player).exits:
    #    exit.hide_path = True

    # s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached
    set_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.has('Return Old Man', player))
    set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player))

    # flute rules
    set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 2', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 3', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 4', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 5', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 6', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 7', player), lambda state: state.can_flute(player))
    set_rule(world.get_entrance('Flute Spot 8', player), lambda state: state.can_flute(player))

    # overworld location rules
    if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal']:
        rule = get_goal_rule('pedgoal', world, player)
        set_rule(world.get_location('Master Sword Pedestal', player), rule)
    else:
        set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player))
    set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player))
    set_rule(world.get_location('Old Man', player), lambda state: state.has('Return Old Man', player))
    set_rule(world.get_location('Old Man Drop Off', player), lambda state: state.has('Escort Old Man', player))
    set_rule(world.get_location('Turtle Medallion Pad', player), lambda state: state.has_sword(player) and state.has_turtle_rock_medallion(player))  # sword required to cast magic (!)
    set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player))
    set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player))
    set_rule(world.get_location('Kiki Assistance', player), lambda state: state.has('Pick Up Kiki', player))  # Can S&Q with chest
    set_rule(world.get_location('Middle Aged Man', player), lambda state: state.has('Pick Up Purple Chest', player))  # Can S&Q with chest
    set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Deliver Purple Chest', player))  # Can S&Q with chest
    set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
    set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player))
    set_rule(world.get_location('Big Bomb', player), lambda state: state.has('Crystal 5', player) and state.has('Crystal 6', player))

    # bonk items
    if world.shuffle_bonk_drops[player]:
        if not world.is_premature_copied_world:
            from Regions import bonk_prize_table
            for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items():
                loc = world.get_location(location_name, player)
                if location_name == 'Cold Fairy Statue':
                    set_rule(loc, lambda state: state.can_use_bombs(player) and state.can_collect_bonkdrops(player))
                elif not aga_required:
                    set_rule(loc, lambda state: state.can_collect_bonkdrops(player))
                else:
                    set_rule(loc, lambda state: state.can_collect_bonkdrops(player) and state.has_beaten_aga(player))
                add_bunny_rule(loc, player)
    
    # underworld location rules
    set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and state.can_reach('Potion Shop Area', 'Region', player))
    set_rule(world.get_location('Sick Kid', player), lambda state: state.has_bottle(player))
    set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player))
    set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player))
    set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player))
    set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player))
    set_rule(world.get_location('Library', player), lambda state: state.has_Boots(player))
    set_rule(world.get_location('Spike Cave', player), lambda state:
             state.has('Hammer', player) and state.can_lift_rocks(player) and
             ((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
             (state.has('Cane of Byrna', player) and
              (state.can_extend_magic(player, 12, True) or
              (state.world.can_take_damage[player] and (state.has_Boots(player) or state.has_hearts(player, 4))))))
             )

    # underworld rules
    set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player))  # can erase block - overridden in noglitches
    set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False)  # drop cannot be climbed up
    set_rule(world.get_entrance('Hookshot Cave Bonk Path', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
    set_rule(world.get_entrance('Hookshot Cave Hook Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Bumper Cave Bottom to Top', player), lambda state: state.has('Cape', player))
    set_rule(world.get_entrance('Bumper Cave Top To Bottom', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player))

    # terrain rules
    set_rule(world.get_entrance('DM Hammer Bridge (West)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('DM Hammer Bridge (East)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('DM Broken Bridge (West)', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('DM Broken Bridge (East)', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Fairy Ascension Rocks (Inner)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Fairy Ascension Rocks (Outer)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('TR Pegs Ledge Entry', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Mountain Pass Rock (Outer)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Mountain Pass Rock (Inner)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Zora Waterfall Water Entry', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Zora Waterfall Approach', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Kakariko Pond Whirlpool', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Kings Grave Rocks (Outer)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Kings Grave Rocks (Inner)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Potion Shop Rock (South)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Zora Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
    set_rule(world.get_entrance('Zora Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
    set_rule(world.get_entrance('Hyrule Castle East Rock (Inner)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Hyrule Castle East Rock (Outer)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Wooden Bridge Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Wooden Bridge Northeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Blacksmith Ledge Peg (West)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Blacksmith Ledge Peg (East)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Desert Statue Move', player), lambda state: state.has('Book of Mudora', player))
    set_rule(world.get_entrance('Desert Ledge Rocks (Outer)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Desert Ledge Rocks (Inner)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('C Whirlpool Rock (Bottom)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('C Whirlpool Rock (Top)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('C Whirlpool Pegs (Outer)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('C Whirlpool Pegs (Inner)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Lake Hylia Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Lake Hylia Central Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Lake Hylia Island Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Lake Hylia Water D Leave', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Ice Cave Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Desert Pass Rocks (North)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Desert Pass Rocks (South)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), lambda state: state.has('Flippers', player))

    set_rule(world.get_entrance('Skull Woods Rock (West)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Skull Woods Rock (East)', player), lambda state: state.can_lift_rocks(player))
    # this more like an ohko rule - dependent on bird being present too - so enemizer could turn this off?
    set_rule(world.get_entrance('Bumper Cave Ledge Drop', player), lambda state: state.has('Cape', player) or state.has('Cane of Byrna', player) or state.has_sword(player))
    set_rule(world.get_entrance('Bumper Cave Rock (Outer)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Bumper Cave Rock (Inner)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Qirn Jump Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Dark Witch Rock (North)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Dark Witch Rock (South)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Catfish Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
    set_rule(world.get_entrance('Catfish Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
    set_rule(world.get_entrance('Bush Yard Pegs (Outer)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Bush Yard Pegs (Inner)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Broken Bridge Hammer Rock (South)', player), lambda state: state.can_lift_rocks(player) or state.has('Hammer', player))
    set_rule(world.get_entrance('Broken Bridge Hammer Rock (North)', player), lambda state: state.can_lift_rocks(player) or state.has('Hammer', player))
    set_rule(world.get_entrance('Broken Bridge Hookshot Gap', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Broken Bridge Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Broken Bridge Northeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Broken Bridge West Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Peg Area Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Peg Area Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Dig Game To Ledge Drop', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Frog Rock (Inner)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Frog Rock (Outer)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Archery Game Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Archery Game Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player))
    set_rule(world.get_entrance('Hammer Bridge Pegs (North)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Hammer Bridge Pegs (South)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Hammer Bridge Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Dark C Whirlpool Rock (Bottom)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Dark C Whirlpool Rock (Top)', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Dark C Whirlpool Pegs (Outer)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Dark C Whirlpool Pegs (Inner)', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Ice Lake Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Ice Lake Northeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Ice Lake Southwest Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Ice Lake Iceberg Water Entry', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Ice Lake Iceberg Bomb Jump', player), lambda state: state.can_use_bombs(player))
    set_rule(world.get_entrance('Shopping Mall Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), lambda state: state.has('Flippers', player))

    # entrance rules
    # Caution: If king's grave is relaxed at all to account for reaching it via a two way cave's exit in insanity mode, then the bomb shop logic will need to be updated (that would involve create a small ledge-like Region for it)
    # TODO: Not sure if this ^ is true anymore since Kings Grave is its own region now
    set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has_beaten_aga(player))
    set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('Checkerboard Cave', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('20 Rupee Cave', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_entrance('Hookshot Cave', player), lambda state: state.can_lift_rocks(player))
    set_rule(world.get_location('Pyramid Crack', player), lambda state: state.has('Pick Up Big Bomb', player))
    set_rule(world.get_entrance('Pyramid Crack', player), lambda state: state.has('Detonate Big Bomb', player))
    set_rule(world.get_entrance('Hammer Peg Cave', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Bonk Fairy (Dark)', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: state.can_lift_rocks(player))

    set_rule(world.get_entrance('Palace of Darkness', player), lambda state: state.has('Dark Palace Opened', player))
    set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player))
    set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player))  # sword required to cast magic (!)
    set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Turtle Opened', player))

    if world.custom_goals[player]['gtentry'] and 'requirements' in world.custom_goals[player]['gtentry']:
        rule = get_goal_rule('gtentry', world, player)
        set_rule(world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player), rule)
    else:
        set_rule(world.get_entrance('Ganons Tower' if not world.is_atgt_swapped(player) else 'Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
    if not world.is_atgt_swapped(player):
        set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player))

    # Start of door rando rules
    # TODO: Do these need to flag off when door rando is off? - some of them, yes

    def is_trapped(entrance):
        return world.get_entrance(entrance, player).door.trapped

    # Eastern Palace
    # Eyegore room needs a bow
    # set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player))
    # set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player))
    set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player))

    # Boss rules. Same as below but no BK or arrow requirement.
    set_defeat_dungeon_boss_rule(world.get_entrance('Eastern Palace Boss', player))

    # Desert
    set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Desert Palace Boss', player))

    # Tower of Hera
    set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player))
    set_rule(world.get_entrance('Hera Big Chest Hook Path', player), lambda state: state.has('Hookshot', player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Tower of Hera Boss', player))

    # Castle Tower
    set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
    set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))

    set_rule(world.get_entrance('PoD Arena Landing Bonk Path', player), lambda state: state.has_Boots(player))
    # set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
    # set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player))
    set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
    set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('PoD Dark Pegs Landing to Right', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('PoD Dark Pegs Right to Landing', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('PoD Turtle Party NW', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('PoD Turtle Party ES', player), lambda state: state.has('Hammer', player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Palace of Darkness Boss', player))

    set_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
    set_rule(world.get_entrance('Swamp Entrance Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
    set_rule(world.get_entrance('Swamp Trench 1 Approach Dry', player), lambda state: not state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Key Ledge Dry', player), lambda state: not state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Departure Dry', player), lambda state: not state.has('Trench 1 Filled', player))
    # these two are here so that, if they flood the area before finding flippers, nothing behind there can lock out the flippers
    set_rule(world.get_entrance('Swamp Trench 1 Nexus Approach', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Swamp Trench 1 Nexus Key', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Swamp Trench 1 Approach Key', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Approach Swim Depart', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Key Approach', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Key Ledge Depart', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Departure Approach', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 1 Departure Key', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
    set_rule(world.get_location('Trench 1 Switch', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('Swamp Hub Hook Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Swamp Hub Side Hook Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Swamp Trench 2 Pots Dry', player), lambda state: not state.has('Trench 2 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 2 Pots Wet', player), lambda state: state.has('Flippers', player) and state.has('Trench 2 Filled', player))
    set_rule(world.get_entrance('Swamp Trench 2 Departure Wet', player), lambda state: state.has('Flippers', player) and state.has('Trench 2 Filled', player))
    set_rule(world.get_entrance('Swamp West Ledge Hook Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Swamp Barrier Ledge Hook Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Swamp Drain Right Switch', player), lambda state: state.has('Drained Swamp', player))
    set_rule(world.get_entrance('Swamp Drain WN', player), lambda state: state.has('Drained Swamp', player))
    # this might be unnecesssary for an insanity style shuffle
    set_rule(world.get_entrance('Swamp Flooded Room WS', player), lambda state: state.has('Drained Swamp', player))
    set_rule(world.get_entrance('Swamp Flooded Room Ladder', player), lambda state: state.has('Drained Swamp', player))
    set_rule(world.get_entrance('Swamp Flooded Spot Ladder', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
    set_rule(world.get_entrance('Swamp Drain Left Up Stairs', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
    set_rule(world.get_entrance('Swamp Waterway NW', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Swamp Waterway N', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: state.has('Flippers', player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Swamp Palace Boss', player))

    set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
    if is_trapped('Skull Torch Room WS'):
        set_rule(world.get_entrance('Skull Torch Room WS', player), lambda state: state.has('Fire Rod', player))
    set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))

    hidden_pits_door = world.get_door('Skull Small Hall WS', player)

    def hidden_pits_rule(state):
        return state.has('Hidden Pits', player)

    if hidden_pits_door.bigKey:
        key_logic = world.key_logic[player][hidden_pits_door.entrance.parent_region.dungeon.name]
        hidden_pits_rule = and_rule(hidden_pits_rule, create_rule(key_logic.bk_name, player))
    elif hidden_pits_door.smallKey:
        d_name = hidden_pits_door.entrance.parent_region.dungeon.name
        hidden_pits_rule = and_rule(hidden_pits_rule, eval_small_key_door('Skull Small Hall WS', d_name, player))

    set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player)
             or hidden_pits_rule(state))
    set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), hidden_pits_rule)
    set_defeat_dungeon_boss_rule(world.get_entrance('Skull Woods Boss', player))

    # blind can't have the small key? - not necessarily true anymore - but likely still

    set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: state.has('Hammer', player))
    for entrance in ['Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Block Path', 'Thieves Conveyor Bridge Block Path']:
        set_rule(world.get_entrance(entrance, player), lambda state: state.can_lift_rocks(player))

    # I think these rules are unnecessary now - testing needed
    # for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']:
    #     forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player)
    # forbid_item(world.get_location('Thieves\' Town - Blind\'s Cell', player), 'Big Key (Thieves Town)', player)
    # for location in ['Suspicious Maiden', 'Thieves\' Town - Blind\'s Cell']:
    #     set_rule(world.get_location(location, player), lambda state: state.has('Big Key (Thieves Town)', player))
    set_rule(world.get_location('Revealing Light', player), lambda state: state.has('Shining Light', player) and state.has('Maiden Rescued', player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Thieves Town Boss', player))

    set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player))
    if is_trapped('Ice Lobby SE'):
        set_rule(world.get_entrance('Ice Lobby SE', player), lambda state: state.can_melt_things(player))
    set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
    set_rule(world.get_entrance('Ice Right H Path', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
    set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
    set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
    set_rule(world.get_entrance('Ice Antechamber Hole', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
    # todo: ohko rules for spike room - could split into two regions instead of these, but can_take_damage is usually true
    set_rule(world.get_entrance('Ice Spike Room WS', player), lambda state: state.world.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
    set_rule(world.get_entrance('Ice Spike Room Up Stairs', player), lambda state: state.world.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
    set_rule(world.get_entrance('Ice Spike Room Down Stairs', player), lambda state: state.world.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
    set_rule(world.get_location('Ice Palace - Spike Room', player), lambda state: state.world.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))
    set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.can_melt_things(player))
    set_rule(world.get_entrance('Ice Hookshot Ledge Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player))
    if not world.get_door('Ice Switch Room SE', player).entranceFlag:
        set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
    if is_trapped('Ice Switch Room ES'):
        set_rule(world.get_entrance('Ice Switch Room ES', player),
                 lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
    if is_trapped('Ice Switch Room NE'):
        set_rule(world.get_entrance('Ice Switch Room NE', player),
                 lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Ice Palace Boss', player))

    set_rule(world.get_entrance('Mire Lobby Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player))
    set_rule(world.get_entrance('Mire Post-Gap Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player))
    set_rule(world.get_entrance('Mire Falling Bridge Hook Path', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player))
    set_rule(world.get_entrance('Mire Falling Bridge Hook Only Path', player), lambda state: state.has('Hookshot', player))
    # Note: new enemy logic doesn't account for Fire Rod + Bombs or Ice Rod + Bombs yet
    # set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or
    #          (state.has('Fire Rod', player) and (state.can_use_bombs(player) or state.can_extend_magic(player, 9))) or  # 9 fr shots or 8 with some bombs
    #          (state.has('Ice Rod', player) and state.can_use_bombs(player)) or  # freeze popo and throw, bomb to finish
    #          state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player))  # need to defeat wizzrobes, bombs don't work ...
    #         byrna could work with sufficient magic
    set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage[player] and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
    loc = world.get_location('Misery Mire - Spikes Pot Key', player)
    if loc.pot:
        if loc.pot.x == 48 and loc.pot.y == 28:  # pot shuffled to spike area
            set_rule(loc, lambda state: (state.world.can_take_damage[player] and state.has_hearts(player, 4))
                     or state.has('Cane of Byrna', player) or state.has('Cape', player))
    set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
    if is_trapped('Mire Tile Room SW'):
        set_rule(world.get_entrance('Mire Tile Room SW', player), lambda state: state.has_fire_source(player))
    if is_trapped('Mire Tile Room ES'):
        set_rule(world.get_entrance('Mire Tile Room ES', player), lambda state: state.has_fire_source(player))
    set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
    set_rule(world.get_entrance('Mire Dark Shooters SW', player), lambda state: state.has('Cane of Somaria', player))
    # Not: somaria doesn't work here, so this cannot be opened if trapped
    # if is_trapped('Mire Dark Shooters SE'):
    #     set_rule(world.get_entrance('Mire Dark Shooters SE', player),
    #              lambda state: state.has('Cane of Somaria', player))

    set_defeat_dungeon_boss_rule(world.get_entrance('Misery Mire Boss', player))

    set_rule(world.get_entrance('TR Main Lobby Gap', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Lobby Ledge Gap', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub SW', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub SE', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub ES', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub EN', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub NW', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
    if is_trapped('TR Torches WN'):
        set_rule(world.get_entrance('TR Torches WN', player),
                 lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
    set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
    set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
    set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))  # due to needing the switch
    set_rule(world.get_entrance('TR Dark Ride Normal Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Dark Ride Backward Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Dark Ride Return Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Dark Ride Ledge Path', player), lambda state: state.has('Cane of Somaria', player))
    for location in world.get_region('TR Dark Ride Ledges', player).locations:
        set_rule(location, lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('TR Final Abyss Ledge Path', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.can_avoid_lasers(player))
    set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.can_avoid_lasers(player))
    set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.can_avoid_lasers(player))
    set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.can_avoid_lasers(player))
    set_defeat_dungeon_boss_rule(world.get_entrance('Turtle Rock Boss', player))

    set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player))
    if is_trapped('GT Hope Room WN'):
        set_rule(world.get_entrance('GT Hope Room WN', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('GT Conveyor Cross Hammer Path', player), lambda state: state.has('Hammer', player))
    set_rule(world.get_entrance('GT Conveyor Cross Hookshot Path', player), lambda state: state.has('Hookshot', player))
    if is_trapped('GT Conveyor Cross EN'):
        set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hammer', player))
    if not world.get_door('GT Speed Torch SE', player).entranceFlag:
        set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player))
    if is_trapped('GT Speed Torch NE'):
        set_rule(world.get_entrance('GT Speed Torch NE', player), lambda state: state.has('Fire Rod', player))
    if is_trapped('GT Speed Torch WS'):
        set_rule(world.get_entrance('GT Speed Torch WS', player), lambda state: state.has('Fire Rod', player))
    if is_trapped('GT Speed Torch WN'):
        set_rule(world.get_entrance('GT Speed Torch WN', player), lambda state: state.has('Fire Rod', player))
    set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
    set_rule(world.get_entrance('GT Hookshot North-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
    set_rule(world.get_entrance('GT Hookshot Mid-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
    set_rule(world.get_entrance('GT Hookshot Mid-East Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
    set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player))

    # I am tempted to stick an invincibility rule for getting across falling bridge
    set_rule(world.get_entrance('GT Ice Armos NE', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))
    set_rule(world.get_entrance('GT Ice Armos WS', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))

    # consider access to refill room - interior doors would need a change
    set_rule(world.get_entrance('GT Cannonball Bridge SE', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
    set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
    # Need cape to safely get past trinexx backwards in this room, makes magic usage tighter
    # Could not guarantee safety with byrna, not sure why
    if world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].name == 'Trinexx':
        add_rule(world.get_entrance('GT Quad Pot SW', player), lambda state: state.has('Cape', player))
    set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
    if is_trapped('GT Torch Cross WN'):
        set_rule(world.get_entrance('GT Torch Cross WN', player), lambda state: state.has_fire_source(player))
    set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
    # todo: the following only applies to crystal state propagation from this supertile
    # you can also reset the supertile, but I'm not sure how to model that
    set_rule(world.get_entrance('GT Falling Torches Down Ladder', player), lambda state: state.has_Boots(player))
    set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state))
    set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))

    # crystal switch rules
    if world.get_door('Thieves Attic ES', player).crystal == CrystalBarrier.Blue:
        set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
    else:
        set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player))
    set_rule(world.get_entrance('Thieves Attic Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player))
    set_rule(world.get_entrance('Thieves Attic Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))

    set_rule(world.get_entrance('Hera Lobby to Front Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Lobby', player), player))
    set_rule(world.get_entrance('Hera Front to Lobby Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Front', player), player))
    set_rule(world.get_entrance('Hera Front to Down Stairs Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Front', player), player))
    set_rule(world.get_entrance('Hera Down Stairs to Front Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Down Stairs Landing', player), player))
    set_rule(world.get_entrance('Hera Front to Up Stairs Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Hera Front', player), player))
    set_rule(world.get_entrance('Hera Up Stairs to Front Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Hera Up Stairs Landing', player), player))
    set_rule(world.get_entrance('Hera Front to Back Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Hera Front', player), player))
    set_rule(world.get_entrance('Hera Back to Front Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Hera Back', player), player))
    set_rule(world.get_location('Tower of Hera - Basement Cage', player), lambda state: state.can_reach_orange(world.get_region('Hera Basement Cage', player), player))
    set_rule(world.get_entrance('Hera Tridorm WN', player), lambda state: state.can_reach_blue(world.get_region('Hera Tridorm', player), player))
    set_rule(world.get_entrance('Hera Tridorm SE', player), lambda state: state.can_reach_orange(world.get_region('Hera Tridorm', player), player))
    set_rule(world.get_entrance('Hera Tile Room EN', player), lambda state: state.can_reach_blue(world.get_region('Hera Tile Room', player), player))

    set_rule(world.get_entrance('Hera Lobby to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Hera Front to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Hera Down Stairs Landing to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('Hera Down Stairs Landing', player), player))) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('Hera Up Stairs Landing to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('Hera Up Stairs Landing', player), player))) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('Hera Back to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player))  # or state.has_beam_sword(player) or (state.has('Hookshot', player) and state.has('Red Boomerang', player))
    set_rule(world.get_entrance('Hera Front to Back Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Cane of Somaria', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player)) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('Hera Basement Cage to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Hera Tridorm to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Hera Startile Wide to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Hera 5F Orange Path', player), lambda state: state.can_reach_orange(world.get_region('Hera 5F', player), player))

    set_rule(world.get_entrance('PoD Arena North to Landing Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Arena North', player), player))
    set_rule(world.get_entrance('PoD Arena Landing to North Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Arena Landing', player), player))
    set_rule(world.get_entrance('PoD Arena Main to Landing Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Main', player), player))
    set_rule(world.get_entrance('PoD Arena Landing to Main Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Landing', player), player))
    set_rule(world.get_entrance('PoD Arena Landing to Right Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Landing', player), player))
    set_rule(world.get_entrance('PoD Arena Right to Landing Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Right', player), player))
    set_rule(world.get_entrance('PoD Bow Statue Left to Right Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Bow Statue Left', player), player))
    set_rule(world.get_entrance('PoD Bow Statue Right to Left Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Bow Statue Right', player), player))
    set_rule(world.get_entrance('PoD Dark Pegs Right to Middle Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Dark Pegs Right', player), player))
    set_rule(world.get_entrance('PoD Dark Pegs Middle to Right Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Dark Pegs Middle', player), player))
    set_rule(world.get_entrance('PoD Dark Pegs Middle to Left Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('PoD Dark Pegs Middle', player), player))
    set_rule(world.get_entrance('PoD Dark Pegs Left to Middle Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('PoD Dark Pegs Left', player), player))

    set_rule(world.get_entrance('PoD Arena Main to Ranged Crystal', player), lambda state: True) # Can always throw pots here
    set_rule(world.get_entrance('PoD Arena Main to Landing Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('PoD Arena Main to Right Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('PoD Arena Bridge to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('PoD Arena Right to Ranged Crystal', player), lambda state: False) # (state.has('Cane of Somaria', player) and state.has_Boots(player))
    set_rule(world.get_entrance('PoD Arena Ledge to Ranged Crystal', player), lambda state: False) # state.has('Cane of Somaria', player) or state.has_beam_sword(player)
    set_rule(world.get_entrance('PoD Map Balcony to Ranged Crystal', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player)) # or state.has('Red Boomerang', player)
    set_rule(world.get_entrance('PoD Bow Statue Left to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('PoD Bow Statue Right to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('PoD Bow Statue Left to Right Bypass', player), lambda state: state.has('Cane of Somaria', player) or state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Ice Rod', player) or state.has('Fire Rod', player)) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('PoD Dark Pegs Landing to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.can_use_bombs(player) or state.has('Blue boomerang', player) or state.has('Red boomerang', player)
    set_rule(world.get_entrance('PoD Dark Pegs Middle to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.can_use_bombs(player) or state.has('Red Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('PoD Dark Pegs Middle', player), player))) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('PoD Dark Pegs Left to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # or state.has_beam_sword(player)
    set_rule(world.get_entrance('PoD Dark Pegs Right to Middle Bypass', player), lambda state: state.has('Blue Boomerang', player))
    set_rule(world.get_entrance('PoD Dark Pegs Middle to Left Bypass', player), lambda state: state.can_use_bombs(player))

    set_rule(world.get_entrance('Swamp Crystal Switch Outer to Inner Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
    set_rule(world.get_entrance('Swamp Crystal Switch Inner to Outer Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
    set_rule(world.get_entrance('Swamp Trench 2 Pots Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
    set_rule(world.get_entrance('Swamp Shortcut Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Shortcut', player), player))
    set_rule(world.get_entrance('Swamp Barrier Ledge - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier Ledge', player), player))
    set_rule(world.get_entrance('Swamp Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier', player), player))

    set_rule(world.get_entrance('Swamp Crystal Switch Inner to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Swamp Crystal Switch Outer to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_beam_sword(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('Swamp Crystal Switch Outer', player), player)))  # It is the length of the sword, not the beam itself that allows this
    set_rule(world.get_entrance('Swamp Crystal Switch Outer to Inner Bypass', player), lambda state: state.world.can_take_damage[player] or state.has('Cape', player) or state.has('Cane of Byrna', player))
    set_rule(world.get_entrance('Swamp Crystal Switch Inner to Outer Bypass', player), lambda state: state.world.can_take_damage[player] or state.has('Cape', player) or state.has('Cane of Byrna', player))

    set_rule(world.get_entrance('Thieves Hellway Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway', player), player))
    set_rule(world.get_entrance('Thieves Hellway Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway', player), player))
    set_rule(world.get_entrance('Thieves Hellway Crystal Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway N Crystal', player), player))
    set_rule(world.get_entrance('Thieves Hellway Crystal Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway S Crystal', player), player))
    set_rule(world.get_entrance('Thieves Triple Bypass SE', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
    set_rule(world.get_entrance('Thieves Triple Bypass WN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
    set_rule(world.get_entrance('Thieves Triple Bypass EN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))

    set_rule(world.get_entrance('Ice Crystal Right Blue Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Right', player), player))
    set_rule(world.get_entrance('Ice Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Right', player), player))
    set_rule(world.get_entrance('Ice Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Left', player), player))
    set_rule(world.get_entrance('Ice Crystal Left Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Left', player), player))
    set_rule(world.get_entrance('Ice Backwards Room Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Backwards Room', player), player))
    set_rule(world.get_entrance('Ice Bomb Jump Ledge Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Ledge', player), player))
    set_rule(world.get_entrance('Ice Bomb Jump Catwalk Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Catwalk', player), player))

    set_rule(world.get_entrance('Ice Bomb Drop Path', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Ice Conveyor to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Ice Refill to Crystal', player), lambda state: state.can_hit_crystal(player) or state.can_reach_blue(world.get_region('Ice Refill', player), player))

    set_rule(world.get_entrance('Mire Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Right', player), player))
    set_rule(world.get_entrance('Mire Crystal Mid Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Mid', player), player))
    set_rule(world.get_entrance('Mire Firesnake Skip Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Firesnake Skip', player), player))
    set_rule(world.get_entrance('Mire Antechamber Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Antechamber', player), player))
    set_rule(world.get_entrance('Mire Hub Upper Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
    set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
    set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
    set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
    set_rule(world.get_entrance('Mire Hub Switch Blue Barrier N', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Switch', player), player))
    set_rule(world.get_entrance('Mire Hub Switch Blue Barrier S', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Switch', player), player))
    set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
    set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
    set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
    set_rule(world.get_entrance('Mire Crystal Dead End Right Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
    set_rule(world.get_entrance('Mire South Fish Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire South Fish', player), player))
    set_rule(world.get_entrance('Mire Compass Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Compass Room', player), player))
    set_rule(world.get_entrance('Mire Crystal Mid Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Mid', player), player))
    set_rule(world.get_entrance('Mire Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Left', player), player))

    set_rule(world.get_entrance('Mire Conveyor to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('Mire Tall Dark and Roomy to Ranged Crystal', player), lambda state: True)  # Can always throw pots
    set_rule(world.get_entrance('Mire Fishbone Blue Barrier Bypass', player), lambda state: False)  # (state.world.can_take_damage[player] or state.has('Cape', player) or state.has('Cane of Byrna', player)) and state.can_tastate.can_use_bombs(player) // Easy to do but obscure. Should it be in logic?

    set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: state.can_reach('TR Chain Chomps Top', 'Region', player) and state.can_hit_crystal_through_barrier(player))
    set_rule(world.get_entrance('TR Chain Chomps Top to Bottom Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Chain Chomps Top', player), player))
    set_rule(world.get_entrance('TR Chain Chomps Bottom to Top Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Chain Chomps Bottom', player), player))
    set_rule(world.get_entrance('TR Pokey 2 Top to Bottom Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Pokey 2 Top', player), player))
    set_rule(world.get_entrance('TR Pokey 2 Bottom to Top Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Pokey 2 Bottom', player), player))
    set_rule(world.get_entrance('TR Crystaroller Bottom to Middle Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player))
    set_rule(world.get_entrance('TR Crystaroller Middle to Bottom Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))
    set_rule(world.get_entrance('TR Crystaroller Middle to Chest Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Crystaroller Middle', player), player))
    set_rule(world.get_entrance('TR Crystaroller Middle to Top Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))
    set_rule(world.get_entrance('TR Crystaroller Top to Middle Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('TR Crystaroller Top', player), player))
    set_rule(world.get_entrance('TR Crystaroller Chest to Middle Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Crystaroller Chest', player), player))
    set_rule(world.get_entrance('TR Crystal Maze Start to Interior Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze Start', player), player))
    set_rule(world.get_entrance('TR Crystal Maze Interior to End Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze Interior', player), player))
    set_rule(world.get_entrance('TR Crystal Maze Interior to Start Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze Interior', player), player))
    set_rule(world.get_entrance('TR Crystal Maze End to Interior Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze End', player), player))

    set_rule(world.get_entrance('TR Chain Chomps Top to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('TR Pokey 2 Top to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('TR Crystaroller Top to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('TR Crystal Maze Start to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('TR Chain Chomps Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Chain Chomps Bottom', player), player)))  # or state.has_beam_sword(player)
    set_rule(world.get_entrance('TR Pokey 2 Bottom to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_blue(world.get_region('TR Pokey 2 Bottom', player), player)))  # or state.has_beam_sword(player)
    set_rule(world.get_entrance('TR Crystaroller Bottom to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player)))  # or state.has_beam_sword(player)
    set_rule(world.get_entrance('TR Crystaroller Middle to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player)))  # or state.has_beam_sword(player)
    set_rule(world.get_entrance('TR Crystaroller Middle to Bottom Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Blue Boomerang', player))
    set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player))  # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes.
    set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player))  # Beam sword does NOT work
    set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True)  # Can always grab a pot from the interior and walk it to the start region and throw it there

    set_rule(world.get_entrance('GT Hookshot Platform Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Platform', player), player))
    set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player))
    set_rule(world.get_entrance('GT Double Switch Entry to Pot Corners Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
    set_rule(world.get_entrance('GT Double Switch Entry to Left Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
    set_rule(world.get_entrance('GT Double Switch Left to Entry Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Left', player), player))
    set_rule(world.get_entrance('GT Double Switch Pot Corners to Entry Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Pot Corners', player), player))
    set_rule(world.get_entrance('GT Double Switch Pot Corners to Exit Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Pot Corners', player), player))
    set_rule(world.get_entrance('GT Double Switch Exit to Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Exit', player), player))
    set_rule(world.get_entrance('GT Spike Crystal Left to Right Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Spike Crystal Left', player), player))
    set_rule(world.get_entrance('GT Spike Crystal Right to Left Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Spike Crystal Right', player), player))
    set_rule(world.get_entrance('GT Crystal Conveyor to Corner Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('GT Crystal Conveyor', player), player))
    set_rule(world.get_entrance('GT Crystal Conveyor Corner to Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('GT Crystal Conveyor Corner', player), player))
    set_rule(world.get_entrance('GT Crystal Conveyor Corner to Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Crystal Conveyor Corner', player), player))
    set_rule(world.get_entrance('GT Crystal Conveyor Left to Corner Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Crystal Conveyor Left', player), player))
    set_rule(world.get_entrance('GT Crystal Circles Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('GT Crystal Circles', player), player))

    set_rule(world.get_entrance('GT Hookshot Platform Barrier Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or state.has('Cane of Somaria', player))  # or state.has_Boots(player) /// There is a super precise trick where you can throw a pot and climp into the blue barrier, then sprint out of them.
    set_rule(world.get_entrance('GT Hookshot South Entry to Ranged Crystal', player), lambda state: state.can_use_bombs(player) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player))  # or state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('GT Double Switch Left to Crystal', player), lambda state: state.can_hit_crystal(player))
    set_rule(world.get_entrance('GT Double Switch Entry to Ranged Switches', player), lambda state: False) # state.has('Cane of Somaria', player)
    set_rule(world.get_entrance('GT Double Switch Left to Entry Bypass', player), lambda state: True) # Can always use pots
    set_rule(world.get_entrance('GT Double Switch Left to Pot Corners Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player) or state.has('Red Boomerang', player)) # or (state.has('Blue Boomerang', player) and state.has('Hookshot', player)) or (state.has('Ice Rod', player) and state.has('Hookshot', player)) or state.has('Hookshot', player) /// You can do this with just a pot and a hookshot
    set_rule(world.get_entrance('GT Double Switch Left to Exit Bypass', player), lambda state: False) # state.can_use_bombs(player) or (state.has('Cane of Somaria', player) and (state.has('Red Boomerang', player) or (state.has('Hookshot', player) and state.has('Blue Boomerang', player)) or (state.has('Hookshot', player) and state.has('Ice Rod', player))))
    set_rule(world.get_entrance('GT Double Switch Pot Corners to Ranged Switches', player), lambda state: False) # state.can_use_bombs(player) or state.has('Cane of Somaria', player) or (state.has('Cane of Somaria', player) and state.has_Boots(player)) /// There's two ways to interact with the switch. Somaria bounce at the top corner, or timed throws at the bottom corner.
    set_rule(world.get_entrance('GT Spike Crystal Left to Right Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player))  # or state.can_use_beam_sword(player)
    set_rule(world.get_entrance('GT Crystal Conveyor to Ranged Crystal', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('GT Crystal Conveyor Corner to Ranged Crystal', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('GT Crystal Conveyor Corner to Left Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Cane of Somaria', player))
    set_rule(world.get_entrance('GT Crystal Circles to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_blunt_weapon(player) or state.has('Cane of Byrna', player)) # or state.has_beam_sword(player)

    add_key_logic_rules(world, player)

    if world.logic[player] == 'hybridglitches' and not world.is_premature_copied_world:
        add_hmg_key_logic_rules(world, player)
    # End of door rando rules.

    if world.restrict_boss_items[player] != 'none':
        def add_mc_rule(l):
            boss_location = world.get_location(l, player)
            d_name = boss_location.parent_region.dungeon.name
            compass_name = f'Compass ({d_name})'
            map_name = f'Map ({d_name})'
            add_rule(boss_location, lambda state: state.has(compass_name, player) and state.has(map_name, player))

        for dungeon, info in dungeon_table.items():
            if info.prize:
                d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
                for loc in [f'{d_name} - Prize', f'{d_name} - Boss']:
                    add_mc_rule(loc)
        if world.doorShuffle[player] not in ['vanilla', 'basic']:
            add_mc_rule('Agahnim 1')
        add_mc_rule('Agahnim 2')

    set_rule(
        world.get_location('Ganon', player),
        lambda state: state.has_beam_sword(player)
            and state.has_fire_source(player)
            and (state.has('Tempered Sword', player) or state.has('Golden Sword', player)
                or state.can_hit_stunned_ganon(player)
                or state.has('Lamp', player)
                or state.can_extend_magic(player, 12)))  # need to light torch a sufficient amount of times

    set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player))  # need to damage ganon to get tiles to drop


def bomb_rules(world, player):
    # todo: kak well, pod hint (bonkable pots), hookshot pot, spike cave pots
    bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended.
    bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Paradox Shop', 'Mini Moldorm Cave',
                      'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front',
                      'Hookshot Cave Middle to Back', 'Hookshot Cave Back to Fairy',  'Hookshot Cave Fairy to Back',
                      'Good Bee Cave Front to Back', 'Good Bee Cave Back to Front', 'Capacity Upgrade East',
                      'Capacity Fairy Pool West', 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery',
                      'Paradox Cave Chest Area NE', 'Blinds Hideout N', 'Kakariko Well (top to back)',
                      'Light Hype Fairy']
    for entrance in bonkable_doors:
        add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
        add_bunny_rule(world.get_entrance(entrance, player), player)
    for entrance in bombable_doors:
        add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player))
        add_bunny_rule(world.get_entrance(entrance, player), player)

    bonkable_items = ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right']
    bombable_items = ['Chicken House', 'Aginah\'s Cave', 'Graveyard Cave',
                      'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom']
    for location in bonkable_items:
        add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
        add_bunny_rule(world.get_location(location, player), player)
    for location in bombable_items:
        add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player))
        add_bunny_rule(world.get_location(location, player), player)

    paradox_switch_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle']
    for location in paradox_switch_chests:
        add_rule(world.get_location(location, player), lambda state: state.can_hit_crystal_through_barrier(player))
        add_bunny_rule(world.get_location(location, player), player)

    add_rule(world.get_location('Attic Cracked Floor', player), lambda state: state.can_use_bombs(player))
    bombable_floors = ['PoD Pit Room Bomb Hole', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', 'GT Bob\'s Room Hole']
    for entrance in bombable_floors:
        add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player))

    if world.doorShuffle[player] == 'vanilla':
        add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player))  # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on.
        add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player))  # Is this the same as above?

        dungeon_bonkable = ['Sewers Rat Path WS', 'Sewers Rat Path WN',
                            'PoD Warp Hint SE', 'PoD Jelly Hall NW', 'PoD Jelly Hall NE', 'PoD Mimics 1 SW',
                            'Thieves Ambush E', 'Thieves Rail Ledge W',
                            'TR Dash Room NW', 'TR Crystaroller SW', 'TR Dash Room ES',
                            'GT Four Torches NW', 'GT Fairy Abyss SW'
                            ]
        dungeon_bombable = ['PoD Map Balcony WS', 'PoD Arena Ledge ES', 'PoD Dark Maze E', 'PoD Big Chest Balcony W',
                            'Swamp Pot Row WN', 'Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S',
                            'Skull Pot Circle WN', 'Skull Pull Switch EN', 'Skull Big Key EN', 'Skull Lone Pot WN',
                            'Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW',
                            'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole',
                            'Mire Crystal Mid NW', 'Mire Tall Dark and Roomy WN', 'Mire Shooter Rupees EN', 'Mire Crystal Top SW',
                            'GT Warp Maze (Rails) WS', 'GT Bob\'s Room Hole', 'GT Randomizer Room ES', 'GT Bomb Conveyor SW', 'GT Crystal Circles NW', 'GT Cannonball Bridge SE', 'GT Refill NE'
                            ]
        for entrance in dungeon_bonkable:
            add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
        for entrance in dungeon_bombable:
            add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player))
    else:
        doors_to_bomb_check = [x for x in world.doors if x.player == player and x.type in [DoorType.Normal, DoorType.Interior]]
        for door in doors_to_bomb_check:
            if door.kind(world) in [DoorKind.Dashable]:
                add_rule(door.entrance, lambda state: state.can_use_bombs(player) or state.has_Boots(player))
                if door.dependents:
                    for dep in door.dependents:
                        add_rule(dep.entrance, lambda state: state.can_use_bombs(player) or state.has_Boots(player))
            elif door.kind(world) in [DoorKind.Bombable]:
                add_rule(door.entrance, lambda state: state.can_use_bombs(player))
                if door.dependents:
                    for dep in door.dependents:
                        add_rule(dep.entrance, lambda state: state.can_use_bombs(player))


def challenge_room_rules(world, player):
    room_map = world.data_tables[player].uw_enemy_table.room_map
    stats = world.data_tables[player].enemy_stats
    for region, data in std_kill_rooms.items():
        entrances, trap_ables, room_id, enemy_list = data
        rule = get_challenge_rule(world, player, room_map, stats, room_id, enemy_list, region)
        for ent in entrances:
            entrance = world.get_entrance(ent, player)
            if not entrance.door or not entrance.door.entranceFlag:
                add_rule_new(entrance, rule)
        for ent in trap_ables:
            entrance = world.get_entrance(ent, player)
            if entrance.door.trapped and not entrance.door.entranceFlag:
                add_rule_new(entrance, rule)
    for region, data in kill_chests.items():
        locations, room_id, enemy_list = data
        rule = get_challenge_rule(world, player, room_map, stats, room_id, enemy_list, region)
        for loc in locations:
            add_rule_new(world.get_location(loc, player), rule)


def get_challenge_rule(world, player, room_map, stats, room_id, enemy_list, region):
    sprite_list = room_map[room_id]
    sprite_region_pairs = []
    for idx, sprite in enumerate(sprite_list):
        if idx in enemy_list:
            if not stats[sprite.kind].ignore_for_kill_room:
                sprite_region_pairs.append((sprite, world.get_region(sprite.region, player)))
    if region == 'Eastern Stalfos Spawn':
        stalfos_spawn_exception(sprite_region_pairs, stats, world, player)
    if sprite_region_pairs:
        return defeat_rule_multiple(world, player, sprite_region_pairs)
    return RuleFactory.static_rule(True)


def stalfos_spawn_exception(sprite_region_pairs, stats, world, player):
    if stats[EnemySprite.Stalfos].health * 4 > 40:
        for x in range(0, 4):
            sprite_region_pairs.append((Sprite(0x00a8, EnemySprite.Stalfos, 0, 0, 0, 0, 'Eastern Stalfos Spawn'),
                                       world.get_region('Eastern Stalfos Spawn', player)))
    return sprite_region_pairs


def pot_rules(world, player):
    if world.pottery[player] != 'none':
        blocks = [l for l in world.get_locations() if l.type == LocationType.Pot and l.pot.flags & PotFlags.Block]
        for block_pot in blocks:
            add_rule(block_pot, lambda state: state.can_lift_rocks(player))
        for l in world.get_region('Hookshot Fairy', player).locations:
            if l.type == LocationType.Pot:
                add_rule(l, lambda state: state.has('Hookshot', player))
        for l in world.get_region('Spike Cave', player).locations:
            if l.type == LocationType.Pot:
                add_rule(l, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and
                         ((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
                         (state.has('Cane of Byrna', player) and
                          (state.can_extend_magic(player, 12, True) or
                          (state.world.can_take_damage[player] and (state.has_Boots(player) or state.has_hearts(player, 4)))))))
        for l in world.get_region('Mire Hint', player).locations:
            if l.type == LocationType.Pot:
                add_rule(l, lambda state: state.can_use_bombs(player))
        for l in world.get_region('Palace of Darkness Hint', player).locations:
            if l.type == LocationType.Pot:
                add_rule(l, lambda state: state.can_use_bombs(player) or state.has_Boots(player))
        for number in ['1', '2']:
            loc = world.get_location_unsafe(f'Dark Lake Hylia Ledge Spike Cave Pot #{number}', player)
            if loc and loc.type == LocationType.Pot:
                add_rule(loc, lambda state: state.world.can_take_damage[player] or state.has('Hookshot', player)
                         or state.has('Cape', player)
                         or (state.has('Cane of Byrna', player)
                             and state.world.difficulty_adjustments[player] == 'normal'))
        for l in world.get_region('Ice Hammer Block', player).locations:
            if l.type == LocationType.Pot:
                add_rule(l, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player))
        for pot in ['Ice Antechamber Pot #3', 'Ice Antechamber Pot #4']:
            loc = world.get_location_unsafe(pot, player)
            if loc:
                set_rule(loc, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player))
        loc = world.get_location_unsafe('Mire Spikes Pot #3', player)
        if loc:
            set_rule(loc, lambda state: (state.world.can_take_damage[player] and state.has_hearts(player, 4))
                     or state.has('Cane of Byrna', player) or state.has('Cape', player))
        for l in world.get_region('Ice Refill', player).locations:
            if l.type == LocationType.Pot:
                # or can_reach_blue is redundant as you have to hit a crystal switch somewhere...
                add_rule(l, lambda state: state.can_hit_crystal(player))


def drop_rules(world, player):
    data_tables = world.data_tables[player]
    for super_tile, enemy_list in data_tables.uw_enemy_table.room_map.items():
        for enemy in enemy_list:
            if enemy.location:
                true_location = world.get_location(enemy.location.name, player)
                rule = defeat_rule_single(world, player, enemy, true_location.parent_region)
                if true_location.parent_region.name in special_rules_check:
                    rule = special_rules_for_region(world, player, true_location.parent_region.name,
                                                    true_location, rule)
                if rule.rule_lambda is None:
                    raise Exception(f'Bad rule for enemy drop. Need to inspect this case: {hex(enemy.kind)}')
                add_rule_new(true_location, rule)


def ow_inverted_rules(world, player):
    if world.is_atgt_swapped(player):
        set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
    else:
        set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player))  # barrier gets removed after killing agahnim, rule for that added later
        set_rule(world.get_entrance('GT Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
        set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'))

    if world.is_tile_swapped(0x03, player):
        set_rule(world.get_entrance('Spectacle Rock Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'] and state.has_Pearl(player))
        set_rule(world.get_entrance('Spectacle Rock Leave', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'] and state.has_Pearl(player))
        
    if not world.is_tile_swapped(0x05, player):
        set_rule(world.get_entrance('East Death Mountain Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
    else:
        set_rule(world.get_entrance('East Dark Death Mountain Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
        
    if not world.is_tile_swapped(0x07, player):
        set_rule(world.get_entrance('TR Pegs Teleporter', player), lambda state: state.has('Hammer', player))
        set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.can_lift_heavy_rocks(player))
    else:
        set_rule(world.get_entrance('Turtle Rock Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
        set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.has('Hammer', player) and state.can_lift_heavy_rocks(player))
        set_rule(world.get_entrance('Turtle Rock Tail Ledge Drop', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        
    if not world.is_tile_swapped(0x10, player):
        set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: state.can_lift_rocks(player))
    else:
        set_rule(world.get_entrance('West Dark World Teleporter', player), lambda state: state.can_lift_rocks(player))
        
    if not world.is_tile_swapped(0x1b, player):
        set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False)
        set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False)
        set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player))
        
        set_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has_Mirror(player))
        set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player))
        set_rule(world.get_entrance('Castle Gate Teleporter', player), lambda state: state.has_beaten_aga(player))
        set_rule(world.get_entrance('Castle Gate Teleporter (Inner)', player), lambda state: state.has_beaten_aga(player))
    else:
        set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: world.is_pyramid_open(player) or state.has('Beat Agahnim 2', player))
        set_rule(world.get_entrance('Pyramid Hole', player), lambda state: False)
        set_rule(world.get_entrance('Pyramid Entrance', player), lambda state: False)
        
        set_rule(world.get_entrance('Post Aga Teleporter', player), lambda state: state.has_beaten_aga(player))
        
    if not world.is_tile_swapped(0x2f, player):
        set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer
    else:
        set_rule(world.get_entrance('East Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player))
        
    if not world.is_tile_swapped(0x30, player):
        set_rule(world.get_entrance('Mirror To Bombos Tablet Ledge', player), lambda state: state.has_Mirror(player))
        set_rule(world.get_entrance('Desert Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
    else:
        set_rule(world.get_entrance('Mire Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
        set_rule(world.get_entrance('Checkerboard Ledge Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        set_rule(world.get_entrance('Checkerboard Ledge Leave', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        
    if world.is_tile_swapped(0x32, player):
        set_rule(world.get_entrance('Cave 45 Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        set_rule(world.get_entrance('Cave 45 Leave', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        
    if not world.is_tile_swapped(0x33, player):
        set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.can_lift_rocks(player))
    else:
        set_rule(world.get_entrance('South Dark World Teleporter', player), lambda state: state.can_lift_rocks(player))
        
    if not world.is_tile_swapped(0x35, player):
        set_rule(world.get_entrance('Lake Hylia Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
    else:
        set_rule(world.get_entrance('Ice Lake Teleporter', player), lambda state: state.can_lift_heavy_rocks(player))
        set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        
    if world.is_tile_swapped(0x3a, player):
        set_rule(world.get_entrance('Desert Pass Ladder (South)', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])
        set_rule(world.get_entrance('Desert Pass Ladder (North)', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'])


def ow_bunny_rules(world, player):
    # locations
    add_bunny_rule(world.get_location('Mushroom', player), player)
    add_bunny_rule(world.get_location('Turtle Medallion Pad', player), player)
    add_bunny_rule(world.get_location('Zora\'s Ledge', player), player)
    add_bunny_rule(world.get_location('Maze Race', player), player)
    add_bunny_rule(world.get_location('Flute Spot', player), player)
    add_bunny_rule(world.get_location('Catfish', player), player)
    add_bunny_rule(world.get_location('Kiki', player), player)
    add_bunny_rule(world.get_location('Locksmith', player), player)

    # entrances
    add_bunny_rule(world.get_entrance('Lost Woods Hideout Drop', player), player)
    add_bunny_rule(world.get_entrance('Lumberjack Tree Tree', player), player)
    add_bunny_rule(world.get_entrance('Waterfall of Wishing', player), player)
    add_bunny_rule(world.get_entrance('Bonk Rock Cave', player), player)
    add_bunny_rule(world.get_entrance('Sanctuary Grave', player), player)
    add_bunny_rule(world.get_entrance('Kings Grave', player), player)
    add_bunny_rule(world.get_entrance('North Fairy Cave Drop', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle Secret Entrance Drop', player), player)
    add_bunny_rule(world.get_entrance('Bonk Fairy (Light)', player), player)
    add_bunny_rule(world.get_entrance('Checkerboard Cave', player), player)
    add_bunny_rule(world.get_entrance('20 Rupee Cave', player), player)
    add_bunny_rule(world.get_entrance('50 Rupee Cave', player), player)

    add_bunny_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), player)  # bunny cannot lift bush
    add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player)  # bunny cannot lift bush
    add_bunny_rule(world.get_entrance('Skull Woods Final Section', player), player)  # bunny cannot use fire rod
    add_bunny_rule(world.get_entrance('Hookshot Cave', player), player)
    add_bunny_rule(world.get_entrance('Thieves Town', player), player)  # bunny cannot pull
    add_bunny_rule(world.get_entrance('Hammer Peg Cave', player), player)
    add_bunny_rule(world.get_entrance('Bonk Fairy (Dark)', player), player)
    add_bunny_rule(world.get_entrance('Misery Mire', player), player)
    add_bunny_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), player)

    # terrain
    add_bunny_rule(world.get_entrance('Lost Woods Bush (West)', player), player)
    add_bunny_rule(world.get_entrance('Lost Woods Bush (East)', player), player)
    add_bunny_rule(world.get_entrance('DM Hammer Bridge (West)', player), player)
    add_bunny_rule(world.get_entrance('DM Hammer Bridge (East)', player), player)
    add_bunny_rule(world.get_entrance('DM Broken Bridge (West)', player), player)
    add_bunny_rule(world.get_entrance('DM Broken Bridge (East)', player), player)
    add_bunny_rule(world.get_entrance('Fairy Ascension Rocks (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Fairy Ascension Rocks (Outer)', player), player)
    add_bunny_rule(world.get_entrance('TR Pegs Ledge Entry', player), player)
    add_bunny_rule(world.get_entrance('TR Pegs Ledge Leave', player), player)
    add_bunny_rule(world.get_entrance('Mountain Pass Rock (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Mountain Pass Rock (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Zora Waterfall Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Zora Waterfall Water Entry', player), player)
    add_bunny_rule(world.get_entrance('Zora Waterfall Approach', player), player)
    add_bunny_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), player)
    add_bunny_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), player)
    add_bunny_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), player)
    add_bunny_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), player)
    add_bunny_rule(world.get_entrance('Kakariko Pond Whirlpool', player), player)
    add_bunny_rule(world.get_entrance('Kings Grave Rocks (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Kings Grave Rocks (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Graveyard Ladder (Top)', player), player)
    add_bunny_rule(world.get_entrance('Graveyard Ladder (Bottom)', player), player)
    add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player)
    add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Potion Shop Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Potion Shop Rock (North)', player), player)
    add_bunny_rule(world.get_entrance('Potion Shop Rock (South)', player), player)
    add_bunny_rule(world.get_entrance('Zora Approach Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Zora Approach Rocks (West)', player), player)
    add_bunny_rule(world.get_entrance('Zora Approach Rocks (East)', player), player)
    add_bunny_rule(world.get_entrance('Kakariko Southwest Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Kakariko Southwest Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Kakariko Yard Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Kakariko Yard Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle Southwest Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle Southwest Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle Courtyard Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle Courtyard Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle East Rock (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Hyrule Castle East Rock (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Wooden Bridge Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Wooden Bridge Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Wooden Bridge Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Wooden Bridge Northeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Blacksmith Ledge Peg (West)', player), player)
    add_bunny_rule(world.get_entrance('Blacksmith Ledge Peg (East)', player), player)
    add_bunny_rule(world.get_entrance('Maze Race Game', player), player)
    add_bunny_rule(world.get_entrance('Desert Ledge Rocks (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Desert Ledge Rocks (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Flute Boy Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Flute Boy Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('C Whirlpool Water Entry', player), player)
    add_bunny_rule(world.get_entrance('C Whirlpool Rock (Bottom)', player), player)
    add_bunny_rule(world.get_entrance('C Whirlpool Rock (Top)', player), player)
    add_bunny_rule(world.get_entrance('C Whirlpool Pegs (Outer)', player), player)
    add_bunny_rule(world.get_entrance('C Whirlpool Pegs (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Statues Water Entry', player), player)
    add_bunny_rule(world.get_entrance('Lake Hylia Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Lake Hylia South Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Lake Hylia Northeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Lake Hylia Central Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Lake Hylia Island Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Lake Hylia Water D Leave', player), player)
    add_bunny_rule(world.get_entrance('Ice Cave Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Desert Pass Rocks (North)', player), player)
    add_bunny_rule(world.get_entrance('Desert Pass Rocks (South)', player), player)
    add_bunny_rule(world.get_entrance('Octoballoon Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Octoballoon Waterfall Water Drop', player), player)

    add_bunny_rule(world.get_entrance('Skull Woods Rock (West)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Rock (East)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Forgotten Bush (West)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Forgotten Bush (East)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player)
    add_bunny_rule(world.get_entrance('East Dark Death Mountain Bushes', player), player)
    add_bunny_rule(world.get_entrance('Bumper Cave Ledge Drop', player), player)
    add_bunny_rule(world.get_entrance('Bumper Cave Rock (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Bumper Cave Rock (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (West)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (East)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Pass Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Pass Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), player)
    add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), player)
    add_bunny_rule(world.get_entrance('Dark Graveyard Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Dark Graveyard Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Qirn Jump Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Qirn Jump East Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Dark Witch Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Dark Witch Rock (North)', player), player)
    add_bunny_rule(world.get_entrance('Dark Witch Rock (South)', player), player)
    add_bunny_rule(world.get_entrance('Catfish Approach Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Catfish Approach Rocks (West)', player), player)
    add_bunny_rule(world.get_entrance('Catfish Approach Rocks (East)', player), player)
    add_bunny_rule(world.get_entrance('Bush Yard Pegs (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Bush Yard Pegs (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Broken Bridge Hammer Rock (South)', player), player)
    add_bunny_rule(world.get_entrance('Broken Bridge Hammer Rock (North)', player), player)
    add_bunny_rule(world.get_entrance('Broken Bridge Hookshot Gap', player), player)
    add_bunny_rule(world.get_entrance('Broken Bridge Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Broken Bridge Northeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Broken Bridge West Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Peg Area Rocks (West)', player), player)
    add_bunny_rule(world.get_entrance('Peg Area Rocks (East)', player), player)
    add_bunny_rule(world.get_entrance('Dig Game To Ledge Drop', player), player)
    add_bunny_rule(world.get_entrance('Frog Rock (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Frog Rock (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Archery Game Rock (North)', player), player)
    add_bunny_rule(world.get_entrance('Archery Game Rock (South)', player), player)
    add_bunny_rule(world.get_entrance('Hammer Bridge Pegs (North)', player), player)
    add_bunny_rule(world.get_entrance('Hammer Bridge Pegs (South)', player), player)
    add_bunny_rule(world.get_entrance('Hammer Bridge Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Stumpy Approach Bush (North)', player), player)
    add_bunny_rule(world.get_entrance('Stumpy Approach Bush (South)', player), player)
    add_bunny_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), player)
    add_bunny_rule(world.get_entrance('Dark C Whirlpool Rock (Bottom)', player), player)
    add_bunny_rule(world.get_entrance('Dark C Whirlpool Rock (Top)', player), player)
    add_bunny_rule(world.get_entrance('Dark C Whirlpool Pegs (Outer)', player), player)
    add_bunny_rule(world.get_entrance('Dark C Whirlpool Pegs (Inner)', player), player)
    add_bunny_rule(world.get_entrance('Hype Cave Water Entry', player), player)
    add_bunny_rule(world.get_entrance('Ice Lake Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Ice Lake Northeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Ice Lake Southwest Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Ice Lake Iceberg Water Entry', player), player)
    add_bunny_rule(world.get_entrance('Ice Lake Iceberg Bomb Jump', player), player)
    add_bunny_rule(world.get_entrance('Shopping Mall Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Bomber Corner Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), player)

    # OWG rules
    add_bunny_rule(world.get_entrance('Stone Bridge EC Cliff Water Drop', player), player)
    add_bunny_rule(world.get_entrance('Hammer Bridge EC Cliff Water Drop', player), player)

    if not world.is_atgt_swapped(player):
        add_bunny_rule(world.get_entrance('Agahnims Tower', player), player)

    #TODO: This needs to get applied after bunny rules, move somewhere else tho
    if not world.is_atgt_swapped(player):
        add_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_beaten_aga(player), 'or')  # barrier gets removed after killing agahnim, relevant for entrance shuffle


def ow_terrain_rules(world, player):
    for edge in world.owedges:
        if edge.player == player and edge.dest and edge.dest.terrain == Terrain.Water:
            ent = world.get_entrance(edge.name, player)
            if edge.terrain == Terrain.Land:
                set_rule(ent, lambda state: state.has('Flippers', player))
            if ent.connected_region.is_dark_world == (world.mode[player] != 'inverted'):
                add_rule(ent, lambda state: state.has_Pearl(player))

    for whirlpool_name in OWExitTypes['Whirlpool']:
        ent = world.get_entrance(whirlpool_name, player)
        if ent.parent_region.is_light_world == (world.mode[player] != 'inverted') and ent.connected_region.is_dark_world == (world.mode[player] != 'inverted'):
            add_rule(ent, lambda state: state.has_Pearl(player))


def no_glitches_rules(world, player):
    set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Zora Approach Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('C Whirlpool Water Entry', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Statues Water Entry', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Lake Hylia South Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Octoballoon Water Drop', player), lambda state: state.has('Flippers', player))

    set_rule(world.get_entrance('Qirn Jump East Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Dark Witch Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Catfish Approach Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: state.has('Flippers', player))
    set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: state.has('Flippers', player))

    # todo: move some dungeon rules to no glicthes logic - see these for examples
    # add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
    # add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player))
    # DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right']
    # for location in DMs_room_chests:
    #     add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False)  # no glitches does not require block override
    set_rule(world.get_entrance('Ice Lake Northeast Pier Hop', player), lambda state: False)
    forbid_bomb_jump_requirements(world, player)
    if not world.is_premature_copied_world:
        add_conditional_lamps(world, player)


def fake_flipper_rules(world, player):
    set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Potion Shop Northeast Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Zora Approach Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('C Whirlpool Water Entry', player), lambda state: True)
    set_rule(world.get_entrance('Statues Water Entry', player), lambda state: True)
    set_rule(world.get_entrance('Lake Hylia South Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Octoballoon Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Qirn Jump Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Qirn Jump East Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Dark Witch Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Dark Witch Northeast Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Catfish Approach Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Dark C Whirlpool Water Entry', player), lambda state: True)
    set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: True)
    set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: True)
    set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: True)


def forbid_bomb_jump_requirements(world, player):
    DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right']
    for location in DMs_room_chests:
        add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player))
    set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False)
    set_rule(world.get_entrance('Ice Lake Iceberg Bomb Jump', player), lambda state: False)


def add_conditional_lamps(world, player):
    def add_conditional_lamp(spot, spottype='Location'):
        if spottype == 'Location':
            spot = world.get_location(spot, player)
        else:
            spot = world.get_entrance(spot, player)
        add_lamp_requirement(spot, player)

    dark_rooms = {
        'TR Dark Ride North Platform': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride Normal Path', 'TR Dark Ride Ledge Path'], 'locations': []},
        'TR Dark Ride South Platform': {'sewer': False, 'entrances': ['TR Dark Ride SW', 'TR Dark Ride Backward Path'], 'locations': []},
        'TR Dark Ride Ledges': {'sewer': False, 'entrances': ['TR Dark Ride Return Path'], 'locations': []},
        'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
        'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
        'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
        'Mire Tall Dark and Roomy': {'sewer': False, 'entrances': ['Mire Tall Dark and Roomy ES', 'Mire Tall Dark and Roomy WS', 'Mire Tall Dark and Roomy WN', 'Mire Tall Dark and Roomy to Ranged Crystal'], 'locations': []},
        'Mire Crystal Right': {'sewer': False, 'entrances': ['Mire Crystal Right ES'], 'locations': []},
        'Mire Crystal Mid': {'sewer': False, 'entrances': ['Mire Crystal Mid NW'], 'locations': []},
        'Mire Crystal Left': {'sewer': False, 'entrances': ['Mire Crystal Left WS'], 'locations': []},
        'Mire Crystal Top': {'sewer': False, 'entrances': ['Mire Crystal Top SW'], 'locations': []},
        'Mire Shooter Rupees': {'sewer': False, 'entrances': ['Mire Shooter Rupees EN'], 'locations': []},
        'PoD Dark Alley': {'sewer': False, 'entrances': ['PoD Dark Alley NE'], 'locations': []},
        'PoD Callback': {'sewer': False, 'entrances': ['PoD Callback WS', 'PoD Callback Warp'], 'locations': []},
        'PoD Turtle Party': {'sewer': False, 'entrances': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], 'locations': []},
        'PoD Lonely Turtle': {'sewer': False, 'entrances': ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN'], 'locations': []},
        'PoD Dark Pegs Landing': {'sewer': False, 'entrances': ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs Landing to Right', 'PoD Dark Pegs Landing to Ranged Crystal'], 'locations': []},
        'PoD Dark Pegs Left': {'sewer': False, 'entrances': ['PoD Dark Pegs WN', 'PoD Dark Pegs Left to Middle Barrier - Blue', 'PoD Dark Pegs Left to Ranged Crystal'], 'locations': []},
        'PoD Dark Basement': {'sewer': False, 'entrances': ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs'], 'locations': ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right']},
        'PoD Dark Maze': {'sewer': False, 'entrances': ['PoD Dark Maze EN', 'PoD Dark Maze E'], 'locations': ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom']},
        'Eastern Dark Square': {'sewer': False, 'entrances': ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN', 'Eastern Dark Square EN'], 'locations': []},
        'Eastern Dark Pots': {'sewer': False, 'entrances': ['Eastern Dark Pots WN'], 'locations': ['Eastern Palace - Dark Square Pot Key']},
        'Eastern Darkness': {'sewer': False, 'entrances': ['Eastern Darkness S', 'Eastern Darkness Up Stairs', 'Eastern Darkness NE'], 'locations': ['Eastern Palace - Dark Eyegore Key Drop']},
        'Eastern Rupees': {'sewer': False, 'entrances': ['Eastern Rupees SE'], 'locations': []},
        'Tower Lone Statue': {'sewer': False, 'entrances': ['Tower Lone Statue Down Stairs', 'Tower Lone Statue WN'], 'locations': []},
        'Tower Dark Maze': {'sewer': False, 'entrances': ['Tower Dark Maze EN', 'Tower Dark Maze ES'], 'locations': ['Castle Tower - Dark Maze']},
        'Tower Dark Chargers': {'sewer': False, 'entrances': ['Tower Dark Chargers WS', 'Tower Dark Chargers Up Stairs'], 'locations': []},
        'Tower Dual Statues': {'sewer': False, 'entrances': ['Tower Dual Statues Down Stairs', 'Tower Dual Statues WS'], 'locations': []},
        'Tower Dark Pits': {'sewer': False, 'entrances': ['Tower Dark Pits ES', 'Tower Dark Pits EN'], 'locations': []},
        'Tower Dark Archers': {'sewer': False, 'entrances': ['Tower Dark Archers WN', 'Tower Dark Archers Up Stairs'], 'locations': ['Castle Tower - Dark Archer Key Drop']},
        'Sewers Dark Cross': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door N', 'Sewers Dark Cross South Stairs'], 'locations': ['Sewers - Dark Cross']},
        'Sewers Behind Tapestry': {'sewer': True, 'entrances': ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs'], 'locations': []},
        'Sewers Rope Room': {'sewer': True, 'entrances': ['Sewers Rope Room Up Stairs', 'Sewers Rope Room North Stairs'], 'locations': []},
        'Sewers Water': {'sewer': True, 'entrances': ['Sewers Water S', 'Sewers Water W'], 'locations': []},
        'Sewers Dark Aquabats': {'sewer': True, 'entrances': ['Sewers Dark Aquabats N', 'Sewers Dark Aquabats ES'], 'locations': []},
        'Sewers Key Rat': {'sewer': True, 'entrances': ['Sewers Key Rat S', 'Sewers Key Rat NE'], 'locations': ['Hyrule Castle - Key Rat Key Drop']},
        'Old Man Cave (East)': {'sewer': False, 'entrances': ['Old Man Cave Exit (East)', 'Old Man Cave W']},
        'Old Man Cave (West)': {'sewer': False, 'entrances': ['Old Man Cave E']},
        'Old Man House Back': {'sewer': False, 'entrances': ['Old Man House Back to Front', 'Old Man House Exit (Top)']},
        'Death Mountain Return Cave (left)': {'sewer': False, 'entrances': ['Death Mountain Return Cave E', 'Death Mountain Return Cave Exit (West)']},
        'Death Mountain Return Cave (right)': {'sewer': False, 'entrances': ['Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave W']},
    }

    dark_debug_set = set()
    for region, info in dark_rooms.items():
        is_dark = False
        if not world.sewer_light_cone[player]:
            is_dark = True
        elif world.doorShuffle[player] not in ['crossed', 'partitioned'] and not info['sewer']:
            is_dark = True
        elif world.doorShuffle[player] in ['crossed', 'partitioned']:
            sewer_builder = world.dungeon_layouts[player]['Hyrule Castle']
            is_dark = region not in sewer_builder.master_sector.region_set()
        if is_dark:
            dark_debug_set.add(region)
            for ent in info['entrances']:
                add_conditional_lamp(ent, 'Entrance')
            r = world.get_region(region, player)
            for loc in r.locations:
                add_conditional_lamp(loc, 'Location')
    logging.getLogger('').debug('Non Dark Regions: ' + ', '.join(set(dark_rooms.keys()).difference(dark_debug_set)))

    add_conditional_lamp('Old Man House Front to Back', 'Entrance')


def misc_key_rules(world, player):
    # softlock protection as you can reach the sewers small key door with a guard drop key
    set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player))
    set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player))


def swordless_rules(world, player):
    set_rule(world.get_entrance('Tower Altar NW', player), lambda state: True)
    set_rule(world.get_entrance('Skull Vines NW', player), lambda state: True)
    set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
    if world.get_entrance('Ice Lobby SE', player).door.trapped:
        set_rule(world.get_entrance('Ice Lobby SE', player),
                 lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
    set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))

    set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
    set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
    set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player))
    set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player))  # need to damage ganon to get tiles to drop

    set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player))   # sword not required to use medallion for opening in swordless (!)
    set_rule(world.get_location('Turtle Medallion Pad', player), lambda state: state.has_turtle_rock_medallion(player))   # sword not required to use medallion for opening in swordless (!)
    
    if not world.is_atgt_swapped(player):
        set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player))  # barrier gets removed after killing agahnim, rule for that added later

std_kill_rooms = {
    'Hyrule Dungeon Armory Main':  # One green guard
        (['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], ['Hyrule Dungeon Armory Interior Key Door N'],
         0x71, [0]),
    'Hyrule Dungeon Armory Boomerang':  # One blue guard
        (['Hyrule Dungeon Armory Boomerang WS'], [], 0x71, [1]),
    'Eastern Stalfos Spawn':  # Can use pots up to a point see stalfos_spawn_exception
        (['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], [], 0xa8, []),
    'Eastern Single Eyegore':
        (['Eastern Single Eyegore NE'], ['Eastern Single Eyegore ES'], 0xd8, [8, 9, 10]),
    'Eastern Duo Eyegores':
        (['Eastern Duo Eyegores NE'], ['Eastern Duo Eyegores SE'], 0xd8, [0, 1, 2, 3, 4, 5, 6, 7]),
    'Desert Compass Room':  # Three popos (beamos)
        (['Desert Compass NE'], ['Desert Compass Key Door WN'], 0x085, [2, 3, 4, 5]),
    'Desert Four Statues':  # Four popos (beamos)
        (['Desert Four Statues NW', 'Desert Four Statues ES'], [], 0x53, [5, 6, 8, 9, 10]),
    'Hera Beetles':  # Three blue beetles and only two pots, and bombs don't work.
        (['Hera Beetles WS'], [], 0x31, [7, 8, 10]),
    'Tower Gold Knights':  # Two ball and chain
        (['Tower Gold Knights SW', 'Tower Gold Knights EN'], [], 0xe0, [0, 1]),
    'Tower Dark Archers':  # Backwards kill room
        (['Tower Dark Archers WN'], [], 0xc0, [0, 1, 3]),
    'Tower Red Spears':  # Two spear soldiers
        (['Tower Red Spears WN'], [], 0xb0, [1, 2, 3, 4]),
    'Tower Red Guards':  # Two usain bolts
        (['Tower Red Guards EN', 'Tower Red Guards SW'], [], 0xb0, [0, 5]),
    'Tower Circle of Pots':  # Two spear soldiers. Plenty of pots.
        (['Tower Circle of Pots NW'], ['Tower Circle of Pots ES'], 0xb0, [7, 8, 9, 10]),
    'PoD Mimics 1':
        (['PoD Mimics 1 NW'], ['PoD Mimics 1 SW'], 0x4b, [0, 3, 4]),
    'PoD Mimics 2':
        (['PoD Mimics 2 NW'], ['PoD Mimics 2 SW'], 0x1b, [3, 4, 5]),
    'PoD Turtle Party':  # Lots of turtles.
        (['PoD Turtle Party ES', 'PoD Turtle Party NW'], [], 0x0b, [4, 5, 6, 7, 8, 9]),
    'Thieves Basement Block':  # One blue and one red zazak and one Stalfos. Two pots. Need weapon.
        (['Thieves Basement Block WN'], ['Thieves Blocked Entry SW'], 0x45, [1, 2, 3]),
    'Ice Jelly Key':
        (['Ice Jelly Key ES'], [], 0x0e, [1, 2, 3]),
    'Ice Stalfos Hint':  # Need bombs for big stalfos knights
        (['Ice Stalfos Hint SE'], [], 0x3e, [1, 2]),
    'Ice Pengator Trap':  # Five pengators. Bomb-doable?
        (['Ice Pengator Trap NE'], [], 0x6e, [0, 1, 2, 3, 4]),
    'Mire 2':  # Wizzrobes. Bombs dont work.
        (['Mire 2 NE'], [], 0xd2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
    'Mire Cross':  # 4 Sluggulas. Bombs don't work
        (['Mire Cross ES'], ['Mire Cross SW'], 0xb2, [5, 6, 7, 10, 11]),
    'TR Twin Pokeys':  # Two pokeys
        (['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], ['TR Twin Pokeys NW'], 0x24, [3, 4, 5, 6]),
    'TR Tongue Pull':  # Kill zols for money
        (['TR Tongue Pull NE'], ['TR Tongue Pull WS'], 0x04, [9, 13, 14]),
    'GT Petting Zoo':  # Don't make anyone do this room with bombs.
        (['GT Petting Zoo SE'], [], 0x7d, [4, 5, 6, 7, 8, 10]),
    'GT DMs Room':  # Four red stalfos
        (['GT DMs Room SW'], [], 0x7b, [2, 3, 4, 5, 8, 9, 10]),
    'GT Mimics 1':  # two red mimics
        (['GT Mimics 1 NW', 'GT Mimics 1 ES'], [], 0x006b, [5, 6, 7, 8, 9]),
    'GT Mimics 2':  # two red mimics
        (['GT Mimics 2 NE', 'GT Mimics 2 WS'], [], 0x006b, [10, 11, 12, 13]),
    'GT Gauntlet 1':  # Stalfos/zazaks
        (['GT Gauntlet 1 WN'], [], 0x5d, [3, 4, 5, 6]),
    'GT Gauntlet 2':  # Red stalfos
        (['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], [], 0x5d, [0, 1, 2, 7]),
    'GT Gauntlet 3':  # Blue zazaks
        (['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], [], 0x5d, [8, 9, 10, 11, 12]),
    'GT Gauntlet 4':  # Red zazaks
        (['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], [], 0x6d, [0, 1, 2, 3]),
    'GT Gauntlet 5':  # Stalfos and zazak
        (['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], [], 0x6d, [4, 5, 6, 7, 8]),
    'GT Wizzrobes 1':  # Wizzrobes. Bombs don't work
        (['GT Wizzrobes 1 SW'], [], 0xa5, [2, 3, 7]),
    'GT Wizzrobes 2':  # Wizzrobes. Bombs don't work
        (['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'], [], 0xa5, [0, 1, 4, 5, 6]),
    'Spiral Cave (Top)':  # for traversal in enemizer at low health
        (['Spiral Cave (top to bottom)'], [], 0xEE, [0, 1, 2, 3, 4]),
}  # all trap rooms? (Desert Trap Room, Thieves Trap Room currently subtile only)

kill_chests = {
    'Tower Room 03': (['Castle Tower - Room 03'], 0xe0, [2, 3]),
    'Ice Compass Room': (['Ice Palace - Compass Chest'], 0x2e, [0, 1, 2, 3, 4, 5]),
    'Swamp Entrance': (['Swamp Palace - Entrance'], 0x28, [0, 1, 2, 3, 4]),
    'GT Tile Room': (['Ganons Tower - Tile Room'], 0x8d, [1, 2, 3, 4]),
    'Mini Moldorm Cave':
        (['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Left',
          'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy'], 0x123, [0, 1, 2, 3]),
    'Mimic Cave':
        (['Mimic Cave'], 0x10c, [4, 5, 6, 7]),
}





def add_connection(parent_name, target_name, entrance_name, world, player):
    parent = world.get_region(parent_name, player)
    target = world.get_region(target_name, player)
    connection = Entrance(player, entrance_name, parent)
    parent.exits.append(connection)
    connection.connect(target)


def standard_rules(world, player):
    add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
    world.get_entrance('Uncle S&Q', player).hide_path = True
    set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.has('Zelda Delivered', player))
    set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.has('Zelda Delivered', player))
    add_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.has('Zelda Delivered', player))
    # these are because of rails
    if world.shuffle[player] != 'vanilla':
        # where ever these happen to be
        for portal_name in ['Hyrule Castle East', 'Hyrule Castle West']:
            entrance = world.get_portal(portal_name, player).door.entrance
            set_rule(entrance, lambda state: state.has('Zelda Delivered', player))
    set_rule(world.get_entrance('Sanctuary Exit', player), lambda state: state.has('Zelda Delivered', player))
    # zelda should be saved before agahnim is in play
    add_rule(world.get_location('Agahnim 1', player), lambda state: state.has('Zelda Delivered', player))

    # uncle can't have keys generally because unplaced items aren't used here
    def uncle_item_rule(item):
        copy_state = CollectionState(world)
        copy_state.collect(item)
        copy_state.sweep_for_events()
        return copy_state.has('Zelda Delivered', player)

    def bomb_escape_rule():
        loc = world.get_location("Link's Uncle", player)
        return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']

    def standard_escape_rule(state):
        return state.can_kill_most_things(player) or bomb_escape_rule()

    add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule)

    # ensures the required weapon for escape lands on uncle (unless player has it pre-equipped)
    for location in ['Link\'s House', 'Sanctuary', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
                     'Sewers - Secret Room - Right']:
        add_rule(world.get_location(location, player), lambda state: standard_escape_rule(state))
    add_rule(world.get_location('Secret Passage', player), lambda state: standard_escape_rule(state))

    escape_builder = world.dungeon_layouts[player]['Hyrule Castle']
    room_map = world.data_tables[player].uw_enemy_table.room_map
    stats = world.data_tables[player].enemy_stats
    for region in escape_builder.master_sector.regions:
        if region.name in std_kill_rooms:
            entrances, trap_ables, room_id, enemy_list = std_kill_rooms[region.name]
            rule = get_challenge_rule(world, player, room_map, stats, room_id, enemy_list, region)
            for ent in entrances:
                entrance = world.get_entrance(ent, player)
                if not entrance.door or not entrance.door.entranceFlag:
                    add_rule_new(entrance, rule)
            for ent in trap_ables:
                entrance = world.get_entrance(ent, player)
                if entrance.door.trapped and not entrance.door.entranceFlag:
                    add_rule_new(entrance, rule)
        else:
            for loc in region.locations:
                if loc.name in kill_chests:
                    locations, room_id, enemy_list = kill_chests[loc.name]
                    rule = get_challenge_rule(world, player, room_map, stats, room_id, enemy_list, region)
                    add_rule_new(world.get_location(loc, player), rule)
                else:
                    add_rule(loc, lambda state: standard_escape_rule(state))

    set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player))

    def check_rule_list(state, r_list):
        return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:])

    rule_list, debug_path = find_rules_for_zelda_delivery(world, player)
    set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player),
             lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))
    set_rule(world.get_location('Zelda Drop Off', player),
             lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))

    for entrance in ['Links House SC', 'Links House ES', 'Central Bonk Rocks SW', 'Hyrule Castle WN', 'Hyrule Castle ES',
                     'Bonk Fairy (Light)', 'Hyrule Castle Main Gate (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Ledge Drop']:
        add_rule(world.get_entrance(entrance, player), lambda state: state.has('Zelda Delivered', player))

    if world.shuffle_bonk_drops[player]:
        if not world.is_premature_copied_world:
            add_rule(world.get_location('Hyrule Castle Tree', player), lambda state: state.has('Zelda Delivered', player))
            add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player))

    if not world.is_premature_copied_world:
        loc = world.get_location_unsafe('Hyrule Castle Courtyard Tree Pull', player)
        if loc:
            add_rule(loc, lambda state: state.has('Zelda Delivered', player))

    # don't allow bombs to get past here before zelda is rescued
    set_rule(world.get_entrance('GT Hookshot South Entry to Ranged Crystal', player), lambda state: (state.can_use_bombs(player) and state.has('Zelda Delivered', player)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player))  # or state.has('Cane of Somaria', player))


def find_rules_for_zelda_delivery(world, player):
    # path rules for backtracking
    start_region = world.get_region(world.default_zelda_region[player], player)
    queue = deque([(start_region, [], [])])
    visited = {start_region}
    blank_state = CollectionState(world)
    while len(queue) > 0:
        region, path_rules, path = queue.popleft()
        for ext in region.exits:
            connect = ext.connected_region
            valid_region = connect and connect not in visited and\
                (connect.type == RegionType.Dungeon or connect.name == 'Hyrule Castle Ledge')
            if valid_region:
                rule = ext.access_rule
                rule_list = list(path_rules)
                next_path = list(path)
                if not rule(blank_state):
                    rule_list.append(rule)
                    next_path.append(ext.name)
                if connect.name == 'Hyrule Castle Throne Room':
                    return rule_list, next_path
                else:
                    visited.add(connect)
                    queue.append((connect, rule_list, next_path))
    raise Exception('No path to Throne Room found')


def set_bunny_rules(world, player, inverted):
    # regions for the exits of multi-entrace caves/drops that bunny cannot pass
    # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
    all_single_exit_dungeons = ['Eastern Palace', 'Tower of Hera', 'Castle Tower', 'Palace of Darkness', 'Swamp Palace', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Ganons Tower']
    hmg_single_exit_dungeons = [d for d in all_single_exit_dungeons if d not in ['Tower of Hera', 'Misery Mire', 'Thieves Town']]
    bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', 'Two Brothers House',
                              'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)',
                              'Death Mountain Return Cave (right)', 'Paradox Cave (Top)']
    bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
                                  'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Old Man',
                                  'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins',
                                  'Spectacle Rock', 'Bombos Tablet', 'Ether Tablet', 'Kiki Assistance', 'Purple Chest', 'Blacksmith',
                                  'Missing Smith', 'Master Sword Pedestal', 'Bottle Merchant', 'Sunken Treasure', 'Desert Ledge',
                                  'Pyramid Crack', 'Big Bomb', 'Stumpy', 'Lost Old Man', 'Old Man Drop Off', 'Murahdahla',
                                  'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right',
                                  'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right',
                                  'Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right',
                                  'Capacity Upgrade - Left', 'Capacity Upgrade - Right',
                                  'Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right',
                                  'Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right',
                                  'Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right',
                                  'Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right',
                                  'Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right',
                                  'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right',
                                  'Old Man Sword Cave Item 1',
                                  'Take - Any  # 1 Item 1', 'Take - Any  # 1 Item 2',
                                  'Take - Any  # 2 Item 1', 'Take - Any  # 2 Item 2',
                                  'Take - Any  # 3 Item 1', 'Take - Any  # 3 Item 2',
                                  'Take - Any  # 4 Item 1', 'Take - Any  # 4 Item 2',
                                  ]
    bunny_pocket_entrances = ['Skull Woods Final Section', 'Bush Yard Pegs (Inner)', 'Bush Yard Pegs (Outer)',
                              'DM Hammer Bridge (West)', 'DM Hammer Bridge (East)', 'Blacksmith Ledge Peg (West)'
                              ]

    def path_to_access_rule(path, entrance):
        return lambda state: state.can_reach(entrance) and all(rule_func(state) for rule_func in path)

    def options_to_access_rule(options):
        return lambda state: any(rule_func(state) for rule_func in options)

    # Helper functions to determine if the moon pearl is required
    def is_bunny(region):
        if inverted:
            return region.is_light_world
        else:
            return region.is_dark_world

    def is_link(region):
        if inverted:
            return region.is_dark_world
        else:
            return region.is_light_world
        
    # Is it possible to do bunny pocket here        
    def can_bunny_pocket_to(world, entrance_name, player):
        def can_activate_bunny_pocket(region):
            explored_regions.append(region.name)
            for ent in region.entrances:
                if (is_link(ent.parent_region) or
                    (ent.parent_region.type == RegionType.Dungeon and ent.parent_region.name in bunny_revivable_entrances)):
                    return True
            for ent in region.entrances:
                if ent.spot_type in ['OWEdge', 'Ledge', 'OpenTerrain'] and ent.parent_region.name not in explored_regions:
                    if can_activate_bunny_pocket(ent.parent_region):
                        return True
            return False

        entrance = world.get_entrance(entrance_name, player)
        explored_regions = []
        return can_activate_bunny_pocket(entrance.parent_region)


    def get_rule_to_add(region, location=None, connecting_entrance=None):
        # In OWG, a location can potentially be superbunny-mirror accessible or
        # bunny revival accessible.
        if world.logic[player] in ['owglitches', 'hybridglitches']:
            if region.type != RegionType.Dungeon \
                    and (location is None or location.name not in OverworldGlitchRules.superbunny_accessible_locations) \
                    and not is_link(region):
                return lambda state: state.has_Pearl(player)
        else:
            if not is_link(region):
                return lambda state: state.has_Pearl(player)

        # in this case we are mixed region.
        # we collect possible options.

        # The base option is having the moon pearl
        possible_options = [lambda state: state.has_Pearl(player)]

        # We will search entrances recursively until we find
        # one that leads to an exclusively light world region
        # for each such entrance a new option is added that consist of:
        #    a) being able to reach it, and
        #    b) being able to access all entrances from there to `region`
        queue = deque([(region, [], {region}, [region])])
        seen_sets = set([frozenset({region})])
        while queue:
            (current, path, seen, region_path) = queue.popleft()
            for entrance in current.entrances:
                if entrance.door and entrance.door.blocked:
                    continue
                new_region = entrance.parent_region
                new_seen = seen.union({new_region})
                if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_seen in seen_sets:
                    continue
                new_path = path + [entrance.access_rule]
                new_region_path = region_path + [new_region]
                seen_sets.add(frozenset(new_seen))
                if not is_link(new_region):
                    if world.logic[player] in ['owglitches', 'hybridglitches']:
                        # Is this a bunny pocketable entrance?
                        if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon:
                            if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances:
                                continue
                            if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player):
                                continue
                            if entrance.name in drop_dungeon_entrances:
                                lobby = entrance.connected_region
                            else:
                                portal_regions = [world.get_region(reg, player) for reg in region.dungeon.regions if reg.endswith('Portal')]
                                lobby = next(reg.connected_region for portal_reg in portal_regions for reg in portal_reg.exits if reg.name.startswith('Enter '))
                            if lobby.name in bunny_revivable_entrances:
                                possible_options.append(path_to_access_rule(new_path, entrance))
                            elif lobby.name in superbunny_revivable_entrances:
                                possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player)], entrance))
                            elif lobby.name in superbunny_sword_revivable_entrances:
                                possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance))
                            continue
                        elif region.type == RegionType.Cave and new_region.type != RegionType.Cave:
                            if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances:
                                continue
                            if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player):
                                continue
                            if region.name in OverworldGlitchRules.sword_required_superbunny_mirror_regions:
                                possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance))
                            elif region.name in OverworldGlitchRules.boots_required_superbunny_mirror_regions:
                                possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance))
                            elif location and location.name in OverworldGlitchRules.superbunny_accessible_locations:
                                if location.name in OverworldGlitchRules.boots_required_superbunny_mirror_locations:
                                    possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance))
                                elif region.name == 'Kakariko Well (top)':
                                    possible_options.append(path_to_access_rule(new_path, entrance))
                                else:
                                    possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player)], entrance))
                            continue
                        elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.superbunny_accessible_locations:
                            possible_options.append(path_to_access_rule(new_path, entrance))
                    else:
                        continue
                if is_bunny(new_region):
                    # todo: if not owg or hmg and entrance is in bunny_impassible_doors, then skip this nonsense?
                    queue.append((new_region, new_path, new_seen, new_region_path))
                else:
                    # we have reached pure light world, so we have a new possible option
                    possible_options.append(path_to_access_rule(new_path, entrance))
        return options_to_access_rule(possible_options)

    # Add requirements for bunny-impassible caves if they occur in the light world
    for region in [world.get_region(name, player) for name in bunny_impassable_caves]:
        if not is_bunny(region):
            continue
        rule = get_rule_to_add(region)
        for ext in region.exits:
            add_rule(ext, rule)

    paradox_shop = world.get_region('Paradox Shop', player)
    if is_bunny(paradox_shop):
        add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop))

    for ent_name in bunny_impassible_doors:
        bunny_exit = world.get_entrance(ent_name, player)
        if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region):
            add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))

    for ent_name in bunny_impassible_if_trapped:
        bunny_exit = world.get_entrance(ent_name, player)
        if bunny_exit.door.trapped and is_bunny(bunny_exit.parent_region):
            add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))

    doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors]
    doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
    for door in doors_to_check:
        room = world.get_room(door.roomIndex, player)
        if is_bunny(door.entrance.parent_region) and room.kind(door) in [DoorKind.Dashable, DoorKind.Bombable, DoorKind.Hidden]:
            add_rule(door.entrance, get_rule_to_add(door.entrance.parent_region))

    for region in world.get_regions():
        if region.player == player and is_bunny(region):
            for location in region.locations:
                if location.name in bunny_accessible_locations:
                    continue
                add_rule(location, get_rule_to_add(region, location))

    if world.logic[player] in ['owglitches', 'hybridglitches']:
        for ent_name in bunny_pocket_entrances:
            bunny_exit = world.get_entrance(ent_name, player)
            if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region) and not can_bunny_pocket_to(world, ent_name, player):
                add_rule(bunny_exit, lambda state: state.has_Pearl(player))


drop_dungeon_entrances = {
    "Sewer Drop",
    "Skull Left Drop",
    "Skull Pinball",
    "Skull Pot Circle",
    "Skull Back Drop"
}

bunny_revivable_entrances = {
    "Sewers Pull Switch", "TR Dash Room", "Swamp Boss", "Hera Boss",
    "Tower Agahnim 1", "Ice Lobby", "Sewers Rat Path", "PoD Falling Bridge",
    "PoD Harmless Hellway", "PoD Mimics 2", "Ice Cross Bottom", "GT Agahnim 2",
    "Sewers Water", "TR Lazy Eyes", "TR Big Chest Entrance", "Swamp Push Statue",
    "PoD Arena Main", "PoD Arena Bridge", "PoD Map Balcony", "Sewers Dark Cross",
    "Desert Boss", "Swamp Hub", "Skull Spike Corner", "PoD Pit Room",
    "PoD Conveyor", "GT Crystal Circles", "Sewers Behind Tapestry",
    "Desert Tiles 2", "Skull Star Pits", "Hyrule Castle West Hall",
    "Hyrule Castle Throne Room", "Hyrule Castle East Hall", "Skull 2 West Lobby",
    "Skull 2 East Lobby", "Skull Pot Prison", "Skull 1 Lobby", "Skull Map Room",
    "Skull 3 Lobby", "PoD Boss", "GT Hidden Spikes", "GT Gauntlet 3",
    "Ice Spike Cross", "Hyrule Castle West Lobby", "Hyrule Castle Lobby",
    "Hyrule Castle East Lobby", "Desert Back Lobby", "Hyrule Dungeon Armory Main",
    "Hyrule Dungeon North Abyss", "Desert Sandworm Corner", "Desert Dead End",
    "Desert North Hall", "Desert Arrow Pot Corner", "GT DMs Room",
    "GT Petting Zoo", "Ice Tall Hint", "Desert West Lobby", "Desert Main Lobby",
    "Desert East Lobby", "GT Big Chest", "GT Bob\'s Room", "GT Speed Torch",
    "Mire Boss", "GT Conveyor Bridge", "Mire Lobby", "Eastern Darkness",
    "Ice Many Pots", "Mire South Fish", "Mire Right Bridge", "Mire Left Bridge",
    "TR Boss", "Eastern Hint Tile Blocked Path", "Thieves Spike Switch",
    "Thieves Boss", "Mire Spike Barrier", "Mire Cross", "Mire Hidden Shooters",
    "Mire Spikes", "TR Final Abyss Balcony", "TR Dark Ride South Platform", "TR Pokey 1", "TR Tile Room",
    "TR Roller Room", "Eastern Cannonball", "Thieves Hallway", "Ice Switch Room",
    "Mire Tile Room", "Mire Conveyor Crystal", "Mire Hub", "TR Dash Bridge",
    "TR Hub", "Eastern Boss", "Eastern Lobby", "Thieves Ambush",
    "Thieves BK Corner", "TR Eye Bridge", "Thieves Lobby", "Tower Lobby",
    "Sewer Drop", "Skull Left Drop", "Skull Pinball", "Skull Back Drop",
    "Skull Pot Circle",  # You automatically get superbunny by dropping
}

# Revive as superbunny or use superbunny to get the item in a dead end
superbunny_revivable_entrances = {
    "TR Main Lobby", "Sanctuary", "Thieves Pot Alcove Bottom"
}

superbunny_sword_revivable_entrances = {
    "Hera Lobby"
}

bunny_impassible_doors = {
    'Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES', 'Sewers Pull Switch S',
    'Eastern Lobby N', 'Eastern Courtyard Ledge W', 'Eastern Courtyard Ledge E', 'Eastern Pot Switch SE',
    'Eastern Map Balcony Hook Path', 'Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW',
    'Eastern Darkness S', 'Eastern Darkness NE', 'Eastern Darkness Up Stairs',
    'Eastern Attic Start WS', 'Eastern Single Eyegore NE', 'Eastern Duo Eyegores NE', 'Desert Main Lobby Left Path',
    'Desert Main Lobby Right Path', 'Desert Left Alcove Path', 'Desert Right Alcove Path', 'Desert Compass NE',
    'Desert West Lobby NW', 'Desert Back Lobby NW', 'Desert Four Statues NW',  'Desert Four Statues ES',
    'Desert Beamos Hall WS', 'Desert Beamos Hall NE', 'Desert Wall Slide NW',
    'Hera Lobby to Front Barrier - Blue', 'Hera Front to Lobby Barrier - Blue', 'Hera Front to Down Stairs Barrier - Blue',
    'Hera Down Stairs to Front Barrier - Blue', 'Hera Tile Room EN', 'Hera Tridorm SE', 'Hera Beetles WS',
    'Hera 4F Down Stairs', 'Tower Gold Knights SW', 'Tower Dark Maze EN', 'Tower Dark Pits ES', 'Tower Dark Archers WN',
    'Tower Red Spears WN', 'Tower Red Guards EN', 'Tower Red Guards SW', 'Tower Circle of Pots NW', 'Tower Altar NW',
    'PoD Left Cage SW', 'PoD Middle Cage SE', 'PoD Pit Room Bomb Hole', 'PoD Stalfos Basement Warp',
    'PoD Arena Main to Landing Barrier - Blue', 'PoD Arena Landing to Right Barrier - Blue',
    'PoD Arena Right to Landing Barrier - Blue', 'PoD Arena Main to Landing Barrier - Blue',
    'PoD Arena Landing Bonk Path', 'PoD Sexy Statue NW', 'PoD Map Balcony Drop Down',
    'PoD Mimics 1 NW', 'PoD Falling Bridge Path N', 'PoD Falling Bridge Path S',
    'PoD Mimics 2 NW', 'PoD Bow Statue Down Ladder', 'PoD Dark Pegs Landing to Right',
    'PoD Dark Pegs Left to Middle Barrier - Blue', 'PoD Dark Pegs Left to Ranged Crystal',
    'PoD Turtle Party ES', 'PoD Turtle Party NW', 'PoD Callback Warp', 'Swamp Lobby Moat', 'Swamp Entrance Moat',
    'Swamp Trench 1 Approach Swim Depart', 'Swamp Trench 1 Approach Key', 'Swamp Trench 1 Key Approach',
    'Swamp Trench 1 Key Ledge Depart', 'Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Departure Key',
    'Swamp Hub Hook Path', 'Swamp Shortcut Blue Barrier', 'Swamp Trench 2 Pots Blue Barrier',
    'Swamp Trench 2 Pots Wet', 'Swamp Trench 2 Departure Wet', 'Swamp West Ledge Hook Path', 'Swamp Barrier Ledge Hook Path',
    'Swamp Attic Left Pit', 'Swamp Attic Right Pit', 'Swamp Push Statue NW', 'Swamp Push Statue NE',
    'Swamp Drain Right Switch', 'Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW',
    'Skull Pot Circle WN', 'Skull Pot Circle Star Path', 'Skull Pull Switch S', 'Skull Big Chest N',
    'Skull Big Chest Hookpath', 'Skull 2 East Lobby NW', 'Skull Back Drop Star Path', 'Skull 2 West Lobby NW',
    'Skull 3 Lobby EN', 'Skull Star Pits SW', 'Skull Star Pits ES', 'Skull Torch Room WN', 'Skull Vines NW',
    'Thieves Conveyor Maze EN', 'Thieves Triple Bypass EN', 'Thieves Triple Bypass SE', 'Thieves Triple Bypass WN',
    'Thieves Hellway Blue Barrier', 'Thieves Hellway Crystal Blue Barrier', 'Thieves Attic ES',
    'Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Bridge Block Path',
    'Thieves Conveyor Block Path', 'Ice Lobby WS', 'Ice Cross Left Push Block', 'Ice Cross Bottom Push Block Left',
    'Ice Bomb Drop Hole', 'Ice Pengator Switch WS', 'Ice Pengator Switch ES', 'Ice Big Key Push Block',
    'Ice Stalfos Hint SE', 'Ice Bomb Jump EN', 'Ice Pengator Trap NE', 'Ice Hammer Block ES', 'Ice Right H Path',
    'Ice Bomb Drop Path', 'Ice Tongue Pull WS', 'Ice Freezors Bomb Hole', 'Ice Tall Hint WS',
    'Ice Hookshot Ledge Path', 'Ice Hookshot Balcony Path', 'Ice Many Pots SW', 'Ice Many Pots WS',
    'Ice Crystal Right Blue Hole', 'Ice Crystal Left Blue Barrier', 'Ice Big Chest Landing Push Blocks',
    'Ice Backwards Room Hole', 'Ice Switch Room SE', 'Ice Antechamber NE', 'Ice Antechamber Hole', 'Mire Lobby Gap',
    'Mire Post-Gap Gap', 'Mire 2 NE', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier',
    'Mire Hub Right Blue Barrier', 'Mire Hub Top Blue Barrier', 'Mire Hub Switch Blue Barrier N',
    'Mire Hub Switch Blue Barrier S', 'Mire Falling Bridge Hook Path', 'Mire Falling Bridge Hook Only Path',
    'Mire Map Spike Side Blue Barrier', 'Mire Map Spot Blue Barrier', 'Mire Crystal Dead End Left Barrier',
    'Mire Crystal Dead End Right Barrier', 'Mire Cross ES', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
    'Mire South Fish Blue Barrier', 'Mire Tile Room NW', 'Mire Compass Blue Barrier', 'Mire Attic Hint Hole',
    'Mire Dark Shooters SW', 'Mire Crystal Mid Blue Barrier', 'Mire Crystal Left Blue Barrier', 'TR Main Lobby Gap',
    'TR Lobby Ledge Gap', 'TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Hub Path',
    'TR Hub Ledges Path', 'TR Torches NW', 'TR Pokey 2 Bottom to Top Barrier - Blue',
    'TR Pokey 2 Top to Bottom Barrier - Blue', 'TR Twin Pokeys SW', 'TR Twin Pokeys EN', 'TR Big Chest Gap',
    'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride SW',  # due to needing the switch
    'TR Dark Ride Normal Path', 'TR Dark Ride Ledge Path', 'TR Dark Ride Backward Path', 'TR Dark Ride Return Path',
    'TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze End to Interior Barrier - Blue',
    'TR Final Abyss Balcony Path', 'TR Final Abyss Ledge Path', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
    'GT Bob\'s Room Hole', 'GT Speed Torch SE', 'GT Speed Torch South Path', 'GT Speed Torch North Path',
    'GT Crystal Conveyor NE', 'GT Crystal Conveyor WN', 'GT Conveyor Cross EN', 'GT Conveyor Cross WN',
    'GT Hookshot East-Mid Path', 'GT Hookshot South-Mid Path', 'GT Hookshot North-Mid Path',
    'GT Hookshot Mid-South Path', 'GT Hookshot Mid-East Path', 'GT Hookshot Mid-North Path',
    'GT Hookshot Platform Blue Barrier', 'GT Hookshot Entry Blue Barrier', 'GT Double Switch Pot Corners to Exit Barrier - Blue',
    'GT Double Switch Exit to Blue Barrier', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS',
    'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS',
    'GT Mimics 2 NE', 'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN',
    'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW',
    'GT Gauntlet 5 NW', 'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW',
    'GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap',
    'GT Validation Block Path'
}


# these should generally match trap_door_exceptions unless the switch is in the open/push block
bunny_impassible_if_trapped = {
    'Hyrule Dungeon Armory Interior Key Door N', 'Eastern Pot Switch WN', 'Eastern Lobby NW',
    'Eastern Lobby NE', 'Eastern Courtyard Ledge S',  'Desert Compass Key Door WN', 'Tower Circle of Pots ES',
    'PoD Mimics 1 SW', 'PoD Mimics 2 SW', 'PoD Middle Cage S', 'PoD Lobby N', 'Swamp Push Statue S',
    'Skull 2 East Lobby WS', 'Skull Torch Room WS', 'Thieves Conveyor Maze WN', 'Thieves Conveyor Maze SW',
    'Thieves Blocked Entry SW', 'Ice Bomb Jump NW',
    'Ice Tall Hint EN', 'Ice Tall Hint SE', 'Ice Switch Room ES', 'Ice Switch Room NE', 'Mire Cross SW',
    'Mire Tile Room SW', 'Mire Tile Room ES', 'TR Tongue Pull WS', 'TR Twin Pokeys NW', 'TR Torches WN', 'GT Hope Room WN',
    'GT Speed Torch NE', 'GT Speed Torch WS', 'GT Torch Cross WN', 'GT Hidden Spikes SE', 'GT Conveyor Cross EN',
    'GT Speed Torch WN', 'Ice Lobby SE'
}

def add_hmg_key_logic_rules(world, player):
    for toh_loc in world.key_logic[player]['Tower of Hera'].bk_restricted:
        set_always_allow(world.get_location(toh_loc.name, player), allow_big_key_in_big_chest('Big Key (Tower of Hera)', player))
    set_always_allow(world.get_location('Swamp Palace - Entrance', player), allow_big_key_in_big_chest('Big Key (Swamp Palace)', player))


def add_key_logic_rules(world, player):
    key_logic = world.key_logic[player]
    eval_func = eval_small_key_door
    if world.key_logic_algorithm[player] == 'strict' and world.keyshuffle[player] not in ['none', 'universal']:
        eval_func = eval_small_key_door_strict
    elif world.key_logic_algorithm[player] != 'dangerous':
        eval_func = eval_small_key_door_partial
    for d_name, d_logic in key_logic.items():
        for door_name, rule in d_logic.door_rules.items():
            door_entrance = world.get_entrance(door_name, player)
            if not door_entrance.door.smallKey and door_entrance.door.crystal == CrystalBarrier.Blue:
                add_rule(door_entrance, eval_alternative_crystal(door_name, d_name, player), 'or')
            else:
                add_rule(door_entrance, eval_func(door_name, d_name, player))
                if door_entrance.door.dependents:
                    for dep in door_entrance.door.dependents:
                        add_rule(dep.entrance, eval_func(door_name, d_name, player))
        for location in d_logic.bk_restricted:
            if not location.forced_item:
                forbid_item(location, d_logic.bk_name, player)
        for location in d_logic.sm_restricted:
            forbid_item(location, d_logic.small_key_name, player)
        for door in d_logic.bk_doors:
            add_rule(world.get_entrance(door.name, player), create_rule(d_logic.bk_name, player))
            if door.dependents:
                for dep in door.dependents:
                    add_rule(dep.entrance, create_rule(d_logic.bk_name, player))
        for chest in d_logic.bk_chests:
            big_chest = world.get_location(chest.name, player)
            add_rule(big_chest, create_rule(d_logic.bk_name, player))
            if (len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1
               and world.accessibility[player] != 'locations'):
                set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player))
    if world.keyshuffle[player] == 'universal':
        for d_name, layout in world.key_layout[player].items():
            for door in layout.flat_prop:
                if world.mode[player] != 'standard' or not retro_in_hc(door.entrance):
                    add_rule(door.entrance, create_key_rule('Small Key (Universal)', player, 1))


def eval_small_key_door_main(state, door_name, dungeon, player):
    if state.is_door_open(door_name, player):
        return True
    key_logic = state.world.key_logic[player][dungeon]
    if door_name not in key_logic.door_rules:
        return False
    door_rule = key_logic.door_rules[door_name]
    door_openable = False
    for ruleType, number in door_rule.new_rules.items():
        if door_openable:
            return True
        if ruleType == KeyRuleType.WorstCase:
            door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
        elif ruleType == KeyRuleType.AllowSmall:
            small_loc_item = door_rule.small_location.item
            if small_loc_item and small_loc_item.name == key_logic.small_key_name and small_loc_item.player == player:
                door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
        elif isinstance(ruleType, tuple):
            lock, lock_item = ruleType
            # this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't
            for loc in door_rule.alternate_big_key_loc:
                spot = state.world.get_location(loc, player)
                if spot.item and spot.item.name == lock_item:
                    door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
                    break
    return door_openable


def eval_small_key_door_partial_main(state, door_name, dungeon, player):
    if state.is_door_open(door_name, player):
        return True
    key_logic = state.world.key_logic[player][dungeon]
    if door_name not in key_logic.door_rules:
        return False
    door_rule = key_logic.door_rules[door_name]
    door_openable = False
    for ruleType, number in door_rule.new_rules.items():
        if door_openable:
            return True
        if ruleType == KeyRuleType.WorstCase:
            number = min(number, door_rule.small_key_num)
            door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
        elif ruleType == KeyRuleType.AllowSmall:
            small_loc_item = door_rule.small_location.item
            if small_loc_item and small_loc_item.name == key_logic.small_key_name and small_loc_item.player == player:
                door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
        elif isinstance(ruleType, tuple):
            lock, lock_item = ruleType
            # this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't
            for loc in door_rule.alternate_big_key_loc:
                spot = state.world.get_location(loc, player)
                if spot.item and spot.item.name == lock_item:
                    number = min(number, door_rule.alternate_small_key)
                    door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
                    break
            if state.placing_items and any(lock_item == item.name for item in state.placing_items):
                number = min(number, door_rule.alternate_small_key)
                door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
    return door_openable


def eval_small_key_door_strict_main(state, door_name, dungeon, player):
    if state.is_door_open(door_name, player):
        return True
    key_layout = state.world.key_layout[player][dungeon]
    number = key_layout.max_chests
    if number <= 0:
        return True
    return state.has_sm_key_strict(key_layout.key_logic.small_key_name, player, number)


def eval_alternative_crystal_main(state, door_name, dungeon, player):
    key_logic = state.world.key_logic[player][dungeon]
    door_rule = key_logic.door_rules[door_name]
    for ruleType, number in door_rule.new_rules.items():
        if ruleType == KeyRuleType.CrystalAlternative:
            return state.has_sm_key(key_logic.small_key_name, player, number)
    return False


def eval_small_key_door(door_name, dungeon, player):
    return lambda state: eval_small_key_door_main(state, door_name, dungeon, player)


def eval_small_key_door_partial(door_name, dungeon, player):
    return lambda state: eval_small_key_door_partial_main(state, door_name, dungeon, player)


def eval_small_key_door_strict(door_name, dungeon, player):
    return lambda state: eval_small_key_door_strict_main(state, door_name, dungeon, player)


def eval_alternative_crystal(door_name, dungeon, player):
    return lambda state: eval_alternative_crystal_main(state, door_name, dungeon, player)


def allow_big_key_in_big_chest(bk_name, player):
    return lambda state, item: item.name == bk_name and item.player == player


def retro_in_hc(spot):
    return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False


def create_rule(item_name, player):
    return lambda state: state.has(item_name, player)


def create_key_rule(small_key_name, player, keys):
    return lambda state: state.has_sm_key(small_key_name, player, keys)


def create_key_rule_allow_small(small_key_name, player, keys, location):
    loc = location.name
    return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1))


def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs):
    chest_names = [x.name for x in bk_locs]
    return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys))


def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs):
    loc = location.name
    chest_names = [x.name for x in bk_locs]
    return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys))


def create_advanced_key_rule(key_logic, player, rule):
    if not rule.allow_small and rule.alternate_small_key is None:
        return create_key_rule(key_logic.small_key_name, player, rule.small_key_num)
    if rule.allow_small and rule.alternate_small_key is None:
        return create_key_rule_allow_small(key_logic.small_key_name, player, rule.small_key_num, rule.small_location)
    if not rule.allow_small and rule.alternate_small_key is not None:
        return create_key_rule_bk_exception(key_logic.small_key_name, key_logic.bk_name, player, rule.small_key_num,
                                            rule.alternate_small_key, rule.alternate_big_key_loc)
    if rule.allow_small and rule.alternate_small_key is not None:
        return create_key_rule_bk_exception_or_allow(key_logic.small_key_name, key_logic.bk_name, player,
                                                     rule.small_key_num, rule.small_location, rule.alternate_small_key,
                                                     rule.alternate_big_key_loc)
