Replace world exploration with a faster algorithm - use BFS and keep track of all entrances that are currently blocked by progression items.
New algorithm also obsoletes sweep_for_crystal_access Set up door and entrance caches in advance Replace CrystalBarrier with FastEnum for bitfield arithmetic
This commit is contained in:
190
BaseClasses.py
190
BaseClasses.py
@@ -1,12 +1,20 @@
|
|||||||
import copy
|
import copy
|
||||||
from enum import Enum, unique, Flag
|
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from collections import OrderedDict, Counter, deque, defaultdict
|
from collections import OrderedDict, Counter, deque, defaultdict
|
||||||
|
from enum import Enum, unique
|
||||||
|
|
||||||
|
try:
|
||||||
|
from fast_enum import FastEnum
|
||||||
|
except ImportError:
|
||||||
|
from enum import Flag
|
||||||
|
FastEnum = Flag
|
||||||
|
# Bitflag logic is significantly faster when not using normal python enums.
|
||||||
|
logging.info('fast-enum module not found - falling back to slow enums. Run `pip install fast-enum` to remove this warning.')
|
||||||
|
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
from EntranceShuffle import door_addresses
|
from EntranceShuffle import door_addresses, indirect_connections
|
||||||
from _vendor.collections_extended import bag
|
|
||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes
|
||||||
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
||||||
from RoomData import Room
|
from RoomData import Room
|
||||||
@@ -126,6 +134,12 @@ class World(object):
|
|||||||
for region in regions if regions else self.regions:
|
for region in regions if regions else self.regions:
|
||||||
region.world = self
|
region.world = self
|
||||||
self._region_cache[region.player][region.name] = region
|
self._region_cache[region.player][region.name] = region
|
||||||
|
for exit in region.exits:
|
||||||
|
self._entrance_cache[(exit.name, exit.player)] = exit
|
||||||
|
|
||||||
|
def initialize_doors(self, doors):
|
||||||
|
for door in doors:
|
||||||
|
self._door_cache[(door.name, door.player)] = door
|
||||||
|
|
||||||
def get_regions(self, player=None):
|
def get_regions(self, player=None):
|
||||||
return self.regions if player is None else self._region_cache[player].values()
|
return self.regions if player is None else self._region_cache[player].values()
|
||||||
@@ -384,7 +398,6 @@ class World(object):
|
|||||||
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
|
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
|
||||||
|
|
||||||
while prog_locations:
|
while prog_locations:
|
||||||
state.sweep_for_crystal_access()
|
|
||||||
sphere = []
|
sphere = []
|
||||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
for location in prog_locations:
|
for location in prog_locations:
|
||||||
@@ -410,9 +423,8 @@ class CollectionState(object):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
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: dict() for player in range(1, parent.players + 1)}
|
||||||
self.colored_regions = {player: {} for player in range(1, parent.players + 1)}
|
self.blocked_connections = {player: dict() for player in range(1, parent.players + 1)}
|
||||||
self.blocked_color_regions = {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()
|
||||||
@@ -421,88 +433,71 @@ class CollectionState(object):
|
|||||||
self.collect(item, True)
|
self.collect(item, True)
|
||||||
|
|
||||||
def update_reachable_regions(self, player):
|
def update_reachable_regions(self, player):
|
||||||
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]
|
||||||
ccr = self.colored_regions[player]
|
bc = self.blocked_connections[player]
|
||||||
blocked = self.blocked_color_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)
|
|
||||||
if candidate.type == RegionType.Dungeon:
|
|
||||||
c_switch_present = False
|
|
||||||
for ext in candidate.exits:
|
|
||||||
door = self.world.check_for_door(ext.name, player)
|
|
||||||
if door is not None and door.crystal == CrystalBarrier.Either:
|
|
||||||
c_switch_present = True
|
|
||||||
break
|
|
||||||
if c_switch_present:
|
|
||||||
ccr[candidate] = CrystalBarrier.Either
|
|
||||||
self.spread_crystal_access(candidate, CrystalBarrier.Either, rrp, ccr, player)
|
|
||||||
for ext in candidate.exits:
|
|
||||||
connect = ext.connected_region
|
|
||||||
if connect in rrp and not ext.can_reach(self):
|
|
||||||
blocked.add(candidate)
|
|
||||||
else:
|
|
||||||
color_type = CrystalBarrier.Null
|
|
||||||
for entrance in candidate.entrances:
|
|
||||||
if entrance.parent_region in rrp:
|
|
||||||
if entrance.can_reach(self):
|
|
||||||
door = self.world.check_for_door(entrance.name, player)
|
|
||||||
if door is None or entrance.parent_region.type != RegionType.Dungeon:
|
|
||||||
color_type |= CrystalBarrier.Orange
|
|
||||||
elif entrance.parent_region in ccr.keys():
|
|
||||||
color_type |= (ccr[entrance.parent_region] & (door.crystal or CrystalBarrier.Either))
|
|
||||||
else:
|
|
||||||
blocked.add(entrance.parent_region)
|
|
||||||
if color_type:
|
|
||||||
ccr[candidate] = color_type
|
|
||||||
for ext in candidate.exits:
|
|
||||||
connect = ext.connected_region
|
|
||||||
if connect in rrp and connect in ccr:
|
|
||||||
door = self.world.check_for_door(ext.name, player)
|
|
||||||
if door is not None and not door.blocked:
|
|
||||||
if ext.can_reach(self):
|
|
||||||
new_color = ccr[connect] | (ccr[candidate] & (door.crystal or CrystalBarrier.Either))
|
|
||||||
if new_color != ccr[connect]:
|
|
||||||
self.spread_crystal_access(candidate, new_color, rrp, ccr, player)
|
|
||||||
else:
|
|
||||||
blocked.add(candidate)
|
|
||||||
new_regions = len(rrp) > reachable_regions_count
|
|
||||||
reachable_regions_count = len(rrp)
|
|
||||||
|
|
||||||
def spread_crystal_access(self, region, crystal, rrp, ccr, player):
|
# init on first call - this can't be done on construction since the regions don't exist yet
|
||||||
queue = deque([(region, crystal)])
|
start = self.world.get_region('Menu', player)
|
||||||
visited = set()
|
if not start in rrp:
|
||||||
updated = False
|
rrp[start] = CrystalBarrier.Orange
|
||||||
while len(queue) > 0:
|
for exit in start.exits:
|
||||||
region, crystal = queue.popleft()
|
bc[exit] = CrystalBarrier.Orange
|
||||||
visited.add(region)
|
|
||||||
for ext in region.exits:
|
queue = deque(self.blocked_connections[player].items())
|
||||||
connect = ext.connected_region
|
|
||||||
if connect is not None and connect.type == RegionType.Dungeon:
|
# run BFS on all connections, and keep track of those blocked by missing items
|
||||||
if connect not in visited and connect in rrp and connect in ccr:
|
while True:
|
||||||
if ext.can_reach(self):
|
try:
|
||||||
door = self.world.check_for_door(ext.name, player)
|
connection, crystal_state = queue.popleft()
|
||||||
|
new_region = connection.connected_region
|
||||||
|
if new_region is None or new_region in rrp and (new_region.type != RegionType.Dungeon or (rrp[new_region] & crystal_state) == crystal_state):
|
||||||
|
bc.pop(connection, None)
|
||||||
|
elif connection.can_reach(self):
|
||||||
|
if new_region.type == RegionType.Dungeon:
|
||||||
|
new_crystal_state = crystal_state
|
||||||
|
for exit in new_region.exits:
|
||||||
|
door = exit.door
|
||||||
|
if door is not None and door.crystal == CrystalBarrier.Either:
|
||||||
|
new_crystal_state = CrystalBarrier.Either
|
||||||
|
break
|
||||||
|
if new_region in rrp:
|
||||||
|
new_crystal_state |= rrp[new_region]
|
||||||
|
|
||||||
|
rrp[new_region] = new_crystal_state
|
||||||
|
|
||||||
|
for exit in new_region.exits:
|
||||||
|
door = exit.door
|
||||||
if door is not None and not door.blocked:
|
if door is not None and not door.blocked:
|
||||||
current_crystal = ccr[connect]
|
door_crystal_state = new_crystal_state & (door.crystal or CrystalBarrier.Either)
|
||||||
new_crystal = current_crystal | (crystal & (door.crystal or CrystalBarrier.Either))
|
bc[exit] = door_crystal_state
|
||||||
if current_crystal != new_crystal:
|
queue.append((exit, door_crystal_state))
|
||||||
updated = True
|
elif door is None:
|
||||||
ccr[connect] = new_crystal
|
queue.append((exit, new_crystal_state))
|
||||||
queue.append((connect, new_crystal))
|
else:
|
||||||
return updated
|
new_crystal_state = CrystalBarrier.Orange
|
||||||
|
rrp[new_region] = new_crystal_state
|
||||||
|
bc.pop(connection, None)
|
||||||
|
for exit in new_region.exits:
|
||||||
|
bc[exit] = new_crystal_state
|
||||||
|
queue.append((exit, new_crystal_state))
|
||||||
|
|
||||||
|
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 and new_entrance.parent_region in rrp:
|
||||||
|
queue.append((new_entrance, rrp[new_entrance.parent_region]))
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
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 range(1, self.world.players + 1)}
|
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
|
||||||
ret.colored_regions = {player: copy.copy(self.colored_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.blocked_color_regions = {player: copy.copy(self.blocked_color_regions[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)
|
||||||
@@ -523,19 +518,6 @@ class CollectionState(object):
|
|||||||
|
|
||||||
return spot.can_reach(self)
|
return spot.can_reach(self)
|
||||||
|
|
||||||
def sweep_for_crystal_access(self):
|
|
||||||
for player, rrp in self.reachable_regions.items():
|
|
||||||
updated = True
|
|
||||||
while updated:
|
|
||||||
if self.stale[player]:
|
|
||||||
self.update_reachable_regions(player)
|
|
||||||
updated = False
|
|
||||||
dungeon_regions = self.blocked_color_regions[player]
|
|
||||||
ccr = self.colored_regions[player]
|
|
||||||
for region in dungeon_regions.copy():
|
|
||||||
if region in ccr.keys():
|
|
||||||
updated |= self.spread_crystal_access(region, ccr[region], rrp, ccr, player)
|
|
||||||
self.stale[player] = updated
|
|
||||||
|
|
||||||
def sweep_for_events(self, key_only=False, locations=None):
|
def sweep_for_events(self, key_only=False, locations=None):
|
||||||
# this may need improvement
|
# this may need improvement
|
||||||
@@ -554,18 +536,13 @@ class CollectionState(object):
|
|||||||
self.collect(event.item, True, event)
|
self.collect(event.item, True, event)
|
||||||
new_locations = len(reachable_events) > checked_locations
|
new_locations = len(reachable_events) > checked_locations
|
||||||
checked_locations = len(reachable_events)
|
checked_locations = len(reachable_events)
|
||||||
if new_locations:
|
|
||||||
self.sweep_for_crystal_access()
|
|
||||||
|
|
||||||
def can_reach_blue(self, region, player):
|
def can_reach_blue(self, region, player):
|
||||||
if region not in self.colored_regions[player].keys():
|
return region in self.reachable_regions[player] and self.reachable_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
|
||||||
return False
|
|
||||||
return self.colored_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
|
|
||||||
|
|
||||||
def can_reach_orange(self, region, player):
|
def can_reach_orange(self, region, player):
|
||||||
if region not in self.colored_regions[player].keys():
|
return region in self.reachable_regions[player] and self.reachable_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
|
||||||
return False
|
|
||||||
return self.colored_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
|
|
||||||
|
|
||||||
def _do_not_flood_the_keys(self, reachable_events):
|
def _do_not_flood_the_keys(self, reachable_events):
|
||||||
adjusted_checks = list(reachable_events)
|
adjusted_checks = list(reachable_events)
|
||||||
@@ -844,7 +821,8 @@ class CollectionState(object):
|
|||||||
if self.prog_items[to_remove, item.player] < 1:
|
if self.prog_items[to_remove, item.player] < 1:
|
||||||
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] = dict()
|
||||||
|
self.blocked_connections[item.player] = set()
|
||||||
self.stale[item.player] = True
|
self.stale[item.player] = True
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
@@ -934,10 +912,11 @@ class Entrance(object):
|
|||||||
self.access_rule = lambda state: True
|
self.access_rule = lambda state: True
|
||||||
self.player = player
|
self.player = player
|
||||||
self.door = None
|
self.door = None
|
||||||
|
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
|
||||||
|
|
||||||
@@ -1134,8 +1113,7 @@ class PolSlot(Enum):
|
|||||||
EastWest = 1
|
EastWest = 1
|
||||||
Stairs = 2
|
Stairs = 2
|
||||||
|
|
||||||
@unique
|
class CrystalBarrier(FastEnum):
|
||||||
class CrystalBarrier(Flag):
|
|
||||||
Null = 0 # no special requirement
|
Null = 0 # no special requirement
|
||||||
Blue = 1 # blue must be down and explore state set to Blue
|
Blue = 1 # blue must be down and explore state set to Blue
|
||||||
Orange = 2 # orange must be down and explore state set to Orange
|
Orange = 2 # orange must be down and explore state set to Orange
|
||||||
|
|||||||
6
Doors.py
6
Doors.py
@@ -41,7 +41,7 @@ Intr = DoorType.Interior
|
|||||||
|
|
||||||
|
|
||||||
def create_doors(world, player):
|
def create_doors(world, player):
|
||||||
world.doors += [
|
doors = [
|
||||||
# hyrule castle
|
# hyrule castle
|
||||||
create_door(player, 'Hyrule Castle Lobby W', Nrml).dir(We, 0x61, Mid, High).toggler().pos(0),
|
create_door(player, 'Hyrule Castle Lobby W', Nrml).dir(We, 0x61, Mid, High).toggler().pos(0),
|
||||||
create_door(player, 'Hyrule Castle Lobby E', Nrml).dir(Ea, 0x61, Mid, High).toggler().pos(2),
|
create_door(player, 'Hyrule Castle Lobby E', Nrml).dir(Ea, 0x61, Mid, High).toggler().pos(2),
|
||||||
@@ -1069,6 +1069,10 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0),
|
create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0),
|
||||||
create_door(player, 'GT Agahnim 2 SW', Nrml).dir(So, 0x0d, Left, High).no_exit().trap(0x4).pos(0)
|
create_door(player, 'GT Agahnim 2 SW', Nrml).dir(So, 0x0d, Left, High).no_exit().trap(0x4).pos(0)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
world.doors += doors
|
||||||
|
world.initialize_doors(doors)
|
||||||
|
|
||||||
create_paired_doors(world, player)
|
create_paired_doors(world, player)
|
||||||
|
|
||||||
# swamp events
|
# swamp events
|
||||||
|
|||||||
@@ -1204,7 +1204,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)',
|
||||||
@@ -1279,7 +1281,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)
|
||||||
@@ -1410,7 +1413,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
|
||||||
@@ -1547,7 +1551,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)
|
||||||
@@ -1680,7 +1685,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):
|
||||||
@@ -2814,7 +2820,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'),
|
||||||
@@ -2923,7 +2932,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', 'Lake Hylia Island'),
|
('Lake Hylia Island', 'Lake Hylia Island'),
|
||||||
('Zoras River', 'Zoras River'),
|
('Zoras River', 'Zoras River'),
|
||||||
('Kings Grave Outer Rocks', 'Kings Grave Area'),
|
('Kings Grave Outer Rocks', 'Kings Grave Area'),
|
||||||
@@ -3352,6 +3365,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'),
|
||||||
@@ -3485,11 +3499,23 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
|
|||||||
]
|
]
|
||||||
|
|
||||||
# format:
|
# format:
|
||||||
|
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'
|
||||||
|
}
|
||||||
# Key=Name
|
# Key=Name
|
||||||
# addr = (door_index, exitdata) # multiexit
|
# addr = (door_index, exitdata) # multiexit
|
||||||
# | ([addr], None) # holes
|
# | ([addr], None) # holes
|
||||||
# exitdata = (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)
|
# exitdata = (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)
|
||||||
|
|
||||||
|
# ToDo somehow merge this with creation of the locations
|
||||||
|
|
||||||
# ToDo somehow merge this with creation of the locations
|
# ToDo somehow merge this with creation of the locations
|
||||||
door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)),
|
door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)),
|
||||||
'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)),
|
'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)),
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -176,7 +177,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)']),
|
||||||
|
|||||||
9
Main.py
9
Main.py
@@ -370,7 +370,6 @@ def copy_world(world):
|
|||||||
create_inverted_regions(ret, player)
|
create_inverted_regions(ret, player)
|
||||||
create_dungeon_regions(ret, player)
|
create_dungeon_regions(ret, player)
|
||||||
create_shops(ret, player)
|
create_shops(ret, player)
|
||||||
create_doors(ret, player)
|
|
||||||
create_rooms(ret, player)
|
create_rooms(ret, player)
|
||||||
create_dungeons(ret, player)
|
create_dungeons(ret, player)
|
||||||
|
|
||||||
@@ -417,6 +416,10 @@ def copy_world(world):
|
|||||||
ret.state.stale = {player: True for player in range(1, world.players + 1)}
|
ret.state.stale = {player: True for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
ret.doors = world.doors
|
ret.doors = world.doors
|
||||||
|
for door in ret.doors:
|
||||||
|
entrance = ret.check_for_entrance(door.name, door.player)
|
||||||
|
if entrance is not None:
|
||||||
|
entrance.door = door
|
||||||
ret.paired_doors = world.paired_doors
|
ret.paired_doors = world.paired_doors
|
||||||
ret.rooms = world.rooms
|
ret.rooms = world.rooms
|
||||||
ret.inaccessible_regions = world.inaccessible_regions
|
ret.inaccessible_regions = world.inaccessible_regions
|
||||||
@@ -468,7 +471,6 @@ def create_playthrough(world):
|
|||||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
|
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
|
||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
state.sweep_for_events(key_only=True)
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_crystal_access()
|
|
||||||
|
|
||||||
sphere = []
|
sphere = []
|
||||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
@@ -486,7 +488,7 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
||||||
if not sphere:
|
if not sphere:
|
||||||
logging.getLogger('').debug(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
logging.getLogger('').error(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||||
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
||||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
|
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
|
||||||
else:
|
else:
|
||||||
@@ -530,7 +532,6 @@ def create_playthrough(world):
|
|||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
while required_locations:
|
while required_locations:
|
||||||
state.sweep_for_events(key_only=True)
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_crystal_access()
|
|
||||||
|
|
||||||
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,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
@@ -2121,7 +2121,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]
|
||||||
|
|
||||||
|
|||||||
57
Rules.py
57
Rules.py
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from BaseClasses import CollectionState, RegionType, DoorType
|
from BaseClasses import CollectionState, RegionType, DoorType
|
||||||
from Regions import key_only_locations
|
from Regions import key_only_locations
|
||||||
|
from Items import ItemFactory
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
@@ -9,19 +10,9 @@ 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
|
|
||||||
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
|
return
|
||||||
|
|
||||||
global_rules(world, player)
|
global_rules(world, player)
|
||||||
@@ -113,8 +104,11 @@ def global_rules(world, player):
|
|||||||
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!
|
# 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('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))
|
||||||
@@ -384,16 +378,6 @@ def global_rules(world, player):
|
|||||||
|
|
||||||
|
|
||||||
def default_rules(world, player):
|
def default_rules(world, player):
|
||||||
if world.mode[player] == 'standard':
|
|
||||||
# Links house requires reaching Sanc so skipping that chest isn't a softlock.
|
|
||||||
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.has('Zelda Delivered', 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))
|
||||||
@@ -506,12 +490,7 @@ 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. 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
|
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 = 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(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))
|
||||||
@@ -824,7 +803,19 @@ std_kill_rooms = {
|
|||||||
} # all trap rooms?
|
} # all trap rooms?
|
||||||
|
|
||||||
|
|
||||||
|
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('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))
|
||||||
# these are because of rails
|
# these are because of rails
|
||||||
if world.shuffle[player] != 'vanilla':
|
if world.shuffle[player] != 'vanilla':
|
||||||
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
@@ -1039,7 +1030,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
|
||||||
@@ -1072,7 +1063,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
|
||||||
|
|||||||
Reference in New Issue
Block a user