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
|
||||
import logging
|
||||
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 typing import Union
|
||||
|
||||
|
@ -396,6 +396,7 @@ class CollectionState(object):
|
|||
self.prog_items = Counter()
|
||||
self.world = parent
|
||||
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.path = {}
|
||||
self.locations_checked = set()
|
||||
|
@ -404,24 +405,46 @@ class CollectionState(object):
|
|||
self.collect(item, True)
|
||||
|
||||
def update_reachable_regions(self, player: int):
|
||||
player_regions = self.world.get_regions(player)
|
||||
self.stale[player] = False
|
||||
rrp = self.reachable_regions[player]
|
||||
new_regions = True
|
||||
reachable_regions_count = len(rrp)
|
||||
while new_regions:
|
||||
player_regions = [region for region in player_regions if region not in rrp]
|
||||
for candidate in player_regions:
|
||||
if candidate.can_reach_private(self):
|
||||
rrp.add(candidate)
|
||||
new_regions = len(rrp) > reachable_regions_count
|
||||
reachable_regions_count = len(rrp)
|
||||
bc = self.blocked_connections[player]
|
||||
queue = deque(self.blocked_connections[player])
|
||||
start = self.world.get_region('Menu', player)
|
||||
|
||||
# init on first call - this can't be done on construction since the regions don't exist yet
|
||||
if not start in rrp:
|
||||
rrp.add(start)
|
||||
bc.update(start.exits)
|
||||
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:
|
||||
ret = CollectionState(self.world)
|
||||
ret.prog_items = self.prog_items.copy()
|
||||
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in
|
||||
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.path = copy.copy(self.path)
|
||||
ret.locations_checked = copy.copy(self.locations_checked)
|
||||
|
@ -737,6 +760,7 @@ class CollectionState(object):
|
|||
del (self.prog_items[to_remove, item.player])
|
||||
# invalidate caches, nothing can be trusted anymore now
|
||||
self.reachable_regions[item.player] = set()
|
||||
self.blocked_connections[item.player] = set()
|
||||
self.stale[item.player] = True
|
||||
|
||||
@unique
|
||||
|
@ -814,10 +838,11 @@ class Entrance(object):
|
|||
self.vanilla = None
|
||||
self.access_rule = lambda state: True
|
||||
self.player = player
|
||||
self.hide_path = False
|
||||
|
||||
def can_reach(self, 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)))
|
||||
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_door = random.choice(sanc_doors)
|
||||
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)',
|
||||
'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_door = random.choice(sanc_doors)
|
||||
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
|
||||
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_door = random.choice(sanc_doors)
|
||||
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
|
||||
# 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_door = random.choice(sanc_doors)
|
||||
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
|
||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||
|
@ -1674,7 +1679,8 @@ def link_inverted_entrances(world, player):
|
|||
sanc_door = random.choice(sanc_doors)
|
||||
entrances.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
|
||||
def extract_reachable_exit(cavelist):
|
||||
|
@ -2810,7 +2816,10 @@ Isolated_LH_Doors = ['Kings Grave',
|
|||
'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
|
||||
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'),
|
||||
('Zoras River', 'Zoras River'),
|
||||
('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')
|
||||
]
|
||||
|
||||
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 Warp', 'Northeast 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 Big Bomb Shop', 'Inverted Big Bomb Shop'),
|
||||
('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'),
|
||||
('Inverted Dark Sanctuary Exit', 'West Dark World'),
|
||||
('Old Man Cave (West)', 'Bumper Cave'),
|
||||
('Old Man Cave (East)', 'Death Mountain Return Cave'),
|
||||
('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'),
|
||||
('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:
|
||||
# Key=Name
|
||||
# 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):
|
||||
|
||||
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'],
|
||||
["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',
|
||||
|
@ -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, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
|
||||
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_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)']),
|
||||
|
|
|
@ -5,6 +5,7 @@ from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
|
|||
def create_regions(world, player):
|
||||
|
||||
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'],
|
||||
["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',
|
||||
|
|
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])
|
||||
|
||||
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]
|
||||
door_index = door_addresses[str(dark_sanc_entrance)][0]
|
||||
|
||||
|
|
69
Rules.py
69
Rules.py
|
@ -1,7 +1,7 @@
|
|||
import collections
|
||||
import logging
|
||||
import OverworldGlitchRules
|
||||
from BaseClasses import RegionType, World
|
||||
from BaseClasses import RegionType, World, Entrance
|
||||
from Items import ItemFactory
|
||||
from OverworldGlitchRules import overworld_glitches_rules
|
||||
|
||||
|
@ -10,20 +10,10 @@ 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!')
|
||||
if world.mode[player] != 'inverted':
|
||||
world.get_region('Links House', player).can_reach_private = lambda state: True
|
||||
world.get_region('Sanctuary', player).can_reach_private = lambda state: True
|
||||
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)
|
||||
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
|
||||
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)
|
||||
if world.mode[player] != 'inverted':
|
||||
|
@ -148,9 +138,12 @@ 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!
|
||||
old_rule = 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(state)
|
||||
# determines which S&Q locations are available - hide from paths since it isn't an in-game location
|
||||
world.get_region('Menu', player).can_reach_private = lambda state: True
|
||||
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('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
|
||||
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 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):
|
||||
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
|
||||
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))
|
||||
|
@ -501,16 +485,8 @@ def default_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
|
||||
world.get_region('Inverted Links House', player).can_reach_private = lambda state: True
|
||||
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)
|
||||
# s&q regions.
|
||||
set_rule(world.get_entrance('Castle Ledge S&Q', player), lambda state: state.has_Mirror(player) and state.has('Beat Agahnim 1', player))
|
||||
|
||||
# overworld requirements
|
||||
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))
|
||||
|
||||
|
||||
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('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('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):
|
||||
|
@ -1033,7 +1020,7 @@ def set_big_bomb_rules(world, player):
|
|||
# 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
|
||||
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:
|
||||
# P = pearl
|
||||
|
@ -1066,7 +1053,7 @@ def set_big_bomb_rules(world, player):
|
|||
#1. Mirror and enter via gate: Need mirror and Aga1
|
||||
#2. cross peg bridge: Need hammer and moon pearl
|
||||
# -> 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:
|
||||
# 1. mirror then flute then basic routes
|
||||
# -> 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):
|
||||
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):
|
||||
return lambda state: any(rule(state) for rule in options)
|
||||
|
|
|
@ -13,7 +13,8 @@ class TestDungeon(unittest.TestCase):
|
|||
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'},
|
||||
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']
|
||||
create_regions(self.world, 1)
|
||||
create_dungeons(self.world, 1)
|
||||
|
@ -21,6 +22,7 @@ class TestDungeon(unittest.TestCase):
|
|||
for exitname, regionname in mandatory_connections:
|
||||
connect_simple(self.world, exitname, regionname, 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
|
||||
set_rules(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))
|
||||
|
||||
def run_tests(self, access_pool):
|
||||
for region in self.starting_regions:
|
||||
self.world.get_region(region, 1).can_reach_private = lambda _: True
|
||||
for exit in self.remove_exits:
|
||||
self.world.get_entrance(exit, 1).connected_region = self.world.get_region('Menu', 1)
|
||||
|
||||
for location, access, *item_pool in access_pool:
|
||||
items = item_pool[0]
|
||||
|
@ -42,6 +44,14 @@ class TestDungeon(unittest.TestCase):
|
|||
else:
|
||||
items = ItemFactory(items, 1)
|
||||
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:
|
||||
item.advancement = True
|
||||
state.collect(item)
|
||||
|
|
|
@ -42,6 +42,7 @@ class TestSkullWoods(TestDungeon):
|
|||
|
||||
def testSkullWoodsLeftOnly(self):
|
||||
self.starting_regions = ['Skull Woods First Section (Left)']
|
||||
self.remove_exits = ['Skull Woods First Section Exit']
|
||||
self.run_tests([
|
||||
["Skull Woods - Big Chest", False, []],
|
||||
["Skull Woods - Big Chest", False, [], ['Never in logic']],
|
||||
|
@ -59,6 +60,7 @@ class TestSkullWoods(TestDungeon):
|
|||
|
||||
def testSkullWoodsBackOnly(self):
|
||||
self.starting_regions = ['Skull Woods First Section (Top)']
|
||||
self.remove_exits = ['Skull Woods First Section Exit']
|
||||
self.run_tests([
|
||||
["Skull Woods - Big Chest", False, []],
|
||||
["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
|
||||
|
@ -81,6 +83,7 @@ class TestSkullWoods(TestDungeon):
|
|||
|
||||
def testSkullWoodsMiddle(self):
|
||||
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, []]])
|
||||
|
||||
def testSkullWoodsBack(self):
|
||||
|
|
Loading…
Reference in New Issue