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
|
import copy
|
||||||
from enum import Enum, unique, Flag
|
|
||||||
import logging
|
|
||||||
import json
|
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 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 +132,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()
|
||||||
@@ -235,41 +247,41 @@ class World(object):
|
|||||||
if ret.has('Golden Sword', item.player):
|
if ret.has('Golden Sword', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 4:
|
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:
|
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:
|
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:
|
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:
|
elif 'Glove' in item.name:
|
||||||
if ret.has('Titans Mitts', item.player):
|
if ret.has('Titans Mitts', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Power Glove', item.player):
|
elif ret.has('Power Glove', item.player):
|
||||||
ret.prog_items.add(('Titans Mitts', item.player))
|
ret.prog_items['Titans Mitts', item.player] += 1
|
||||||
else:
|
else:
|
||||||
ret.prog_items.add(('Power Glove', item.player))
|
ret.prog_items['Power Glove', item.player] += 1
|
||||||
elif 'Shield' in item.name:
|
elif 'Shield' in item.name:
|
||||||
if ret.has('Mirror Shield', item.player):
|
if ret.has('Mirror Shield', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
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:
|
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:
|
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:
|
elif 'Bow' in item.name:
|
||||||
if ret.has('Silver Arrows', item.player):
|
if ret.has('Silver Arrows', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Bow', item.player) and self.difficulty_requirements[item.player].progressive_bow_limit >= 2:
|
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:
|
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'):
|
elif item.name.startswith('Bottle'):
|
||||||
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
|
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:
|
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:
|
for item in self.itempool:
|
||||||
soft_collect(item)
|
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]
|
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:
|
||||||
@@ -408,11 +419,10 @@ class World(object):
|
|||||||
class CollectionState(object):
|
class CollectionState(object):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.prog_items = bag()
|
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 +431,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 +516,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 +534,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)
|
||||||
@@ -584,14 +559,14 @@ class CollectionState(object):
|
|||||||
def has(self, item, player, count=1):
|
def has(self, item, player, count=1):
|
||||||
if count == 1:
|
if count == 1:
|
||||||
return (item, player) in self.prog_items
|
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):
|
def has_key(self, item, player, count=1):
|
||||||
if self.world.retro[player]:
|
if self.world.retro[player]:
|
||||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||||
if count == 1:
|
if count == 1:
|
||||||
return (item, player) in self.prog_items
|
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):
|
def can_buy_unlimited(self, item, player):
|
||||||
for shop in self.world.shops:
|
for shop in self.world.shops:
|
||||||
@@ -600,7 +575,7 @@ class CollectionState(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def item_count(self, item, player):
|
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):
|
def has_crystals(self, count, player):
|
||||||
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
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.
|
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
|
basemagic = 8
|
||||||
if self.has('Quarter Magic', player):
|
if self.has('Magic Upgrade (1/4)', player):
|
||||||
basemagic = 32
|
basemagic = 32
|
||||||
elif self.has('Half Magic', player):
|
elif self.has('Magic Upgrade (1/2)', player):
|
||||||
basemagic = 16
|
basemagic = 16
|
||||||
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
|
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:
|
if self.world.difficulty_adjustments[player] == 'hard' and not fullrefill:
|
||||||
@@ -657,9 +632,8 @@ class CollectionState(object):
|
|||||||
|
|
||||||
def can_shoot_arrows(self, player):
|
def can_shoot_arrows(self, player):
|
||||||
if self.world.retro[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)
|
#todo: Non-progressive silvers grant wooden arrows, but progressive bows do not. Always require shop arrows to be safe
|
||||||
#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.can_buy_unlimited('Single Arrow', player)
|
||||||
return self.has('Bow', player) and (self.has('Silver Arrows', player) or self.can_buy_unlimited('Single Arrow', player))
|
|
||||||
return self.has('Bow', player)
|
return self.has('Bow', player)
|
||||||
|
|
||||||
def can_get_good_bee(self, player):
|
def can_get_good_bee(self, player):
|
||||||
@@ -734,63 +708,63 @@ class CollectionState(object):
|
|||||||
if self.has('Golden Sword', item.player):
|
if self.has('Golden Sword', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 4:
|
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
|
changed = True
|
||||||
elif self.has('Master Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 3:
|
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
|
changed = True
|
||||||
elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
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
|
changed = True
|
||||||
elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
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
|
changed = True
|
||||||
elif 'Glove' in item.name:
|
elif 'Glove' in item.name:
|
||||||
if self.has('Titans Mitts', item.player):
|
if self.has('Titans Mitts', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Power Glove', item.player):
|
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
|
changed = True
|
||||||
else:
|
else:
|
||||||
self.prog_items.add(('Power Glove', item.player))
|
self.prog_items['Power Glove', item.player] += 1
|
||||||
changed = True
|
changed = True
|
||||||
elif 'Shield' in item.name:
|
elif 'Shield' in item.name:
|
||||||
if self.has('Mirror Shield', item.player):
|
if self.has('Mirror Shield', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
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
|
changed = True
|
||||||
elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
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
|
changed = True
|
||||||
elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
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
|
changed = True
|
||||||
elif 'Bow' in item.name:
|
elif 'Bow' in item.name:
|
||||||
if self.has('Silver Arrows', item.player):
|
if self.has('Silver Arrows', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Bow', item.player):
|
elif self.has('Bow', item.player):
|
||||||
self.prog_items.add(('Silver Arrows', item.player))
|
self.prog_items['Silver Arrows', item.player] += 1
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
self.prog_items.add(('Bow', item.player))
|
self.prog_items['Bow', item.player] += 1
|
||||||
changed = True
|
changed = True
|
||||||
elif 'Armor' in item.name:
|
elif 'Armor' in item.name:
|
||||||
if self.has('Red Mail', item.player):
|
if self.has('Red Mail', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Blue Mail', item.player):
|
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
|
changed = True
|
||||||
else:
|
else:
|
||||||
self.prog_items.add(('Blue Mail', item.player))
|
self.prog_items['Blue Mail', item.player] += 1
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
elif item.name.startswith('Bottle'):
|
elif item.name.startswith('Bottle'):
|
||||||
if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit:
|
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
|
changed = True
|
||||||
elif event or item.advancement:
|
elif event or item.advancement:
|
||||||
self.prog_items.add((item.name, item.player))
|
self.prog_items[item.name, item.player] += 1
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
self.stale[item.player] = True
|
self.stale[item.player] = True
|
||||||
@@ -839,13 +813,13 @@ class CollectionState(object):
|
|||||||
to_remove = None
|
to_remove = None
|
||||||
|
|
||||||
if to_remove is not 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
|
# 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):
|
||||||
@@ -935,10 +909,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
|
||||||
|
|
||||||
@@ -1135,8 +1110,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
|
||||||
|
|||||||
12
Bosses.py
12
Bosses.py
@@ -18,6 +18,7 @@ def ArmosKnightsDefeatRule(state, player):
|
|||||||
# Magic amounts are probably a bit overkill
|
# Magic amounts are probably a bit overkill
|
||||||
return (
|
return (
|
||||||
state.has_blunt_weapon(player) or
|
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 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('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
|
(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))
|
state.has('Red Boomerang', player))
|
||||||
|
|
||||||
def LanmolasDefeatRule(state, player):
|
def LanmolasDefeatRule(state, player):
|
||||||
# TODO: Allow the canes here?
|
|
||||||
return (
|
return (
|
||||||
state.has_blunt_weapon(player) or
|
state.has_blunt_weapon(player) or
|
||||||
state.has('Fire Rod', player) or
|
state.has('Fire Rod', player) or
|
||||||
state.has('Ice 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))
|
state.can_shoot_arrows(player))
|
||||||
|
|
||||||
def MoldormDefeatRule(state, player):
|
def MoldormDefeatRule(state, player):
|
||||||
return state.has_blunt_weapon(player)
|
return state.has_blunt_weapon(player)
|
||||||
|
|
||||||
def HelmasaurKingDefeatRule(state, 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):
|
def ArrghusDefeatRule(state, player):
|
||||||
if not state.has('Hookshot', player):
|
if not state.has('Hookshot', player):
|
||||||
@@ -95,7 +97,11 @@ def VitreousDefeatRule(state, player):
|
|||||||
def TrinexxDefeatRule(state, player):
|
def TrinexxDefeatRule(state, player):
|
||||||
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):
|
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):
|
||||||
return False
|
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):
|
def AgahnimDefeatRule(state, player):
|
||||||
return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', 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])
|
name = ' '.join(builder.name.split(' ')[:-1])
|
||||||
origin_list = list(builder.entrance_list)
|
origin_list = list(builder.entrance_list)
|
||||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
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:
|
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'
|
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))
|
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)
|
region = world.get_region(region_name, player)
|
||||||
for ent in region.entrances:
|
for ent in region.entrances:
|
||||||
parent = ent.parent_region
|
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]:
|
if parent.name not in world.inaccessible_regions[player]:
|
||||||
entrance_map[key].append(region_name)
|
entrance_map[key].append(region_name)
|
||||||
else:
|
else:
|
||||||
|
|||||||
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),
|
||||||
@@ -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 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
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class GraphPiece:
|
|||||||
|
|
||||||
|
|
||||||
# Dungeons shouldn't be generated until all entrances are appropriately accessible
|
# 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)
|
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||||
proposed_map = {}
|
proposed_map = {}
|
||||||
doors_to_connect = {}
|
doors_to_connect = {}
|
||||||
@@ -36,7 +36,7 @@ def pre_validate(builder, entrance_region_names, world, player):
|
|||||||
for door in sector.outstanding_doors:
|
for door in sector.outstanding_doors:
|
||||||
doors_to_connect[door.name] = door
|
doors_to_connect[door.name] = door
|
||||||
all_regions.update(sector.regions)
|
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)
|
bk_special = bk_special or check_for_special(sector)
|
||||||
dungeon, hangers, hooks = gen_dungeon_info(builder.name, builder.sectors, entrance_regions, proposed_map,
|
dungeon, hangers, hooks = gen_dungeon_info(builder.name, builder.sectors, entrance_regions, proposed_map,
|
||||||
doors_to_connect, bk_needed, bk_special, world, player)
|
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_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):
|
||||||
@@ -2298,6 +2304,8 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
|||||||
'Death Mountain Return Cave (East)',
|
'Death Mountain Return Cave (East)',
|
||||||
'Death Mountain Return Cave (West)',
|
'Death Mountain Return Cave (West)',
|
||||||
'Spectacle Rock Cave Peak',
|
'Spectacle Rock Cave Peak',
|
||||||
|
'Spectacle Rock Cave',
|
||||||
|
'Spectacle Rock Cave (Bottom)',
|
||||||
'Paradox Cave (Bottom)',
|
'Paradox Cave (Bottom)',
|
||||||
'Paradox Cave (Middle)',
|
'Paradox Cave (Middle)',
|
||||||
'Paradox Cave (Top)',
|
'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 (East)',
|
||||||
'Death Mountain Return Cave (West)',
|
'Death Mountain Return Cave (West)',
|
||||||
'Spectacle Rock Cave Peak',
|
'Spectacle Rock Cave Peak',
|
||||||
'Spectacle Rock Cave',
|
|
||||||
'Spectacle Rock Cave (Bottom)',
|
|
||||||
'Paradox Cave (Bottom)',
|
'Paradox Cave (Bottom)',
|
||||||
'Paradox Cave (Middle)',
|
'Paradox Cave (Middle)',
|
||||||
'Paradox Cave (Top)',
|
'Paradox Cave (Top)',
|
||||||
@@ -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
@@ -371,7 +371,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)
|
||||||
|
|
||||||
@@ -418,6 +417,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
|
||||||
@@ -469,7 +472,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
|
||||||
@@ -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))
|
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:
|
||||||
@@ -531,7 +533,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
@@ -2122,7 +2122,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]
|
||||||
|
|
||||||
|
|||||||
70
Rules.py
70
Rules.py
@@ -1,27 +1,18 @@
|
|||||||
import logging
|
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 Regions import key_only_locations
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world, player):
|
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))
|
||||||
@@ -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 - Boss', player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', 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_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
|
set_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_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 - 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_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 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 N', player), lambda state: state.has('Flippers', player))
|
||||||
set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_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):
|
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))
|
||||||
@@ -508,12 +501,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))
|
||||||
@@ -826,7 +814,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))
|
||||||
@@ -1049,7 +1049,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
|
||||||
@@ -1082,7 +1082,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
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
aenum
|
aenum
|
||||||
|
fast-enum
|
||||||
Reference in New Issue
Block a user