Merge pull request #93 from compiling/owg_multi
Optimise world exploration
This commit is contained in:
		
						commit
						e1add44d83
					
				| 
						 | 
					@ -4,9 +4,9 @@ import copy
 | 
				
			||||||
from enum import Enum, unique
 | 
					from enum import Enum, unique
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
from collections import OrderedDict, Counter
 | 
					from collections import OrderedDict, Counter, deque
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from EntranceShuffle import door_addresses
 | 
					from EntranceShuffle import door_addresses, indirect_connections
 | 
				
			||||||
from Utils import int16_as_bytes
 | 
					from Utils import int16_as_bytes
 | 
				
			||||||
from typing import Union
 | 
					from typing import Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -396,6 +396,7 @@ class CollectionState(object):
 | 
				
			||||||
        self.prog_items = Counter()
 | 
					        self.prog_items = Counter()
 | 
				
			||||||
        self.world = parent
 | 
					        self.world = parent
 | 
				
			||||||
        self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
 | 
					        self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
 | 
				
			||||||
 | 
					        self.blocked_connections = {player: set() for player in range(1, parent.players + 1)}
 | 
				
			||||||
        self.events = []
 | 
					        self.events = []
 | 
				
			||||||
        self.path = {}
 | 
					        self.path = {}
 | 
				
			||||||
        self.locations_checked = set()
 | 
					        self.locations_checked = set()
 | 
				
			||||||
| 
						 | 
					@ -404,24 +405,46 @@ class CollectionState(object):
 | 
				
			||||||
            self.collect(item, True)
 | 
					            self.collect(item, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_reachable_regions(self, player: int):
 | 
					    def update_reachable_regions(self, player: int):
 | 
				
			||||||
        player_regions = self.world.get_regions(player)
 | 
					 | 
				
			||||||
        self.stale[player] = False
 | 
					        self.stale[player] = False
 | 
				
			||||||
        rrp = self.reachable_regions[player]
 | 
					        rrp = self.reachable_regions[player]
 | 
				
			||||||
        new_regions = True
 | 
					        bc = self.blocked_connections[player]
 | 
				
			||||||
        reachable_regions_count = len(rrp)
 | 
					        queue = deque(self.blocked_connections[player])
 | 
				
			||||||
        while new_regions:
 | 
					        start = self.world.get_region('Menu', player)
 | 
				
			||||||
            player_regions = [region for region in player_regions if region not in rrp]
 | 
					
 | 
				
			||||||
            for candidate in player_regions:
 | 
					        # init on first call - this can't be done on construction since the regions don't exist yet
 | 
				
			||||||
                if candidate.can_reach_private(self):
 | 
					        if not start in rrp:
 | 
				
			||||||
                    rrp.add(candidate)
 | 
					            rrp.add(start)
 | 
				
			||||||
            new_regions = len(rrp) > reachable_regions_count
 | 
					            bc.update(start.exits)
 | 
				
			||||||
            reachable_regions_count = len(rrp)
 | 
					            queue.extend(start.exits)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # run BFS on all connections, and keep track of those blocked by missing items
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                connection = queue.popleft()
 | 
				
			||||||
 | 
					                new_region = connection.connected_region
 | 
				
			||||||
 | 
					                if new_region in rrp:
 | 
				
			||||||
 | 
					                    bc.remove(connection)
 | 
				
			||||||
 | 
					                elif connection.can_reach(self):
 | 
				
			||||||
 | 
					                    rrp.add(new_region)
 | 
				
			||||||
 | 
					                    bc.remove(connection)
 | 
				
			||||||
 | 
					                    bc.update(new_region.exits)
 | 
				
			||||||
 | 
					                    queue.extend(new_region.exits)
 | 
				
			||||||
 | 
					                    self.path[new_region] = (new_region.name, self.path.get(connection, None))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Retry connections if the new region can unblock them
 | 
				
			||||||
 | 
					                    if new_region.name in indirect_connections:
 | 
				
			||||||
 | 
					                        new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player)
 | 
				
			||||||
 | 
					                        if new_entrance in bc and new_entrance not in queue:
 | 
				
			||||||
 | 
					                            queue.append(new_entrance)
 | 
				
			||||||
 | 
					            except IndexError:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def copy(self) -> CollectionState:
 | 
					    def copy(self) -> CollectionState:
 | 
				
			||||||
        ret = CollectionState(self.world)
 | 
					        ret = CollectionState(self.world)
 | 
				
			||||||
        ret.prog_items = self.prog_items.copy()
 | 
					        ret.prog_items = self.prog_items.copy()
 | 
				
			||||||
        ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
 | 
					        ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
 | 
				
			||||||
                                 range(1, self.world.players + 1)}
 | 
					                                 range(1, self.world.players + 1)}
 | 
				
			||||||
 | 
					        ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)}
 | 
				
			||||||
        ret.events = copy.copy(self.events)
 | 
					        ret.events = copy.copy(self.events)
 | 
				
			||||||
        ret.path = copy.copy(self.path)
 | 
					        ret.path = copy.copy(self.path)
 | 
				
			||||||
        ret.locations_checked = copy.copy(self.locations_checked)
 | 
					        ret.locations_checked = copy.copy(self.locations_checked)
 | 
				
			||||||
