Merge branch 'DoorPerf' of https://github.com/compiling/ALttPEntranceRandomizer into compiling-DoorPerf
This commit is contained in:
270
BaseClasses.py
270
BaseClasses.py
@@ -1,12 +1,18 @@
|
||||
import copy
|
||||
from enum import Enum, unique, Flag
|
||||
import logging
|
||||
import json
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
import logging
|
||||
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
|
||||
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
from EntranceShuffle import door_addresses
|
||||
from _vendor.collections_extended import bag
|
||||
from EntranceShuffle import door_addresses, indirect_connections
|
||||
from Utils import int16_as_bytes
|
||||
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
||||
from RoomData import Room
|
||||
@@ -126,6 +132,12 @@ class World(object):
|
||||
for region in regions if regions else self.regions:
|
||||
region.world = self
|
||||
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):
|
||||
return self.regions if player is None else self._region_cache[player].values()
|
||||
@@ -235,41 +247,41 @@ class World(object):
|
||||
if ret.has('Golden Sword', item.player):
|
||||
pass
|
||||
elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 4:
|
||||
ret.prog_items.add(('Golden Sword', item.player))
|
||||
ret.prog_items['Golden Sword', item.player] += 1
|
||||
elif ret.has('Master Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 3:
|
||||
ret.prog_items.add(('Tempered Sword', item.player))
|
||||
ret.prog_items['Tempered Sword', item.player] += 1
|
||||
elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||
ret.prog_items.add(('Master Sword', item.player))
|
||||
ret.prog_items['Master Sword', item.player] += 1
|
||||
elif self.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||
ret.prog_items.add(('Fighter Sword', item.player))
|
||||
ret.prog_items['Fighter Sword', item.player] += 1
|
||||
elif 'Glove' in item.name:
|
||||
if ret.has('Titans Mitts', item.player):
|
||||
pass
|
||||
elif ret.has('Power Glove', item.player):
|
||||
ret.prog_items.add(('Titans Mitts', item.player))
|
||||
ret.prog_items['Titans Mitts', item.player] += 1
|
||||
else:
|
||||
ret.prog_items.add(('Power Glove', item.player))
|
||||
ret.prog_items['Power Glove', item.player] += 1
|
||||
elif 'Shield' in item.name:
|
||||
if ret.has('Mirror Shield', item.player):
|
||||
pass
|
||||
elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||
ret.prog_items.add(('Mirror Shield', item.player))
|
||||
ret.prog_items['Mirror Shield', item.player] += 1
|
||||
elif ret.has('Blue Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||
ret.prog_items.add(('Red Shield', item.player))
|
||||
ret.prog_items['Red Shield', item.player] += 1
|
||||
elif self.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||
ret.prog_items.add(('Blue Shield', item.player))
|
||||
ret.prog_items['Blue Shield', item.player] += 1
|
||||
elif 'Bow' in item.name:
|
||||
if ret.has('Silver Arrows', item.player):
|
||||
pass
|
||||
elif ret.has('Bow', item.player) and self.difficulty_requirements[item.player].progressive_bow_limit >= 2:
|
||||
ret.prog_items.add(('Silver Arrows', item.player))
|
||||
ret.prog_items['Silver Arrows', item.player] += 1
|
||||
elif self.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||
ret.prog_items.add(('Bow', item.player))
|
||||
ret.prog_items['Bow', item.player] += 1
|
||||
elif item.name.startswith('Bottle'):
|
||||
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||
ret.prog_items.add((item.name, item.player))
|
||||
ret.prog_items[item.name, item.player] += 1
|
||||
elif item.advancement or item.smallkey or item.bigkey:
|
||||
ret.prog_items.add((item.name, item.player))
|
||||
ret.prog_items[item.name, item.player] += 1
|
||||
|
||||
for item in self.itempool:
|
||||
soft_collect(item)
|
||||
@@ -384,7 +396,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]
|
||||
|
||||
while prog_locations:
|
||||
state.sweep_for_crystal_access()
|
||||
sphere = []
|
||||
# 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:
|
||||
@@ -408,11 +419,10 @@ class World(object):
|
||||
class CollectionState(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.prog_items = bag()
|
||||
self.prog_items = Counter()
|
||||
self.world = parent
|
||||
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.colored_regions = {player: {} for player in range(1, parent.players + 1)}
|
||||
self.blocked_color_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.blocked_connections = {player: dict() for player in range(1, parent.players + 1)}
|
||||
self.events = []
|
||||
self.path = {}
|
||||
self.locations_checked = set()
|
||||
@@ -421,88 +431,71 @@ class CollectionState(object):
|
||||
self.collect(item, True)
|
||||
|
||||
def update_reachable_regions(self, player):
|
||||
player_regions = self.world.get_regions(player)
|
||||
self.stale[player] = False
|
||||
rrp = self.reachable_regions[player]
|
||||
ccr = self.colored_regions[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)
|
||||
bc = self.blocked_connections[player]
|
||||
|
||||
def spread_crystal_access(self, region, crystal, rrp, ccr, player):
|
||||
queue = deque([(region, crystal)])
|
||||
visited = set()
|
||||
updated = False
|
||||
while len(queue) > 0:
|
||||
region, crystal = queue.popleft()
|
||||
visited.add(region)
|
||||
for ext in region.exits:
|
||||
connect = ext.connected_region
|
||||
if connect is not None and connect.type == RegionType.Dungeon:
|
||||
if connect not in visited and connect in rrp and connect in ccr:
|
||||
if ext.can_reach(self):
|
||||
door = self.world.check_for_door(ext.name, player)
|
||||
# init on first call - this can't be done on construction since the regions don't exist yet
|
||||
start = self.world.get_region('Menu', player)
|
||||
if not start in rrp:
|
||||
rrp[start] = CrystalBarrier.Orange
|
||||
for exit in start.exits:
|
||||
bc[exit] = CrystalBarrier.Orange
|
||||
|
||||
queue = deque(self.blocked_connections[player].items())
|
||||
|
||||
# run BFS on all connections, and keep track of those blocked by missing items
|
||||
while True:
|
||||
try:
|
||||
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:
|
||||
current_crystal = ccr[connect]
|
||||
new_crystal = current_crystal | (crystal & (door.crystal or CrystalBarrier.Either))
|
||||
if current_crystal != new_crystal:
|
||||
updated = True
|
||||
ccr[connect] = new_crystal
|
||||
queue.append((connect, new_crystal))
|
||||
return updated
|
||||
door_crystal_state = new_crystal_state & (door.crystal or CrystalBarrier.Either)
|
||||
bc[exit] = door_crystal_state
|
||||
queue.append((exit, door_crystal_state))
|
||||
elif door is None:
|
||||
queue.append((exit, new_crystal_state))
|
||||
else:
|
||||
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):
|
||||
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.colored_regions = {player: copy.copy(self.colored_regions[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.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)
|
||||
@@ -523,19 +516,6 @@ class CollectionState(object):
|
||||
|
||||
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):
|
||||
# this may need improvement
|
||||
@@ -554,18 +534,13 @@ class CollectionState(object):
|
||||
self.collect(event.item, True, event)
|
||||
new_locations = len(reachable_events) > checked_locations
|
||||
checked_locations = len(reachable_events)
|
||||
if new_locations:
|
||||
self.sweep_for_crystal_access()
|
||||
|
||||
|
||||
def can_reach_blue(self, region, player):
|
||||
if region not in self.colored_regions[player].keys():
|
||||
return False
|
||||
return self.colored_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
|
||||
return region in self.reachable_regions[player] and self.reachable_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
|
||||
|
||||
def can_reach_orange(self, region, player):
|
||||
if region not in self.colored_regions[player].keys():
|
||||
return False
|
||||
return self.colored_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
|
||||
return region in self.reachable_regions[player] and self.reachable_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
|
||||
|
||||
def _do_not_flood_the_keys(self, reachable_events):
|
||||
adjusted_checks = list(reachable_events)
|
||||
@@ -584,14 +559,14 @@ class CollectionState(object):
|
||||
def has(self, item, player, count=1):
|
||||
if count == 1:
|
||||
return (item, player) in self.prog_items
|
||||
return self.prog_items.count((item, player)) >= count
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
def has_key(self, item, player, count=1):
|
||||
if self.world.retro[player]:
|
||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||
if count == 1:
|
||||
return (item, player) in self.prog_items
|
||||
return self.prog_items.count((item, player)) >= count
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
def can_buy_unlimited(self, item, player):
|
||||
for shop in self.world.shops:
|
||||
@@ -600,7 +575,7 @@ class CollectionState(object):
|
||||
return False
|
||||
|
||||
def item_count(self, item, player):
|
||||
return self.prog_items.count((item, player))
|
||||
return self.prog_items[item, player]
|
||||
|
||||
def has_crystals(self, count, player):
|
||||
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
||||
@@ -634,9 +609,9 @@ class CollectionState(object):
|
||||
|
||||
def can_extend_magic(self, player, smallmagic=16, fullrefill=False): #This reflects the total magic Link has, not the total extra he has.
|
||||
basemagic = 8
|
||||
if self.has('Quarter Magic', player):
|
||||
if self.has('Magic Upgrade (1/4)', player):
|
||||
basemagic = 32
|
||||
elif self.has('Half Magic', player):
|
||||
elif self.has('Magic Upgrade (1/2)', player):
|
||||
basemagic = 16
|
||||
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
|
||||
if self.world.difficulty_adjustments[player] == 'hard' and not fullrefill:
|
||||
@@ -657,9 +632,8 @@ class CollectionState(object):
|
||||
|
||||
def can_shoot_arrows(self, player):
|
||||
if self.world.retro[player]:
|
||||
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location)
|
||||
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe
|
||||
return self.has('Bow', player) and (self.has('Silver Arrows', player) or self.can_buy_unlimited('Single Arrow', player))
|
||||
#todo: Non-progressive silvers grant wooden arrows, but progressive bows do not. Always require shop arrows to be safe
|
||||
return self.has('Bow', player) and self.can_buy_unlimited('Single Arrow', player)
|
||||
return self.has('Bow', player)
|
||||
|
||||
def can_get_good_bee(self, player):
|
||||
@@ -734,63 +708,63 @@ class CollectionState(object):
|
||||
if self.has('Golden Sword', item.player):
|
||||
pass
|
||||
elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 4:
|
||||
self.prog_items.add(('Golden Sword', item.player))
|
||||
self.prog_items['Golden Sword', item.player] += 1
|
||||
changed = True
|
||||
elif self.has('Master Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 3:
|
||||
self.prog_items.add(('Tempered Sword', item.player))
|
||||
self.prog_items['Tempered Sword', item.player] += 1
|
||||
changed = True
|
||||
elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||
self.prog_items.add(('Master Sword', item.player))
|
||||
self.prog_items['Master Sword', item.player] += 1
|
||||
changed = True
|
||||
elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||
self.prog_items.add(('Fighter Sword', item.player))
|
||||
self.prog_items['Fighter Sword', item.player] += 1
|
||||
changed = True
|
||||
elif 'Glove' in item.name:
|
||||
if self.has('Titans Mitts', item.player):
|
||||
pass
|
||||
elif self.has('Power Glove', item.player):
|
||||
self.prog_items.add(('Titans Mitts', item.player))
|
||||
self.prog_items['Titans Mitts', item.player] += 1
|
||||
changed = True
|
||||
else:
|
||||
self.prog_items.add(('Power Glove', item.player))
|
||||
self.prog_items['Power Glove', item.player] += 1
|
||||
changed = True
|
||||
elif 'Shield' in item.name:
|
||||
if self.has('Mirror Shield', item.player):
|
||||
pass
|
||||
elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||
self.prog_items.add(('Mirror Shield', item.player))
|
||||
self.prog_items['Mirror Shield', item.player] += 1
|
||||
changed = True
|
||||
elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||
self.prog_items.add(('Red Shield', item.player))
|
||||
self.prog_items['Red Shield', item.player] += 1
|
||||
changed = True
|
||||
elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||
self.prog_items.add(('Blue Shield', item.player))
|
||||
self.prog_items['Blue Shield', item.player] += 1
|
||||
changed = True
|
||||
elif 'Bow' in item.name:
|
||||
if self.has('Silver Arrows', item.player):
|
||||
pass
|
||||
elif self.has('Bow', item.player):
|
||||
self.prog_items.add(('Silver Arrows', item.player))
|
||||
self.prog_items['Silver Arrows', item.player] += 1
|
||||
changed = True
|
||||
else:
|
||||
self.prog_items.add(('Bow', item.player))
|
||||
self.prog_items['Bow', item.player] += 1
|
||||
changed = True
|
||||
elif 'Armor' in item.name:
|
||||
if self.has('Red Mail', item.player):
|
||||
pass
|
||||
elif self.has('Blue Mail', item.player):
|
||||
self.prog_items.add(('Red Mail', item.player))
|
||||
self.prog_items['Red Mail', item.player] += 1
|
||||
changed = True
|
||||
else:
|
||||
self.prog_items.add(('Blue Mail', item.player))
|
||||
self.prog_items['Blue Mail', item.player] += 1
|
||||
changed = True
|
||||
|
||||
elif item.name.startswith('Bottle'):
|
||||
if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||
self.prog_items.add((item.name, item.player))
|
||||
self.prog_items[item.name, item.player] += 1
|
||||
changed = True
|
||||
elif event or item.advancement:
|
||||
self.prog_items.add((item.name, item.player))
|
||||
self.prog_items[item.name, item.player] += 1
|
||||
changed = True
|
||||
|
||||
self.stale[item.player] = True
|
||||
@@ -839,13 +813,13 @@ class CollectionState(object):
|
||||
to_remove = None
|
||||
|
||||
if to_remove is not None:
|
||||
try:
|
||||
self.prog_items.remove((to_remove, item.player))
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
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])
|
||||
# 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
|
||||
|
||||
def __getattr__(self, item):
|
||||
@@ -935,10 +909,11 @@ class Entrance(object):
|
||||
self.access_rule = lambda state: True
|
||||
self.player = player
|
||||
self.door = None
|
||||
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
|
||||
|
||||
@@ -1135,8 +1110,7 @@ class PolSlot(Enum):
|
||||
EastWest = 1
|
||||
Stairs = 2
|
||||
|
||||
@unique
|
||||
class CrystalBarrier(Flag):
|
||||
class CrystalBarrier(FastEnum):
|
||||
Null = 0 # no special requirement
|
||||
Blue = 1 # blue must be down and explore state set to Blue
|
||||
Orange = 2 # orange must be down and explore state set to Orange
|
||||
|
||||
12
Bosses.py
12
Bosses.py
@@ -18,6 +18,7 @@ def ArmosKnightsDefeatRule(state, player):
|
||||
# Magic amounts are probably a bit overkill
|
||||
return (
|
||||
state.has_blunt_weapon(player) or
|
||||
state.can_shoot_arrows(player) or
|
||||
(state.has('Cane of Somaria', player) and state.can_extend_magic(player, 10)) or
|
||||
(state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or
|
||||
(state.has('Ice Rod', player) and state.can_extend_magic(player, 32)) or
|
||||
@@ -26,18 +27,19 @@ def ArmosKnightsDefeatRule(state, player):
|
||||
state.has('Red Boomerang', player))
|
||||
|
||||
def LanmolasDefeatRule(state, player):
|
||||
# TODO: Allow the canes here?
|
||||
return (
|
||||
state.has_blunt_weapon(player) or
|
||||
state.has('Fire Rod', player) or
|
||||
state.has('Ice Rod', player) or
|
||||
state.has('Cane of Somaria', player) or
|
||||
state.has('Cane of Byrna', player) or
|
||||
state.can_shoot_arrows(player))
|
||||
|
||||
def MoldormDefeatRule(state, player):
|
||||
return state.has_blunt_weapon(player)
|
||||
|
||||
def HelmasaurKingDefeatRule(state, player):
|
||||
return state.has_blunt_weapon(player) or state.can_shoot_arrows(player)
|
||||
return state.has_sword(player) or state.can_shoot_arrows(player)
|
||||
|
||||
def ArrghusDefeatRule(state, player):
|
||||
if not state.has('Hookshot', player):
|
||||
@@ -95,7 +97,11 @@ def VitreousDefeatRule(state, player):
|
||||
def TrinexxDefeatRule(state, player):
|
||||
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):
|
||||
return False
|
||||
return state.has('Hammer', player) or state.has_beam_sword(player) or (state.has_sword(player) and state.can_extend_magic(player, 32))
|
||||
return (state.has('Hammer', player) or
|
||||
state.has('Golden Sword', player) or
|
||||
state.has('Tempered Sword', player) or
|
||||
(state.has('Master Sword', player) and state.can_extend_magic(player, 16)) or
|
||||
(state.has_sword(player) and state.can_extend_magic(player, 32)))
|
||||
|
||||
def AgahnimDefeatRule(state, player):
|
||||
return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player)
|
||||
|
||||
@@ -365,7 +365,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
||||
name = ' '.join(builder.name.split(' ')[:-1])
|
||||
origin_list = list(builder.entrance_list)
|
||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
||||
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, world, player):
|
||||
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
|
||||
if last_key == builder.name or loops > 1000:
|
||||
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
|
||||
raise Exception('Infinite loop detected for "%s" located at %s' % (builder.name, origin_name))
|
||||
@@ -398,7 +398,7 @@ def determine_entrance_list(world, player):
|
||||
region = world.get_region(region_name, player)
|
||||
for ent in region.entrances:
|
||||
parent = ent.parent_region
|
||||
if parent.type != RegionType.Dungeon or parent.name == 'Sewer Drop':
|
||||
if (parent.type != RegionType.Dungeon and parent.name != 'Menu') or parent.name == 'Sewer Drop':
|
||||
if parent.name not in world.inaccessible_regions[player]:
|
||||
entrance_map[key].append(region_name)
|
||||
else:
|
||||
|
||||
6
Doors.py
6
Doors.py
@@ -41,7 +41,7 @@ Intr = DoorType.Interior
|
||||
|
||||
|
||||
def create_doors(world, player):
|
||||
world.doors += [
|
||||
doors = [
|
||||
# hyrule castle
|
||||
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),
|
||||
@@ -1071,6 +1071,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 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)
|
||||
|
||||
# swamp events
|
||||
|
||||
@@ -25,7 +25,7 @@ class GraphPiece:
|
||||
|
||||
|
||||
# Dungeons shouldn't be generated until all entrances are appropriately accessible
|
||||
def pre_validate(builder, entrance_region_names, world, player):
|
||||
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
|
||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||
proposed_map = {}
|
||||
doors_to_connect = {}
|
||||
@@ -36,7 +36,7 @@ def pre_validate(builder, entrance_region_names, world, player):
|
||||
for door in sector.outstanding_doors:
|
||||
doors_to_connect[door.name] = door
|
||||
all_regions.update(sector.regions)
|
||||
bk_needed = bk_needed or determine_if_bk_needed(sector, False, world, player)
|
||||
bk_needed = bk_needed or determine_if_bk_needed(sector, split_dungeon, world, player)
|
||||
bk_special = bk_special or check_for_special(sector)
|
||||
dungeon, hangers, hooks = gen_dungeon_info(builder.name, builder.sectors, entrance_regions, proposed_map,
|
||||
doors_to_connect, bk_needed, bk_special, world, player)
|
||||
|
||||
@@ -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_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)',
|
||||
@@ -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_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)
|
||||
@@ -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_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
|
||||
@@ -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_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)
|
||||
@@ -1680,7 +1685,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):
|
||||
@@ -2298,6 +2304,8 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
||||
'Death Mountain Return Cave (East)',
|
||||
'Death Mountain Return Cave (West)',
|
||||
'Spectacle Rock Cave Peak',
|
||||
'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave (Bottom)',
|
||||
'Paradox Cave (Bottom)',
|
||||
'Paradox Cave (Middle)',
|
||||
'Paradox Cave (Top)',
|
||||
@@ -2638,8 +2646,6 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
||||
'Death Mountain Return Cave (East)',
|
||||
'Death Mountain Return Cave (West)',
|
||||
'Spectacle Rock Cave Peak',
|
||||
'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave (Bottom)',
|
||||
'Paradox Cave (Bottom)',
|
||||
'Paradox Cave (Middle)',
|
||||
'Paradox Cave (Top)',
|
||||
@@ -2814,7 +2820,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'),
|
||||
@@ -2923,7 +2932,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', 'Lake Hylia Island'),
|
||||
('Zoras River', 'Zoras River'),
|
||||
('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 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'),
|
||||
@@ -3485,11 +3499,23 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
|
||||
]
|
||||
|
||||
# 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
|
||||
# addr = (door_index, exitdata) # multiexit
|
||||
# | ([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)
|
||||
|
||||
# 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)),
|
||||
'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):
|
||||
|
||||
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',
|
||||
@@ -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, '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)']),
|
||||
|
||||
9
Main.py
9
Main.py
@@ -371,7 +371,6 @@ def copy_world(world):
|
||||
create_inverted_regions(ret, player)
|
||||
create_dungeon_regions(ret, player)
|
||||
create_shops(ret, player)
|
||||
create_doors(ret, player)
|
||||
create_rooms(ret, player)
|
||||
create_dungeons(ret, player)
|
||||
|
||||
@@ -418,6 +417,10 @@ def copy_world(world):
|
||||
ret.state.stale = {player: True for player in range(1, world.players + 1)}
|
||||
|
||||
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.rooms = world.rooms
|
||||
ret.inaccessible_regions = world.inaccessible_regions
|
||||
@@ -469,7 +472,6 @@ def create_playthrough(world):
|
||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
|
||||
while sphere_candidates:
|
||||
state.sweep_for_events(key_only=True)
|
||||
state.sweep_for_crystal_access()
|
||||
|
||||
sphere = []
|
||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||
@@ -487,7 +489,7 @@ def create_playthrough(world):
|
||||
|
||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
||||
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]):
|
||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
|
||||
else:
|
||||
@@ -531,7 +533,6 @@ def create_playthrough(world):
|
||||
collection_spheres = []
|
||||
while required_locations:
|
||||
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))
|
||||
|
||||
|
||||
@@ -4,6 +4,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
@@ -2122,7 +2122,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]
|
||||
|
||||
|
||||
72
Rules.py
72
Rules.py
@@ -1,28 +1,19 @@
|
||||
import logging
|
||||
from BaseClasses import CollectionState, RegionType, DoorType
|
||||
from collections import deque
|
||||
|
||||
from BaseClasses import CollectionState, RegionType, DoorType, Entrance
|
||||
from Regions import key_only_locations
|
||||
from RoomData import DoorKind
|
||||
from collections import deque
|
||||
|
||||
|
||||
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':
|
||||
@@ -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)
|
||||
|
||||
# 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)
|
||||
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))
|
||||
@@ -168,6 +162,13 @@ def global_rules(world, player):
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
|
||||
|
||||
# Castle Tower
|
||||
set_rule(world.get_entrance('Tower Gold Knights SW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Tower Gold Knights EN', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Tower Dark Archers WN', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
|
||||
|
||||
@@ -207,6 +208,8 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Swamp Drain Left Up Stairs', player), lambda state: state.has('Drained Swamp', player) or state.has('Flippers', player))
|
||||
set_rule(world.get_location('Swamp Palace - Flooded Room - Left', player), lambda state: state.has('Drained Swamp', player))
|
||||
set_rule(world.get_location('Swamp Palace - Flooded Room - Right', player), lambda state: state.has('Drained Swamp', player))
|
||||
set_rule(world.get_entrance('Swamp Flooded Spot Ladder', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
|
||||
set_rule(world.get_entrance('Swamp Drain Left Up Stairs', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
|
||||
set_rule(world.get_entrance('Swamp Waterway NW', player), lambda state: state.has('Flippers', player))
|
||||
set_rule(world.get_entrance('Swamp Waterway N', player), lambda state: state.has('Flippers', player))
|
||||
set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player))
|
||||
@@ -386,16 +389,6 @@ def global_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
|
||||
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))
|
||||
@@ -508,12 +501,7 @@ 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 = 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)
|
||||
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))
|
||||
@@ -826,7 +814,19 @@ std_kill_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):
|
||||
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
|
||||
if world.shuffle[player] != 'vanilla':
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
||||
@@ -1049,7 +1049,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
|
||||
@@ -1082,7 +1082,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
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
aenum
|
||||
fast-enum
|
||||
Reference in New Issue
Block a user