| 
						 | 
					@ -737,6 +760,7 @@ class CollectionState(object):
 | 
				
			||||||
                    del (self.prog_items[to_remove, item.player])
 | 
					                    del (self.prog_items[to_remove, item.player])
 | 
				
			||||||
                # invalidate caches, nothing can be trusted anymore now
 | 
					                # invalidate caches, nothing can be trusted anymore now
 | 
				
			||||||
                self.reachable_regions[item.player] = set()
 | 
					                self.reachable_regions[item.player] = set()
 | 
				
			||||||
 | 
					                self.blocked_connections[item.player] = set()
 | 
				
			||||||
                self.stale[item.player] = True
 | 
					                self.stale[item.player] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@unique
 | 
					@unique
 | 
				
			||||||
| 
						 | 
					@ -814,10 +838,11 @@ class Entrance(object):
 | 
				
			||||||
        self.vanilla = None
 | 
					        self.vanilla = None
 | 
				
			||||||
        self.access_rule = lambda state: True
 | 
					        self.access_rule = lambda state: True
 | 
				
			||||||
        self.player = player
 | 
					        self.player = player
 | 
				
			||||||
 | 
					        self.hide_path = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def can_reach(self, state):
 | 
					    def can_reach(self, state):
 | 
				
			||||||
        if self.parent_region.can_reach(state) and self.access_rule(state):
 | 
					        if self.parent_region.can_reach(state) and self.access_rule(state):
 | 
				
			||||||
            if not self in state.path:
 | 
					            if not self.hide_path and not self in state.path:
 | 
				
			||||||
                state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
 | 
					                state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1198,7 +1198,9 @@ def link_inverted_entrances(world, player):
 | 
				
			||||||
        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
 | 
					        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
 | 
				
			||||||
        sanc_door = random.choice(sanc_doors)
 | 
					        sanc_door = random.choice(sanc_doors)
 | 
				
			||||||
        bomb_shop_doors.remove(sanc_door)
 | 
					        bomb_shop_doors.remove(sanc_door)
 | 
				
			||||||
        connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player)
 | 
					
 | 
				
			||||||
 | 
					        connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
 | 
				
			||||||
 | 
					        world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        lw_dm_entrances = ['Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Old Man House (Bottom)',
 | 
					        lw_dm_entrances = ['Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Old Man House (Bottom)',
 | 
				
			||||||
                           'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave (Bottom)', 'Old Man Cave (East)',
 | 
					                           'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave (Bottom)', 'Old Man Cave (East)',
 | 
				
			||||||
| 
						 | 
					@ -1273,7 +1275,8 @@ def link_inverted_entrances(world, player):
 | 
				
			||||||
        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
 | 
					        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
 | 
				
			||||||
        sanc_door = random.choice(sanc_doors)
 | 
					        sanc_door = random.choice(sanc_doors)
 | 
				
			||||||
        dw_entrances.remove(sanc_door)
 | 
					        dw_entrances.remove(sanc_door)
 | 
				
			||||||
        connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player)
 | 
					        connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
 | 
				
			||||||
 | 
					        world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # tavern back door cannot be shuffled yet
 | 
					        # tavern back door cannot be shuffled yet
 | 
				
			||||||
        connect_doors(world, ['Tavern North'], ['Tavern'], player)
 | 
					        connect_doors(world, ['Tavern North'], ['Tavern'], player)
 | 
				
			||||||
| 
						 | 
					@ -1404,7 +1407,8 @@ def link_inverted_entrances(world, player):
 | 
				
			||||||
        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
 | 
					        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
 | 
				
			||||||
        sanc_door = random.choice(sanc_doors)
 | 
					        sanc_door = random.choice(sanc_doors)
 | 
				
			||||||
        dw_entrances.remove(sanc_door)
 | 
					        dw_entrances.remove(sanc_door)
 | 
				
			||||||
        connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player)
 | 
					        connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
 | 
				
			||||||
 | 
					        world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        # place old man house
 | 
					        # place old man house
 | 
				
			||||||
        # no dw must exits in inverted, but we randomize whether cave is in light or dark world
 | 
					        # no dw must exits in inverted, but we randomize whether cave is in light or dark world
 | 
				
			||||||
| 
						 | 
					@ -1541,7 +1545,8 @@ def link_inverted_entrances(world, player):
 | 
				
			||||||
        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances]
 | 
					        sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances]
 | 
				
			||||||
        sanc_door = random.choice(sanc_doors)
 | 
					        sanc_door = random.choice(sanc_doors)
 | 
				
			||||||
        entrances.remove(sanc_door)
 | 
					        entrances.remove(sanc_door)
 | 
				
			||||||
        connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player)
 | 
					        connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
 | 
				
			||||||
 | 
					        world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # tavern back door cannot be shuffled yet
 | 
					        # tavern back door cannot be shuffled yet
 | 
				
			||||||
        connect_doors(world, ['Tavern North'], ['Tavern'], player)
 | 
					        connect_doors(world, ['Tavern North'], ['Tavern'], player)
 | 
				
			||||||
| 
						 | 
					@ -1674,7 +1679,8 @@ def link_inverted_entrances(world, player):
 | 
				
			||||||
        sanc_door = random.choice(sanc_doors)
 | 
					        sanc_door = random.choice(sanc_doors)
 | 
				
			||||||
        entrances.remove(sanc_door)
 | 
					        entrances.remove(sanc_door)
 | 
				
			||||||
        doors.remove(sanc_door)
 | 
					        doors.remove(sanc_door)
 | 
				
			||||||
        connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player)
 | 
					        connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
 | 
				
			||||||
 | 
					        world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # now let's deal with mandatory reachable stuff
 | 
					        # now let's deal with mandatory reachable stuff
 | 
				
			||||||
        def extract_reachable_exit(cavelist):
 | 
					        def extract_reachable_exit(cavelist):
 | 
				
			||||||
| 
						 | 
					@ -2810,7 +2816,10 @@ Isolated_LH_Doors = ['Kings Grave',
 | 
				
			||||||
                     'Turtle Rock Isolated Ledge Entrance']
 | 
					                     'Turtle Rock Isolated Ledge Entrance']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
 | 
					# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
 | 
				
			||||||
mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
 | 
					mandatory_connections = [('Links House S&Q', 'Links House'),
 | 
				
			||||||
 | 
					                         ('Sanctuary S&Q', 'Sanctuary'),
 | 
				
			||||||
 | 
					                         ('Old Man S&Q', 'Old Man House'),
 | 
				
			||||||
 | 
					                         ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
 | 
				
			||||||
                         ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'),
 | 
					                         ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'),
 | 
				
			||||||
                         ('Zoras River', 'Zoras River'),
 | 
					                         ('Zoras River', 'Zoras River'),
 | 
				
			||||||
                         ('Kings Grave Outer Rocks', 'Kings Grave Area'),
 | 
					                         ('Kings Grave Outer Rocks', 'Kings Grave Area'),
 | 
				
			||||||
| 
						 | 
					@ -2991,7 +3000,11 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
 | 
				
			||||||
                         ('Pyramid Drop', 'East Dark World')
 | 
					                         ('Pyramid Drop', 'East Dark World')
 | 
				
			||||||
                        ]
 | 
					                        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
 | 
					inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
 | 
				
			||||||
 | 
					                                  ('Dark Sanctuary S&Q', 'Inverted Dark Sanctuary'),
 | 
				
			||||||
 | 
					                                  ('Old Man S&Q', 'Old Man House'),
 | 
				
			||||||
 | 
					                                  ('Castle Ledge S&Q', 'Hyrule Castle Ledge'),
 | 
				
			||||||
 | 
					                                  ('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
 | 
				
			||||||
                                  ('Lake Hylia Island Pier', 'Lake Hylia Island'),
 | 
					                                  ('Lake Hylia Island Pier', 'Lake Hylia Island'),
 | 
				
			||||||
                                  ('Lake Hylia Warp', 'Northeast Light World'),
 | 
					                                  ('Lake Hylia Warp', 'Northeast Light World'),
 | 
				
			||||||
                                  ('Northeast Light World Warp', 'Light World'),
 | 
					                                  ('Northeast Light World Warp', 'Light World'),
 | 
				
			||||||
| 
						 | 
					@ -3495,6 +3508,7 @@ inverted_default_connections =  [('Waterfall of Wishing', 'Waterfall of Wishing'
 | 
				
			||||||
                                 ('Inverted Links House Exit', 'South Dark World'),
 | 
					                                 ('Inverted Links House Exit', 'South Dark World'),
 | 
				
			||||||
                                 ('Inverted Big Bomb Shop', 'Inverted Big Bomb Shop'),
 | 
					                                 ('Inverted Big Bomb Shop', 'Inverted Big Bomb Shop'),
 | 
				
			||||||
                                 ('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'),
 | 
					                                 ('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'),
 | 
				
			||||||
 | 
					                                 ('Inverted Dark Sanctuary Exit', 'West Dark World'),
 | 
				
			||||||
                                 ('Old Man Cave (West)', 'Bumper Cave'),
 | 
					                                 ('Old Man Cave (West)', 'Bumper Cave'),
 | 
				
			||||||
                                 ('Old Man Cave (East)', 'Death Mountain Return Cave'),
 | 
					                                 ('Old Man Cave (East)', 'Death Mountain Return Cave'),
 | 
				
			||||||
                                 ('Old Man Cave Exit (West)', 'West Dark World'),
 | 
					                                 ('Old Man Cave Exit (West)', 'West Dark World'),
 | 
				
			||||||
| 
						 | 
					@ -3626,6 +3640,18 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
 | 
				
			||||||
                                        ('Turtle Rock Exit (Front)', 'Dark Death Mountain'),
 | 
					                                        ('Turtle Rock Exit (Front)', 'Dark Death Mountain'),
 | 
				
			||||||
                                        ('Ice Palace Exit', 'Dark Lake Hylia')]
 | 
					                                        ('Ice Palace Exit', 'Dark Lake Hylia')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Regions that can be required to access entrances through rules, not paths
 | 
				
			||||||
 | 
					indirect_connections = {
 | 
				
			||||||
 | 
					    'Turtle Rock (Top)': 'Turtle Rock',
 | 
				
			||||||
 | 
					    'East Dark World': 'Pyramid Fairy',
 | 
				
			||||||
 | 
					    'Big Bomb Shop': 'Pyramid Fairy',
 | 
				
			||||||
 | 
					    'Dark Desert': 'Pyramid Fairy',
 | 
				
			||||||
 | 
					    'West Dark World': 'Pyramid Fairy',
 | 
				
			||||||
 | 
					    'South Dark World': 'Pyramid Fairy',
 | 
				
			||||||
 | 
					    'Light World': 'Pyramid Fairy',
 | 
				
			||||||
 | 
					    'Old Man Cave': 'Old Man S&Q'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# format:
 | 
					# format:
 | 
				
			||||||
# Key=Name
 | 
					# Key=Name
 | 
				
			||||||
# addr = (door_index, exitdata) # multiexit
 | 
					# addr = (door_index, exitdata) # multiexit
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ from Regions import create_lw_region, create_dw_region, create_cave_region, crea
 | 
				
			||||||
def create_inverted_regions(world, player):
 | 
					def create_inverted_regions(world, player):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    world.regions += [
 | 
					    world.regions += [
 | 
				
			||||||
 | 
					        create_dw_region(player, 'Menu', None, ['Links House S&Q', 'Dark Sanctuary S&Q', 'Old Man S&Q', 'Castle Ledge S&Q']),
 | 
				
			||||||
        create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'],
 | 
					        create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'],
 | 
				
			||||||
                         ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
 | 
					                         ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
 | 
				
			||||||
                          'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
 | 
					                          'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
 | 
				
			||||||
| 
						 | 
					@ -197,7 +198,7 @@ def create_inverted_regions(world, player):
 | 
				
			||||||
        create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
 | 
					        create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
 | 
				
			||||||
        create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
 | 
					        create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
 | 
				
			||||||
        create_cave_region(player, 'Red Shield Shop', 'the rare shop'),
 | 
					        create_cave_region(player, 'Red Shield Shop', 'the rare shop'),
 | 
				
			||||||
        create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller'),
 | 
					        create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller', None, ['Inverted Dark Sanctuary Exit']),
 | 
				
			||||||
        create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']),
 | 
					        create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']),
 | 
				
			||||||
        create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
 | 
					        create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
 | 
				
			||||||
                                                      'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
 | 
					                                                      'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
 | 
				
			||||||
def create_regions(world, player):
 | 
					def create_regions(world, player):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    world.regions += [
 | 
					    world.regions += [
 | 
				
			||||||
 | 
					        create_lw_region(player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']),
 | 
				
			||||||
        create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'],
 | 
					        create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'],
 | 
				
			||||||
                         ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam',
 | 
					                         ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam',
 | 
				
			||||||
                          'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
 | 
					                          'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								Rom.py
								
								
								
								
							
							
						
						
									
										3
									
								
								Rom.py
								
								
								
								
							| 
						 | 
					@ -2054,7 +2054,8 @@ def set_inverted_mode(world, player, rom):
 | 
				
			||||||
    rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05])
 | 
					    rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def patch_shuffled_dark_sanc(world, rom, player):
 | 
					def patch_shuffled_dark_sanc(world, rom, player):
 | 
				
			||||||
    dark_sanc_entrance = str(world.get_region('Inverted Dark Sanctuary', player).entrances[0].name)
 | 
					    dark_sanc = world.get_region('Inverted Dark Sanctuary', player)
 | 
				
			||||||
 | 
					    dark_sanc_entrance = str([i for i in dark_sanc.entrances if i.parent_region.name != 'Menu'][0].name)
 | 
				
			||||||
    room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1]
 | 
					    room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1]
 | 
				
			||||||
    door_index = door_addresses[str(dark_sanc_entrance)][0]
 | 
					    door_index = door_addresses[str(dark_sanc_entrance)][0]
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										69
									
								
								Rules.py
								
								
								
								
							
							
						
						
									
										69
									
								
								Rules.py
								
								
								
								
							| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import collections
 | 
					import collections
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import OverworldGlitchRules
 | 
					import OverworldGlitchRules
 | 
				
			||||||
from BaseClasses import RegionType, World
 | 
					from BaseClasses import RegionType, World, Entrance
 | 
				
			||||||
from Items import ItemFactory
 | 
					from Items import ItemFactory
 | 
				
			||||||
from OverworldGlitchRules import overworld_glitches_rules
 | 
					from OverworldGlitchRules import overworld_glitches_rules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,20 +10,10 @@ def set_rules(world, player):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if world.logic[player] == 'nologic':
 | 
					    if world.logic[player] == 'nologic':
 | 
				
			||||||
        logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
 | 
					        logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
 | 
				
			||||||
        if world.mode[player] != 'inverted':
 | 
					        world.get_region('Menu', player).can_reach_private = lambda state: True
 | 
				
			||||||
            world.get_region('Links House', player).can_reach_private = lambda state: True
 | 
					        for exit in world.get_region('Menu', player).exits:
 | 
				
			||||||
            world.get_region('Sanctuary', player).can_reach_private = lambda state: True
 | 
					            exit.hide_path = True
 | 
				
			||||||
            old_rule = world.get_region('Old Man House', player).can_reach
 | 
					        return
 | 
				
			||||||
            world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            world.get_region('Inverted Links House', player).can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
            world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
            if world.shuffle[player] != 'vanilla':
 | 
					 | 
				
			||||||
                old_rule = world.get_region('Old Man House', player).can_reach
 | 
					 | 
				
			||||||
                world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state)
 | 
					 | 
				
			||||||
            world.get_region('Hyrule Castle Ledge', player).can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    global_rules(world, player)
 | 
					    global_rules(world, player)
 | 
				
			||||||
    if world.mode[player] != 'inverted':
 | 
					    if world.mode[player] != 'inverted':
 | 
				
			||||||
| 
						 | 
					@ -148,9 +138,12 @@ def global_rules(world, player):
 | 
				
			||||||
    # ganon can only carry triforce
 | 
					    # ganon can only carry triforce
 | 
				
			||||||
    add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)
 | 
					    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!
 | 
					    # determines which S&Q locations are available - hide from paths since it isn't an in-game location
 | 
				
			||||||
    old_rule = world.get_region('Old Man House', player).can_reach_private
 | 
					    world.get_region('Menu', player).can_reach_private = lambda state: True
 | 
				
			||||||
    world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state)
 | 
					    for exit in world.get_region('Menu', player).exits:
 | 
				
			||||||
 | 
					        exit.hide_path = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.can_reach('Old Man', 'Location', player))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
 | 
					    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('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player))
 | 
				
			||||||
| 
						 | 
					@ -206,7 +199,7 @@ def global_rules(world, player):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # logic patch to prevent placing a crystal in Desert that's required to reach the required keys
 | 
					    # logic patch to prevent placing a crystal in Desert that's required to reach the required keys
 | 
				
			||||||
    if not (world.keyshuffle[player] and world.bigkeyshuffle[player]):
 | 
					    if not (world.keyshuffle[player] and world.bigkeyshuffle[player]):
 | 
				
			||||||
        add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.world.get_region('Desert Palace Main (Outer)', 1).can_reach(state))
 | 
					        add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.world.get_region('Desert Palace Main (Outer)', player).can_reach(state))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state.has_key('Small Key (Tower of Hera)', player) or item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))
 | 
					    set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state.has_key('Small Key (Tower of Hera)', player) or item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))
 | 
				
			||||||
    set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
 | 
					    set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
 | 
				
			||||||
| 
						 | 
					@ -382,15 +375,6 @@ def global_rules(world, player):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def default_rules(world, player):
 | 
					def default_rules(world, player):
 | 
				
			||||||
    if world.mode[player] == 'standard':
 | 
					 | 
				
			||||||
        world.get_region('Hyrule Castle Secret Entrance', player).can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
        old_rule = world.get_region('Links House', player).can_reach_private
 | 
					 | 
				
			||||||
        world.get_region('Links House', player).can_reach_private = lambda state: state.can_reach('Sanctuary', 'Region', player) or old_rule(state)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        # these are default save&quit points and always accessible
 | 
					 | 
				
			||||||
        world.get_region('Links House', player).can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
        world.get_region('Sanctuary', player).can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # overworld requirements
 | 
					    # overworld requirements
 | 
				
			||||||
    set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player))
 | 
					    set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player))
 | 
				
			||||||
    set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player))
 | 
					    set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player))
 | 
				
			||||||
| 
						 | 
					@ -501,16 +485,8 @@ def default_rules(world, player):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def inverted_rules(world, player):
 | 
					def inverted_rules(world, player):
 | 
				
			||||||
    # s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached
 | 
					    # s&q regions.
 | 
				
			||||||
    world.get_region('Inverted Links House', player).can_reach_private = lambda state: True
 | 
					    set_rule(world.get_entrance('Castle Ledge S&Q', player), lambda state: state.has_Mirror(player) and state.has('Beat Agahnim 1', player))
 | 
				
			||||||
    world.get_region('Inverted Links House', player).entrances[0].can_reach = lambda state: True
 | 
					 | 
				
			||||||
    world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach_private = lambda state: True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    old_rule_old_man = world.get_region('Old Man House', player).can_reach_private
 | 
					 | 
				
			||||||
    world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule_old_man(state)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    old_rule_castle_ledge = world.get_region('Hyrule Castle Ledge', player).can_reach_private
 | 
					 | 
				
			||||||
    world.get_region('Hyrule Castle Ledge', player).can_reach_private = lambda state: (state.has_Mirror(player) and state.has('Beat Agahnim 1', player) and state.can_reach_light_world(player)) or old_rule_castle_ledge(state)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # overworld requirements 
 | 
					    # overworld requirements 
 | 
				
			||||||
    set_rule(world.get_location('Maze Race', player), lambda state: state.has_Pearl(player))
 | 
					    set_rule(world.get_location('Maze Race', player), lambda state: state.has_Pearl(player))
 | 
				
			||||||
| 
						 | 
					@ -805,9 +781,20 @@ def swordless_rules(world, 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('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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):
 | 
					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('Hyrule Castle Exit (East)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
 | 
					    set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
 | 
				
			||||||
    set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
 | 
					    set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
 | 
				
			||||||
 | 
					    set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
 | 
				
			||||||
 | 
					    set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_trock_key_rules(world, player):
 | 
					def set_trock_key_rules(world, player):
 | 
				
			||||||
| 
						 | 
					@ -1033,7 +1020,7 @@ def set_big_bomb_rules(world, player):
 | 
				
			||||||
    # the basic routes assume you can reach eastern light world with the bomb.
 | 
					    # the basic routes assume you can reach eastern light world with the bomb.
 | 
				
			||||||
    # you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp
 | 
					    # you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp
 | 
				
			||||||
    def basic_routes(state):
 | 
					    def basic_routes(state):
 | 
				
			||||||
        return southern_teleporter(state) or state.can_reach('Top of Pyramid', 'Entrance', player)
 | 
					        return southern_teleporter(state) or state.has('Beat Agahnim 1', player)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Key for below abbreviations:
 | 
					    # Key for below abbreviations:
 | 
				
			||||||
    # P = pearl
 | 
					    # P = pearl
 | 
				
			||||||
| 
						 | 
					@ -1066,7 +1053,7 @@ def set_big_bomb_rules(world, player):
 | 
				
			||||||
        #1. Mirror and enter via gate: Need mirror and Aga1
 | 
					        #1. Mirror and enter via gate: Need mirror and Aga1
 | 
				
			||||||
        #2. cross peg bridge: Need hammer and moon pearl
 | 
					        #2. cross peg bridge: Need hammer and moon pearl
 | 
				
			||||||
        # -> CPB or (M and A)
 | 
					        # -> CPB or (M and A)
 | 
				
			||||||
        add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.can_reach('Top of Pyramid', 'Entrance', player)))
 | 
					        add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player)))
 | 
				
			||||||
    elif bombshop_entrance.name in Isolated_DW_entrances:
 | 
					    elif bombshop_entrance.name in Isolated_DW_entrances:
 | 
				
			||||||
        # 1. mirror then flute then basic routes
 | 
					        # 1. mirror then flute then basic routes
 | 
				
			||||||
        # -> M and Flute and BR
 | 
					        # -> M and Flute and BR
 | 
				
			||||||
| 
						 | 
					@ -1335,7 +1322,7 @@ def set_bunny_rules(world: World, player: int, inverted: bool):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def path_to_access_rule(path, entrance):
 | 
					    def path_to_access_rule(path, entrance):
 | 
				
			||||||
        return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
 | 
					        return lambda state: state.can_reach(entrance.name, 'Entrance', entrance.player) and all(rule(state) for rule in path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def options_to_access_rule(options):
 | 
					    def options_to_access_rule(options):
 | 
				
			||||||
        return lambda state: any(rule(state) for rule in options)
 | 
					        return lambda state: any(rule(state) for rule in options)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,8 @@ class TestDungeon(unittest.TestCase):
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.world = World(1, {1:'vanilla'}, {1:'noglitches'}, {1:'open'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced',  {1:'items'},
 | 
					        self.world = World(1, {1:'vanilla'}, {1:'noglitches'}, {1:'open'}, {1:'random'}, {1:'normal'}, {1:'normal'}, {1:False}, {1:'on'}, {1:'ganon'}, 'balanced',  {1:'items'},
 | 
				
			||||||
                           True,  {1:False}, False, None,  {1:False})
 | 
					                           True,  {1:False}, False, None,  {1:False})
 | 
				
			||||||
        self.starting_regions = []
 | 
					        self.starting_regions = []  # Where to start exploring
 | 
				
			||||||
 | 
					        self.remove_exits = []      # Block dungeon exits
 | 
				
			||||||
        self.world.difficulty_requirements[1] = difficulties['normal']
 | 
					        self.world.difficulty_requirements[1] = difficulties['normal']
 | 
				
			||||||
        create_regions(self.world, 1)
 | 
					        create_regions(self.world, 1)
 | 
				
			||||||
        create_dungeons(self.world, 1)
 | 
					        create_dungeons(self.world, 1)
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,7 @@ class TestDungeon(unittest.TestCase):
 | 
				
			||||||
        for exitname, regionname in mandatory_connections:
 | 
					        for exitname, regionname in mandatory_connections:
 | 
				
			||||||
            connect_simple(self.world, exitname, regionname, 1)
 | 
					            connect_simple(self.world, exitname, regionname, 1)
 | 
				
			||||||
        connect_simple(self.world, 'Big Bomb Shop', 'Big Bomb Shop', 1)
 | 
					        connect_simple(self.world, 'Big Bomb Shop', 'Big Bomb Shop', 1)
 | 
				
			||||||
 | 
					        self.world.get_region('Menu', 1).exits = []
 | 
				
			||||||
        self.world.swamp_patch_required[1] = True
 | 
					        self.world.swamp_patch_required[1] = True
 | 
				
			||||||
        set_rules(self.world, 1)
 | 
					        set_rules(self.world, 1)
 | 
				
			||||||
        generate_itempool(self.world, 1)
 | 
					        generate_itempool(self.world, 1)
 | 
				
			||||||
| 
						 | 
					@ -28,8 +30,8 @@ class TestDungeon(unittest.TestCase):
 | 
				
			||||||
        self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
 | 
					        self.world.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run_tests(self, access_pool):
 | 
					    def run_tests(self, access_pool):
 | 
				
			||||||
        for region in self.starting_regions:
 | 
					        for exit in self.remove_exits:
 | 
				
			||||||
            self.world.get_region(region, 1).can_reach_private = lambda _: True
 | 
					            self.world.get_entrance(exit, 1).connected_region = self.world.get_region('Menu', 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for location, access, *item_pool in access_pool:
 | 
					        for location, access, *item_pool in access_pool:
 | 
				
			||||||
            items = item_pool[0]
 | 
					            items = item_pool[0]
 | 
				
			||||||
| 
						 | 
					@ -42,6 +44,14 @@ class TestDungeon(unittest.TestCase):
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    items = ItemFactory(items, 1)
 | 
					                    items = ItemFactory(items, 1)
 | 
				
			||||||
                state = CollectionState(self.world)
 | 
					                state = CollectionState(self.world)
 | 
				
			||||||
 | 
					                state.reachable_regions[1].add(self.world.get_region('Menu', 1))
 | 
				
			||||||
 | 
					                for region_name in self.starting_regions:
 | 
				
			||||||
 | 
					                    region = self.world.get_region(region_name, 1)
 | 
				
			||||||
 | 
					                    state.reachable_regions[1].add(region)
 | 
				
			||||||
 | 
					                    for exit in region.exits:
 | 
				
			||||||
 | 
					                        if exit.connected_region is not None:
 | 
				
			||||||
 | 
					                            state.blocked_connections[1].add(exit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for item in items:
 | 
					                for item in items:
 | 
				
			||||||
                    item.advancement = True
 | 
					                    item.advancement = True
 | 
				
			||||||
                    state.collect(item)
 | 
					                    state.collect(item)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,6 +42,7 @@ class TestSkullWoods(TestDungeon):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def testSkullWoodsLeftOnly(self):
 | 
					    def testSkullWoodsLeftOnly(self):
 | 
				
			||||||
        self.starting_regions = ['Skull Woods First Section (Left)']
 | 
					        self.starting_regions = ['Skull Woods First Section (Left)']
 | 
				
			||||||
 | 
					        self.remove_exits = ['Skull Woods First Section Exit']
 | 
				
			||||||
        self.run_tests([
 | 
					        self.run_tests([
 | 
				
			||||||
            ["Skull Woods - Big Chest", False, []],
 | 
					            ["Skull Woods - Big Chest", False, []],
 | 
				
			||||||
            ["Skull Woods - Big Chest", False, [], ['Never in logic']],
 | 
					            ["Skull Woods - Big Chest", False, [], ['Never in logic']],
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,7 @@ class TestSkullWoods(TestDungeon):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def testSkullWoodsBackOnly(self):
 | 
					    def testSkullWoodsBackOnly(self):
 | 
				
			||||||
        self.starting_regions = ['Skull Woods First Section (Top)']
 | 
					        self.starting_regions = ['Skull Woods First Section (Top)']
 | 
				
			||||||
 | 
					        self.remove_exits = ['Skull Woods First Section Exit']
 | 
				
			||||||
        self.run_tests([
 | 
					        self.run_tests([
 | 
				
			||||||
            ["Skull Woods - Big Chest", False, []],
 | 
					            ["Skull Woods - Big Chest", False, []],
 | 
				
			||||||
            ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
 | 
					            ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
 | 
				
			||||||
| 
						 | 
					@ -81,6 +83,7 @@ class TestSkullWoods(TestDungeon):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def testSkullWoodsMiddle(self):
 | 
					    def testSkullWoodsMiddle(self):
 | 
				
			||||||
        self.starting_regions = ['Skull Woods Second Section']
 | 
					        self.starting_regions = ['Skull Woods Second Section']
 | 
				
			||||||
 | 
					        self.remove_exits = ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']
 | 
				
			||||||
        self.run_tests([["Skull Woods - Big Key Chest", True, []]])
 | 
					        self.run_tests([["Skull Woods - Big Key Chest", True, []]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def testSkullWoodsBack(self):
 | 
					    def testSkullWoodsBack(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue