mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Added Super Metroid support (#46)
Varia Randomizer based implementation LttPClient -> SNIClient
This commit is contained in:
0
worlds/sm/variaRandomizer/graph/__init__.py
Normal file
0
worlds/sm/variaRandomizer/graph/__init__.py
Normal file
413
worlds/sm/variaRandomizer/graph/graph.py
Normal file
413
worlds/sm/variaRandomizer/graph/graph.py
Normal file
@@ -0,0 +1,413 @@
|
||||
import copy, logging
|
||||
from operator import attrgetter
|
||||
import utils.log
|
||||
from logic.smbool import SMBool, smboolFalse
|
||||
from utils.parameters import infinity
|
||||
from logic.helpers import Bosses
|
||||
|
||||
class Path(object):
|
||||
__slots__ = ( 'path', 'pdiff', 'distance' )
|
||||
|
||||
def __init__(self, path, pdiff, distance):
|
||||
self.path = path
|
||||
self.pdiff = pdiff
|
||||
self.distance = distance
|
||||
|
||||
class AccessPoint(object):
|
||||
# name : AccessPoint name
|
||||
# graphArea : graph area the node is located in
|
||||
# transitions : intra-area transitions
|
||||
# traverse: traverse function, will be wand to the added transitions
|
||||
# exitInfo : dict carrying vanilla door information : 'DoorPtr': door address, 'direction', 'cap', 'screen', 'bitFlag', 'distanceToSpawn', 'doorAsmPtr' : door properties
|
||||
# entryInfo : dict carrying forced samus X/Y position with keys 'SamusX' and 'SamusY'.
|
||||
# (to be updated after reading vanillaTransitions and gather entry info from matching exit door)
|
||||
# roomInfo : dict with 'RoomPtr' : room address, 'area'
|
||||
# shortName : short name for the credits
|
||||
# internal : if true, shall not be used for connecting areas
|
||||
def __init__(self, name, graphArea, transitions,
|
||||
traverse=lambda sm: SMBool(True),
|
||||
exitInfo=None, entryInfo=None, roomInfo=None,
|
||||
internal=False, boss=False, escape=False,
|
||||
start=None,
|
||||
dotOrientation='w'):
|
||||
self.Name = name
|
||||
self.GraphArea = graphArea
|
||||
self.ExitInfo = exitInfo
|
||||
self.EntryInfo = entryInfo
|
||||
self.RoomInfo = roomInfo
|
||||
self.Internal = internal
|
||||
self.Boss = boss
|
||||
self.Escape = escape
|
||||
self.Start = start
|
||||
self.DotOrientation = dotOrientation
|
||||
self.intraTransitions = self.sortTransitions(transitions)
|
||||
self.transitions = copy.copy(self.intraTransitions)
|
||||
self.traverse = traverse
|
||||
self.distance = 0
|
||||
# inter-area connection
|
||||
self.ConnectedTo = None
|
||||
|
||||
def __copy__(self):
|
||||
exitInfo = copy.deepcopy(self.ExitInfo) if self.ExitInfo is not None else None
|
||||
entryInfo = copy.deepcopy(self.EntryInfo) if self.EntryInfo is not None else None
|
||||
roomInfo = copy.deepcopy(self.RoomInfo) if self.RoomInfo is not None else None
|
||||
start = copy.deepcopy(self.Start) if self.Start is not None else None
|
||||
# in any case, do not copy connections
|
||||
return AccessPoint(self.Name, self.GraphArea, self.intraTransitions, self.traverse,
|
||||
exitInfo, entryInfo, roomInfo,
|
||||
self.Internal, self.Boss, self.Escape,
|
||||
start, self.DotOrientation)
|
||||
|
||||
def __str__(self):
|
||||
return "[" + self.GraphArea + "] " + self.Name
|
||||
|
||||
def __repr__(self):
|
||||
return self.Name
|
||||
|
||||
def sortTransitions(self, transitions=None):
|
||||
# sort transitions before the loop in getNewAvailNodes.
|
||||
# as of python3.7 insertion order is guaranteed in dictionaires.
|
||||
if transitions is None:
|
||||
transitions = self.transitions
|
||||
return { key: transitions[key] for key in sorted(transitions.keys()) }
|
||||
|
||||
# connect to inter-area access point
|
||||
def connect(self, destName):
|
||||
self.disconnect()
|
||||
if self.Internal is False:
|
||||
self.transitions[destName] = self.traverse
|
||||
self.ConnectedTo = destName
|
||||
else:
|
||||
raise RuntimeError("Cannot add an internal access point as inter-are transition")
|
||||
self.transitions = self.sortTransitions()
|
||||
|
||||
def disconnect(self):
|
||||
if self.ConnectedTo is not None:
|
||||
if self.ConnectedTo not in self.intraTransitions:
|
||||
del self.transitions[self.ConnectedTo]
|
||||
else:
|
||||
self.transitions[self.ConnectedTo] = self.intraTransitions[self.ConnectedTo]
|
||||
self.ConnectedTo = None
|
||||
|
||||
# tells if this node is to connect areas together
|
||||
def isArea(self):
|
||||
return not self.Internal and not self.Boss and not self.Escape
|
||||
|
||||
# used by the solver to get area and boss APs
|
||||
def isInternal(self):
|
||||
return self.Internal or self.Escape
|
||||
|
||||
def isLoop(self):
|
||||
return self.ConnectedTo == self.Name
|
||||
|
||||
class AccessGraph(object):
|
||||
__slots__ = ( 'log', 'accessPoints', 'InterAreaTransitions',
|
||||
'EscapeAttributes', 'apCache', '_useCache',
|
||||
'availAccessPoints' )
|
||||
|
||||
def __init__(self, accessPointList, transitions, dotFile=None):
|
||||
self.log = utils.log.get('Graph')
|
||||
self.accessPoints = {}
|
||||
self.InterAreaTransitions = []
|
||||
self.EscapeAttributes = {
|
||||
'Timer': None,
|
||||
'Animals': None
|
||||
}
|
||||
for ap in accessPointList:
|
||||
self.addAccessPoint(ap)
|
||||
for srcName, dstName in transitions:
|
||||
self.addTransition(srcName, dstName)
|
||||
if dotFile is not None:
|
||||
self.toDot(dotFile)
|
||||
self.apCache = {}
|
||||
self._useCache = False
|
||||
# store the avail access points to display in vcr
|
||||
self.availAccessPoints = {}
|
||||
|
||||
def useCache(self, use):
|
||||
self._useCache = use
|
||||
if self._useCache:
|
||||
self.resetCache()
|
||||
|
||||
def resetCache(self):
|
||||
self.apCache = {}
|
||||
|
||||
def printGraph(self):
|
||||
if self.log.getEffectiveLevel() == logging.DEBUG:
|
||||
self.log("Area graph:")
|
||||
for s, d in self.InterAreaTransitions:
|
||||
self.log("{} -> {}".format(s.Name, d.Name))
|
||||
|
||||
def addAccessPoint(self, ap):
|
||||
ap.distance = 0
|
||||
self.accessPoints[ap.Name] = ap
|
||||
|
||||
def toDot(self, dotFile):
|
||||
colors = ['red', 'blue', 'green', 'yellow', 'skyblue', 'violet', 'orange',
|
||||
'lawngreen', 'crimson', 'chocolate', 'turquoise', 'tomato',
|
||||
'navyblue', 'darkturquoise', 'green', 'blue', 'maroon', 'magenta',
|
||||
'bisque', 'coral', 'chartreuse', 'chocolate', 'cyan']
|
||||
with open(dotFile, "w") as f:
|
||||
f.write("digraph {\n")
|
||||
f.write('size="30,30!";\n')
|
||||
f.write('rankdir=LR;\n')
|
||||
f.write('ranksep=2.2;\n')
|
||||
f.write('overlap=scale;\n')
|
||||
f.write('edge [dir="both",arrowhead="box",arrowtail="box",arrowsize=0.5,fontsize=7,style=dotted];\n')
|
||||
f.write('node [shape="box",fontsize=10];\n')
|
||||
for area in set([ap.GraphArea for ap in self.accessPoints.values()]):
|
||||
f.write(area + ";\n") # TODO area long name and color
|
||||
drawn = []
|
||||
i = 0
|
||||
for src, dst in self.InterAreaTransitions:
|
||||
if src.Name in drawn:
|
||||
continue
|
||||
f.write('%s:%s -> %s:%s [taillabel="%s",headlabel="%s",color=%s];\n' % (src.GraphArea, src.DotOrientation, dst.GraphArea, dst.DotOrientation, src.Name, dst.Name, colors[i]))
|
||||
drawn += [src.Name,dst.Name]
|
||||
i += 1
|
||||
f.write("}\n")
|
||||
|
||||
def addTransition(self, srcName, dstName, both=True):
|
||||
src = self.accessPoints[srcName]
|
||||
dst = self.accessPoints[dstName]
|
||||
src.connect(dstName)
|
||||
self.InterAreaTransitions.append((src, dst))
|
||||
if both is True:
|
||||
self.addTransition(dstName, srcName, False)
|
||||
|
||||
# availNodes: all already available nodes
|
||||
# nodesToCheck: nodes we have to check transitions for
|
||||
# smbm: smbm to test logic on. if None, discard logic check, assume we can reach everything
|
||||
# maxDiff: difficulty limit
|
||||
# return newly opened access points
|
||||
def getNewAvailNodes(self, availNodes, nodesToCheck, smbm, maxDiff, item=None):
|
||||
newAvailNodes = {}
|
||||
# with python >= 3.6 the insertion order in a dict is keeps when looping on the keys,
|
||||
# so we no longer have to sort them.
|
||||
for src in nodesToCheck:
|
||||
for dstName in src.transitions:
|
||||
dst = self.accessPoints[dstName]
|
||||
if dst in availNodes or dst in newAvailNodes:
|
||||
continue
|
||||
if smbm is not None:
|
||||
if self._useCache == True and (src, dst, item) in self.apCache:
|
||||
diff = self.apCache[(src, dst, item)]
|
||||
else:
|
||||
tFunc = src.transitions[dstName]
|
||||
diff = tFunc(smbm)
|
||||
if self._useCache == True:
|
||||
self.apCache[(src, dst, item)] = diff
|
||||
else:
|
||||
diff = SMBool(True)
|
||||
if diff.bool and diff.difficulty <= maxDiff:
|
||||
if src.GraphArea == dst.GraphArea:
|
||||
dst.distance = src.distance + 0.01
|
||||
else:
|
||||
dst.distance = src.distance + 1
|
||||
newAvailNodes[dst] = { 'difficulty': diff, 'from': src }
|
||||
|
||||
#self.log.debug("{} -> {}: {}".format(src.Name, dstName, diff))
|
||||
return newAvailNodes
|
||||
|
||||
# rootNode: starting AccessPoint instance
|
||||
# smbm: smbm to test logic on. if None, discard logic check, assume we can reach everything
|
||||
# maxDiff: difficulty limit.
|
||||
# smbm: if None, discard logic check, assume we can reach everything
|
||||
# return available AccessPoint list
|
||||
def getAvailableAccessPoints(self, rootNode, smbm, maxDiff, item=None):
|
||||
availNodes = { rootNode : { 'difficulty' : SMBool(True, 0), 'from' : None } }
|
||||
newAvailNodes = availNodes
|
||||
rootNode.distance = 0
|
||||
while len(newAvailNodes) > 0:
|
||||
newAvailNodes = self.getNewAvailNodes(availNodes, newAvailNodes, smbm, maxDiff, item)
|
||||
availNodes.update(newAvailNodes)
|
||||
return availNodes
|
||||
|
||||
# gets path from the root AP used to compute availAps
|
||||
def getPath(self, dstAp, availAps):
|
||||
path = []
|
||||
root = dstAp
|
||||
while root != None:
|
||||
path = [root] + path
|
||||
root = availAps[root]['from']
|
||||
|
||||
return path
|
||||
|
||||
def getAvailAPPaths(self, availAccessPoints, locsAPs):
|
||||
paths = {}
|
||||
for ap in availAccessPoints:
|
||||
if ap.Name in locsAPs:
|
||||
path = self.getPath(ap, availAccessPoints)
|
||||
pdiff = SMBool.wandmax(*(availAccessPoints[ap]['difficulty'] for ap in path))
|
||||
paths[ap.Name] = Path(path, pdiff, len(path))
|
||||
return paths
|
||||
|
||||
def getSortedAPs(self, paths, locAccessFrom):
|
||||
ret = []
|
||||
|
||||
for apName in locAccessFrom:
|
||||
path = paths.get(apName, None)
|
||||
if path is None:
|
||||
continue
|
||||
difficulty = paths[apName].pdiff.difficulty
|
||||
ret.append((difficulty if difficulty != -1 else infinity, path.distance, apName))
|
||||
ret.sort()
|
||||
return [apName for diff, dist, apName in ret]
|
||||
|
||||
# locations: locations to check
|
||||
# items: collected items
|
||||
# maxDiff: difficulty limit
|
||||
# rootNode: starting AccessPoint
|
||||
# return available locations list, also stores difficulty in locations
|
||||
def getAvailableLocations(self, locations, smbm, maxDiff, rootNode='Landing Site'):
|
||||
rootAp = self.accessPoints[rootNode]
|
||||
self.availAccessPoints = self.getAvailableAccessPoints(rootAp, smbm, maxDiff)
|
||||
availAreas = set([ap.GraphArea for ap in self.availAccessPoints.keys()])
|
||||
availLocs = []
|
||||
|
||||
# get all the current locations APs first to only compute these paths
|
||||
locsAPs = set()
|
||||
for loc in locations:
|
||||
for ap in loc.AccessFrom:
|
||||
locsAPs.add(ap)
|
||||
|
||||
# sort availAccessPoints based on difficulty to take easier paths first
|
||||
availAPPaths = self.getAvailAPPaths(self.availAccessPoints, locsAPs)
|
||||
|
||||
for loc in locations:
|
||||
if loc.GraphArea not in availAreas:
|
||||
loc.distance = 30000
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} locDiff is area nok".format(loc.Name))
|
||||
continue
|
||||
|
||||
locAPs = self.getSortedAPs(availAPPaths, loc.AccessFrom)
|
||||
if len(locAPs) == 0:
|
||||
loc.distance = 40000
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} no aps".format(loc.Name))
|
||||
continue
|
||||
|
||||
for apName in locAPs:
|
||||
if apName == None:
|
||||
loc.distance = 20000
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} ap is none".format(loc.Name))
|
||||
break
|
||||
|
||||
tFunc = loc.AccessFrom[apName]
|
||||
ap = self.accessPoints[apName]
|
||||
tdiff = tFunc(smbm)
|
||||
#if loc.Name == "Kraid":
|
||||
# print("{} root: {} ap: {}".format(loc.Name, rootNode, apName))
|
||||
if tdiff.bool == True and tdiff.difficulty <= maxDiff:
|
||||
diff = loc.Available(smbm)
|
||||
if diff.bool == True:
|
||||
path = availAPPaths[apName].path
|
||||
#if loc.Name == "Kraid":
|
||||
# print("{} path: {}".format(loc.Name, [a.Name for a in path]))
|
||||
pdiff = availAPPaths[apName].pdiff
|
||||
(allDiff, locDiff) = self.computeLocDiff(tdiff, diff, pdiff)
|
||||
if allDiff.bool == True and allDiff.difficulty <= maxDiff:
|
||||
loc.distance = ap.distance + 1
|
||||
loc.accessPoint = apName
|
||||
loc.difficulty = allDiff
|
||||
loc.path = path
|
||||
# used only by solver
|
||||
loc.pathDifficulty = pdiff
|
||||
loc.locDifficulty = locDiff
|
||||
availLocs.append(loc)
|
||||
#if loc.Name == "Kraid":
|
||||
# print("{} diff: {} tdiff: {} pdiff: {}".format(loc.Name, diff, tdiff, pdiff))
|
||||
break
|
||||
else:
|
||||
loc.distance = 1000 + tdiff.difficulty
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} allDiff is false".format(loc.Name))
|
||||
else:
|
||||
loc.distance = 1000 + tdiff.difficulty
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} allDiff is false".format(loc.Name))
|
||||
else:
|
||||
loc.distance = 10000 + tdiff.difficulty
|
||||
loc.difficulty = smboolFalse
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} tdiff is false".format(loc.Name))
|
||||
|
||||
if loc.difficulty is None:
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {} no difficulty in loc".format(loc.Name))
|
||||
loc.distance = 100000
|
||||
loc.difficulty = smboolFalse
|
||||
|
||||
#if loc.Name == "Kraid":
|
||||
# print("loc: {}: {}".format(loc.Name, loc))
|
||||
|
||||
#print("availableLocs: {}".format([loc.Name for loc in availLocs]))
|
||||
return availLocs
|
||||
|
||||
# test access from an access point to another, given an optional item
|
||||
def canAccess(self, smbm, srcAccessPointName, destAccessPointName, maxDiff, item=None):
|
||||
if item is not None:
|
||||
smbm.addItem(item)
|
||||
#print("canAccess: item: {}, src: {}, dest: {}".format(item, srcAccessPointName, destAccessPointName))
|
||||
destAccessPoint = self.accessPoints[destAccessPointName]
|
||||
srcAccessPoint = self.accessPoints[srcAccessPointName]
|
||||
availAccessPoints = self.getAvailableAccessPoints(srcAccessPoint, smbm, maxDiff, item)
|
||||
can = destAccessPoint in availAccessPoints
|
||||
# if not can:
|
||||
# self.log.debug("canAccess KO: avail = {}".format([ap.Name for ap in availAccessPoints.keys()]))
|
||||
if item is not None:
|
||||
smbm.removeItem(item)
|
||||
#print("canAccess: {}".format(can))
|
||||
return can
|
||||
|
||||
# returns a list of AccessPoint instances from srcAccessPointName to destAccessPointName
|
||||
# (not including source ap)
|
||||
# or None if no possible path
|
||||
def accessPath(self, smbm, srcAccessPointName, destAccessPointName, maxDiff):
|
||||
destAccessPoint = self.accessPoints[destAccessPointName]
|
||||
srcAccessPoint = self.accessPoints[srcAccessPointName]
|
||||
availAccessPoints = self.getAvailableAccessPoints(srcAccessPoint, smbm, maxDiff)
|
||||
if destAccessPoint not in availAccessPoints:
|
||||
return None
|
||||
return self.getPath(destAccessPoint, availAccessPoints)
|
||||
|
||||
# gives theoretically accessible APs in the graph (no logic check)
|
||||
def getAccessibleAccessPoints(self, rootNode='Landing Site'):
|
||||
rootAp = self.accessPoints[rootNode]
|
||||
inBossChk = lambda ap: ap.Boss and ap.Name.endswith("In")
|
||||
allAreas = {dst.GraphArea for (src, dst) in self.InterAreaTransitions if not inBossChk(dst) and not dst.isLoop()}
|
||||
self.log.debug("allAreas="+str(allAreas))
|
||||
nonBossAPs = [ap for ap in self.getAvailableAccessPoints(rootAp, None, 0) if ap.GraphArea in allAreas]
|
||||
bossesAPs = [self.accessPoints[boss+'RoomIn'] for boss in Bosses.Golden4()] + [self.accessPoints['Draygon Room Bottom']]
|
||||
return nonBossAPs + bossesAPs
|
||||
|
||||
# gives theoretically accessible locations within a base list
|
||||
# returns locations with accessible GraphArea in this graph (no logic considered)
|
||||
def getAccessibleLocations(self, locations, rootNode='Landing Site'):
|
||||
availAccessPoints = self.getAccessibleAccessPoints(rootNode)
|
||||
self.log.debug("availAccessPoints="+str([ap.Name for ap in availAccessPoints]))
|
||||
return [loc for loc in locations if any(ap.Name in loc.AccessFrom for ap in availAccessPoints)]
|
||||
|
||||
class AccessGraphSolver(AccessGraph):
|
||||
def computeLocDiff(self, tdiff, diff, pdiff):
|
||||
# tdiff: difficulty from the location's access point to the location's room
|
||||
# diff: difficulty to reach the item in the location's room
|
||||
# pdiff: difficulty of the path from the current access point to the location's access point
|
||||
# in output we need the global difficulty but we also need to separate pdiff and (tdiff + diff)
|
||||
|
||||
locDiff = SMBool.wandmax(tdiff, diff)
|
||||
allDiff = SMBool.wandmax(locDiff, pdiff)
|
||||
|
||||
return (allDiff, locDiff)
|
||||
|
||||
class AccessGraphRando(AccessGraph):
|
||||
def computeLocDiff(self, tdiff, diff, pdiff):
|
||||
allDiff = SMBool.wandmax(tdiff, diff, pdiff)
|
||||
return (allDiff, None)
|
||||
575
worlds/sm/variaRandomizer/graph/graph_utils.py
Normal file
575
worlds/sm/variaRandomizer/graph/graph_utils.py
Normal file
@@ -0,0 +1,575 @@
|
||||
import copy
|
||||
import random
|
||||
from logic.logic import Logic
|
||||
from utils.parameters import Knows
|
||||
from graph.location import locationsDict
|
||||
from rom.rom import snes_to_pc
|
||||
import utils.log
|
||||
|
||||
# order expected by ROM patches
|
||||
graphAreas = [
|
||||
"Ceres",
|
||||
"Crateria",
|
||||
"GreenPinkBrinstar",
|
||||
"RedBrinstar",
|
||||
"WreckedShip",
|
||||
"Kraid",
|
||||
"Norfair",
|
||||
"Crocomire",
|
||||
"LowerNorfair",
|
||||
"WestMaridia",
|
||||
"EastMaridia",
|
||||
"Tourian"
|
||||
]
|
||||
|
||||
vanillaTransitions = [
|
||||
('Lower Mushrooms Left', 'Green Brinstar Elevator'),
|
||||
('Morph Ball Room Left', 'Green Hill Zone Top Right'),
|
||||
('Moat Right', 'West Ocean Left'),
|
||||
('Keyhunter Room Bottom', 'Red Brinstar Elevator'),
|
||||
('Noob Bridge Right', 'Red Tower Top Left'),
|
||||
('Crab Maze Left', 'Le Coude Right'),
|
||||
('Kronic Boost Room Bottom Left', 'Lava Dive Right'),
|
||||
('Crocomire Speedway Bottom', 'Crocomire Room Top'),
|
||||
('Three Muskateers Room Left', 'Single Chamber Top Right'),
|
||||
('Warehouse Entrance Left', 'East Tunnel Right'),
|
||||
('East Tunnel Top Right', 'Crab Hole Bottom Left'),
|
||||
('Caterpillar Room Top Right', 'Red Fish Room Left'),
|
||||
('Glass Tunnel Top', 'Main Street Bottom'),
|
||||
('Green Pirates Shaft Bottom Right', 'Golden Four'),
|
||||
('Warehouse Entrance Right', 'Warehouse Zeela Room Left'),
|
||||
('Crab Shaft Right', 'Aqueduct Top Left')
|
||||
]
|
||||
|
||||
vanillaBossesTransitions = [
|
||||
('KraidRoomOut', 'KraidRoomIn'),
|
||||
('PhantoonRoomOut', 'PhantoonRoomIn'),
|
||||
('DraygonRoomOut', 'DraygonRoomIn'),
|
||||
('RidleyRoomOut', 'RidleyRoomIn')
|
||||
]
|
||||
|
||||
# vanilla escape transition in first position
|
||||
vanillaEscapeTransitions = [
|
||||
('Tourian Escape Room 4 Top Right', 'Climb Bottom Left'),
|
||||
('Brinstar Pre-Map Room Right', 'Green Brinstar Main Shaft Top Left'),
|
||||
('Wrecked Ship Map Room', 'Basement Left'),
|
||||
('Norfair Map Room', 'Business Center Mid Left'),
|
||||
('Maridia Map Room', 'Crab Hole Bottom Right')
|
||||
]
|
||||
|
||||
vanillaEscapeAnimalsTransitions = [
|
||||
('Flyway Right 0', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 1', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 2', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 3', 'Bomb Torizo Room Left'),
|
||||
('Bomb Torizo Room Left Animals', 'Flyway Right')
|
||||
]
|
||||
|
||||
escapeSource = 'Tourian Escape Room 4 Top Right'
|
||||
escapeTargets = ['Green Brinstar Main Shaft Top Left', 'Basement Left', 'Business Center Mid Left', 'Crab Hole Bottom Right']
|
||||
|
||||
locIdsByAreaAddresses = {
|
||||
"Ceres": snes_to_pc(0xA1F568),
|
||||
"Crateria": snes_to_pc(0xA1F569),
|
||||
"GreenPinkBrinstar": snes_to_pc(0xA1F57B),
|
||||
"RedBrinstar": snes_to_pc(0xA1F58C),
|
||||
"WreckedShip": snes_to_pc(0xA1F592),
|
||||
"Kraid": snes_to_pc(0xA1F59E),
|
||||
"Norfair": snes_to_pc(0xA1F5A2),
|
||||
"Crocomire": snes_to_pc(0xA1F5B2),
|
||||
"LowerNorfair": snes_to_pc(0xA1F5B8),
|
||||
"WestMaridia": snes_to_pc(0xA1F5C3),
|
||||
"EastMaridia": snes_to_pc(0xA1F5CB),
|
||||
"Tourian": snes_to_pc(0xA1F5D7)
|
||||
}
|
||||
|
||||
def getAccessPoint(apName, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
return next(ap for ap in apList if ap.Name == apName)
|
||||
|
||||
class GraphUtils:
|
||||
log = utils.log.get('GraphUtils')
|
||||
|
||||
def getStartAccessPointNames():
|
||||
return [ap.Name for ap in Logic.accessPoints if ap.Start is not None]
|
||||
|
||||
def getStartAccessPointNamesCategory():
|
||||
ret = {'regular': [], 'custom': [], 'area': []}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Start == None:
|
||||
continue
|
||||
elif 'areaMode' in ap.Start and ap.Start['areaMode'] == True:
|
||||
ret['area'].append(ap.Name)
|
||||
elif GraphUtils.isStandardStart(ap.Name):
|
||||
ret['regular'].append(ap.Name)
|
||||
else:
|
||||
ret['custom'].append(ap.Name)
|
||||
return ret
|
||||
|
||||
def isStandardStart(startApName):
|
||||
return startApName == 'Ceres' or startApName == 'Landing Site'
|
||||
|
||||
def getPossibleStartAPs(areaMode, maxDiff, morphPlacement, player):
|
||||
ret = []
|
||||
refused = {}
|
||||
allStartAPs = GraphUtils.getStartAccessPointNames()
|
||||
for apName in allStartAPs:
|
||||
start = getAccessPoint(apName).Start
|
||||
ok = True
|
||||
cause = ""
|
||||
if 'knows' in start:
|
||||
for k in start['knows']:
|
||||
if not Knows.knowsDict[player].knows(k, maxDiff):
|
||||
ok = False
|
||||
cause += Knows.desc[k]['display']+" is not known. "
|
||||
break
|
||||
if 'areaMode' in start and start['areaMode'] != areaMode:
|
||||
ok = False
|
||||
cause += "Start location available only with area randomization enabled. "
|
||||
if 'forcedEarlyMorph' in start and start['forcedEarlyMorph'] == True and morphPlacement == 'late':
|
||||
ok = False
|
||||
cause += "Start location unavailable with late morph placement. "
|
||||
if ok:
|
||||
ret.append(apName)
|
||||
else:
|
||||
refused[apName] = cause
|
||||
return ret, refused
|
||||
|
||||
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs):
|
||||
locs = locationsDict
|
||||
preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)]
|
||||
possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs]
|
||||
GraphUtils.log.debug("possLocs="+str([loc.Name for loc in possLocs]))
|
||||
candidates = [loc for loc in locs.values() if loc.GraphArea == startGraphArea and loc.isClass(split) and loc not in preserveMajLocs]
|
||||
remLocs = [loc for loc in locs.values() if loc not in possLocs and loc not in candidates and loc.isClass(split)]
|
||||
newLocs = []
|
||||
while len(newLocs) < nLocs:
|
||||
if len(candidates) == 0:
|
||||
candidates = remLocs
|
||||
loc = possLocs.pop(random.randint(0,len(possLocs)-1))
|
||||
newLocs.append(loc)
|
||||
loc.setClass([split])
|
||||
if not loc in preserveMajLocs:
|
||||
GraphUtils.log.debug("newMajor="+loc.Name)
|
||||
loc = candidates.pop(random.randint(0,len(candidates)-1))
|
||||
loc.setClass(["Minor"])
|
||||
GraphUtils.log.debug("replaced="+loc.Name)
|
||||
|
||||
def getGraphPatches(startApName):
|
||||
ap = getAccessPoint(startApName)
|
||||
return ap.Start['patches'] if 'patches' in ap.Start else []
|
||||
|
||||
def createBossesTransitions():
|
||||
transitions = vanillaBossesTransitions
|
||||
def isVanilla():
|
||||
for t in vanillaBossesTransitions:
|
||||
if t not in transitions:
|
||||
return False
|
||||
return True
|
||||
while isVanilla():
|
||||
transitions = []
|
||||
srcs = []
|
||||
dsts = []
|
||||
for (src,dst) in vanillaBossesTransitions:
|
||||
srcs.append(src)
|
||||
dsts.append(dst)
|
||||
while len(srcs) > 0:
|
||||
src = srcs.pop(random.randint(0,len(srcs)-1))
|
||||
dst = dsts.pop(random.randint(0,len(dsts)-1))
|
||||
transitions.append((src,dst))
|
||||
return transitions
|
||||
|
||||
def createAreaTransitions(lightAreaRando=False):
|
||||
if lightAreaRando:
|
||||
return GraphUtils.createLightAreaTransitions()
|
||||
else:
|
||||
return GraphUtils.createRegularAreaTransitions()
|
||||
|
||||
def createRegularAreaTransitions(apList=None, apPred=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
if apPred is None:
|
||||
apPred = lambda ap: ap.isArea()
|
||||
tFrom = []
|
||||
tTo = []
|
||||
apNames = [ap.Name for ap in apList if apPred(ap) == True]
|
||||
transitions = []
|
||||
|
||||
def findTo(trFrom):
|
||||
ap = getAccessPoint(trFrom, apList)
|
||||
fromArea = ap.GraphArea
|
||||
targets = [apName for apName in apNames if apName not in tTo and getAccessPoint(apName, apList).GraphArea != fromArea]
|
||||
if len(targets) == 0: # fallback if no area transition is found
|
||||
targets = [apName for apName in apNames if apName != ap.Name]
|
||||
if len(targets) == 0: # extreme fallback: loop on itself
|
||||
targets = [ap.Name]
|
||||
return random.choice(targets)
|
||||
|
||||
def addTransition(src, dst):
|
||||
tFrom.append(src)
|
||||
tTo.append(dst)
|
||||
|
||||
while len(apNames) > 0:
|
||||
sources = [apName for apName in apNames if apName not in tFrom]
|
||||
src = random.choice(sources)
|
||||
dst = findTo(src)
|
||||
transitions.append((src, dst))
|
||||
addTransition(src, dst)
|
||||
addTransition(dst, src)
|
||||
toRemove = [apName for apName in apNames if apName in tFrom and apName in tTo]
|
||||
for apName in toRemove:
|
||||
apNames.remove(apName)
|
||||
return transitions
|
||||
|
||||
def getAPs(apPredicate, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
return [ap for ap in apList if apPredicate(ap) == True]
|
||||
|
||||
def loopUnusedTransitions(transitions, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
usedAPs = set()
|
||||
for (src,dst) in transitions:
|
||||
usedAPs.add(getAccessPoint(src, apList))
|
||||
usedAPs.add(getAccessPoint(dst, apList))
|
||||
unusedAPs = [ap for ap in apList if not ap.isInternal() and ap not in usedAPs]
|
||||
for ap in unusedAPs:
|
||||
transitions.append((ap.Name, ap.Name))
|
||||
|
||||
def createMinimizerTransitions(startApName, locLimit):
|
||||
if startApName == 'Ceres':
|
||||
startApName = 'Landing Site'
|
||||
startAp = getAccessPoint(startApName)
|
||||
def getNLocs(locsPredicate, locList=None):
|
||||
if locList is None:
|
||||
locList = Logic.locations
|
||||
# leave out bosses and count post boss locs systematically
|
||||
return len([loc for loc in locList if locsPredicate(loc) == True and not loc.SolveArea.endswith(" Boss") and not loc.isBoss()])
|
||||
availAreas = list(sorted({ap.GraphArea for ap in Logic.accessPoints if ap.GraphArea != startAp.GraphArea and getNLocs(lambda loc: loc.GraphArea == ap.GraphArea) > 0}))
|
||||
areas = [startAp.GraphArea]
|
||||
GraphUtils.log.debug("availAreas: {}".format(availAreas))
|
||||
GraphUtils.log.debug("areas: {}".format(areas))
|
||||
inBossCheck = lambda ap: ap.Boss and ap.Name.endswith("In")
|
||||
nLocs = 0
|
||||
transitions = []
|
||||
usedAPs = []
|
||||
trLimit = 5
|
||||
locLimit -= 3 # 3 "post boss" locs will always be available, and are filtered out in getNLocs
|
||||
def openTransitions():
|
||||
nonlocal areas, inBossCheck, usedAPs
|
||||
return GraphUtils.getAPs(lambda ap: ap.GraphArea in areas and not ap.isInternal() and not inBossCheck(ap) and not ap in usedAPs)
|
||||
while nLocs < locLimit or len(openTransitions()) < trLimit:
|
||||
GraphUtils.log.debug("openTransitions="+str([ap.Name for ap in openTransitions()]))
|
||||
fromAreas = availAreas
|
||||
if nLocs >= locLimit:
|
||||
GraphUtils.log.debug("not enough open transitions")
|
||||
# we just need transitions, avoid adding a huge area
|
||||
fromAreas = []
|
||||
n = trLimit - len(openTransitions())
|
||||
while len(fromAreas) == 0:
|
||||
fromAreas = [area for area in availAreas if len(GraphUtils.getAPs(lambda ap: not ap.isInternal())) > n]
|
||||
n -= 1
|
||||
minLocs = min([getNLocs(lambda loc: loc.GraphArea == area) for area in fromAreas])
|
||||
fromAreas = [area for area in fromAreas if getNLocs(lambda loc: loc.GraphArea == area) == minLocs]
|
||||
elif len(openTransitions()) <= 1: # dont' get stuck by adding dead ends
|
||||
fromAreas = [area for area in fromAreas if len(GraphUtils.getAPs(lambda ap: ap.GraphArea == area and not ap.isInternal())) > 1]
|
||||
nextArea = random.choice(fromAreas)
|
||||
GraphUtils.log.debug("nextArea="+str(nextArea))
|
||||
apCheck = lambda ap: not ap.isInternal() and not inBossCheck(ap) and ap not in usedAPs
|
||||
possibleSources = GraphUtils.getAPs(lambda ap: ap.GraphArea in areas and apCheck(ap))
|
||||
possibleTargets = GraphUtils.getAPs(lambda ap: ap.GraphArea == nextArea and apCheck(ap))
|
||||
src = random.choice(possibleSources)
|
||||
dst = random.choice(possibleTargets)
|
||||
usedAPs += [src,dst]
|
||||
GraphUtils.log.debug("add transition: (src: {}, dst: {})".format(src.Name, dst.Name))
|
||||
transitions.append((src.Name,dst.Name))
|
||||
availAreas.remove(nextArea)
|
||||
areas.append(nextArea)
|
||||
GraphUtils.log.debug("areas: {}".format(areas))
|
||||
nLocs = getNLocs(lambda loc:loc.GraphArea in areas)
|
||||
GraphUtils.log.debug("nLocs: {}".format(nLocs))
|
||||
# we picked the areas, add transitions (bosses and tourian first)
|
||||
sourceAPs = openTransitions()
|
||||
random.shuffle(sourceAPs)
|
||||
targetAPs = GraphUtils.getAPs(lambda ap: (inBossCheck(ap) or ap.Name == "Golden Four") and not ap in usedAPs)
|
||||
random.shuffle(targetAPs)
|
||||
assert len(sourceAPs) >= len(targetAPs), "Minimizer: less source than target APs"
|
||||
while len(targetAPs) > 0:
|
||||
transitions.append((sourceAPs.pop().Name, targetAPs.pop().Name))
|
||||
transitions += GraphUtils.createRegularAreaTransitions(sourceAPs, lambda ap: not ap.isInternal())
|
||||
GraphUtils.log.debug("FINAL MINIMIZER transitions: {}".format(transitions))
|
||||
GraphUtils.loopUnusedTransitions(transitions)
|
||||
GraphUtils.log.debug("FINAL MINIMIZER nLocs: "+str(nLocs+3))
|
||||
GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas))
|
||||
return transitions
|
||||
|
||||
def createLightAreaTransitions():
|
||||
# group APs by area
|
||||
aps = {}
|
||||
totalCount = 0
|
||||
for ap in Logic.accessPoints:
|
||||
if not ap.isArea():
|
||||
continue
|
||||
if not ap.GraphArea in aps:
|
||||
aps[ap.GraphArea] = {'totalCount': 0, 'transCount': {}, 'apNames': []}
|
||||
aps[ap.GraphArea]['apNames'].append(ap.Name)
|
||||
# count number of vanilla transitions between each area
|
||||
for (srcName, destName) in vanillaTransitions:
|
||||
srcAP = getAccessPoint(srcName)
|
||||
destAP = getAccessPoint(destName)
|
||||
aps[srcAP.GraphArea]['transCount'][destAP.GraphArea] = aps[srcAP.GraphArea]['transCount'].get(destAP.GraphArea, 0) + 1
|
||||
aps[srcAP.GraphArea]['totalCount'] += 1
|
||||
aps[destAP.GraphArea]['transCount'][srcAP.GraphArea] = aps[destAP.GraphArea]['transCount'].get(srcAP.GraphArea, 0) + 1
|
||||
aps[destAP.GraphArea]['totalCount'] += 1
|
||||
totalCount += 1
|
||||
|
||||
transitions = []
|
||||
while totalCount > 0:
|
||||
# choose transition
|
||||
srcArea = random.choice(list(aps.keys()))
|
||||
srcName = random.choice(aps[srcArea]['apNames'])
|
||||
src = getAccessPoint(srcName)
|
||||
destArea = random.choice(list(aps[src.GraphArea]['transCount'].keys()))
|
||||
destName = random.choice(aps[destArea]['apNames'])
|
||||
transitions.append((srcName, destName))
|
||||
|
||||
# update counts
|
||||
totalCount -= 1
|
||||
aps[srcArea]['totalCount'] -= 1
|
||||
aps[destArea]['totalCount'] -= 1
|
||||
aps[srcArea]['transCount'][destArea] -= 1
|
||||
if aps[srcArea]['transCount'][destArea] == 0:
|
||||
del aps[srcArea]['transCount'][destArea]
|
||||
aps[destArea]['transCount'][srcArea] -= 1
|
||||
if aps[destArea]['transCount'][srcArea] == 0:
|
||||
del aps[destArea]['transCount'][srcArea]
|
||||
aps[srcArea]['apNames'].remove(srcName)
|
||||
aps[destArea]['apNames'].remove(destName)
|
||||
|
||||
if aps[srcArea]['totalCount'] == 0:
|
||||
del aps[srcArea]
|
||||
if aps[destArea]['totalCount'] == 0:
|
||||
del aps[destArea]
|
||||
|
||||
return transitions
|
||||
|
||||
def getVanillaExit(apName):
|
||||
allVanillaTransitions = vanillaTransitions + vanillaBossesTransitions + vanillaEscapeTransitions
|
||||
for (src,dst) in allVanillaTransitions:
|
||||
if apName == src:
|
||||
return dst
|
||||
if apName == dst:
|
||||
return src
|
||||
return None
|
||||
|
||||
def isEscapeAnimals(apName):
|
||||
return 'Flyway Right' in apName or 'Bomb Torizo Room Left' in apName
|
||||
|
||||
# gets dict like
|
||||
# (RoomPtr, (vanilla entry screen X, vanilla entry screen Y)): AP
|
||||
def getRooms():
|
||||
rooms = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
# special ap for random escape animals surprise
|
||||
if GraphUtils.isEscapeAnimals(ap.Name):
|
||||
continue
|
||||
|
||||
roomPtr = ap.RoomInfo['RoomPtr']
|
||||
|
||||
vanillaExitName = GraphUtils.getVanillaExit(ap.Name)
|
||||
# special ap for random escape animals surprise
|
||||
if GraphUtils.isEscapeAnimals(vanillaExitName):
|
||||
continue
|
||||
|
||||
connAP = getAccessPoint(vanillaExitName)
|
||||
entryInfo = connAP.ExitInfo
|
||||
rooms[(roomPtr, entryInfo['screen'], entryInfo['direction'])] = ap
|
||||
rooms[(roomPtr, entryInfo['screen'], (ap.EntryInfo['SamusX'], ap.EntryInfo['SamusY']))] = ap
|
||||
# for boss rando with incompatible ridley transition, also register this one
|
||||
if ap.Name == 'RidleyRoomIn':
|
||||
rooms[(roomPtr, (0x0, 0x1), 0x5)] = ap
|
||||
rooms[(roomPtr, (0x0, 0x1), (0xbf, 0x198))] = ap
|
||||
|
||||
return rooms
|
||||
|
||||
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape):
|
||||
n = len(possibleTargets)
|
||||
assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets)
|
||||
# first get our list of 4 entries for escape patch
|
||||
if n >= 1:
|
||||
# get actual animals: pick one of the remaining targets
|
||||
animalsAccess = possibleTargets.pop()
|
||||
graph.EscapeAttributes['Animals'] = animalsAccess
|
||||
# we now have at most 3 targets left, fill up to fill cycling 4 targets for animals suprise
|
||||
possibleTargets.append('Climb Bottom Left')
|
||||
if firstEscape is not None:
|
||||
possibleTargets.append(firstEscape)
|
||||
poss = possibleTargets[:]
|
||||
while len(possibleTargets) < 4:
|
||||
possibleTargets.append(poss.pop(random.randint(0, len(poss)-1)))
|
||||
else:
|
||||
# failsafe: if not enough targets left, abort and do vanilla animals
|
||||
animalsAccess = 'Flyway Right'
|
||||
possibleTargets = ['Bomb Torizo Room Left'] * 4
|
||||
GraphUtils.log.debug("escapeAnimalsTransitions. animalsAccess="+animalsAccess)
|
||||
assert len(possibleTargets) == 4, "Invalid possibleTargets list: " + str(possibleTargets)
|
||||
# actually add the 4 connections for successive escapes challenge
|
||||
basePtr = 0xADAC
|
||||
btDoor = getAccessPoint('Flyway Right')
|
||||
for i in range(len(possibleTargets)):
|
||||
ap = copy.copy(btDoor)
|
||||
ap.Name += " " + str(i)
|
||||
ap.ExitInfo['DoorPtr'] = basePtr + i*24
|
||||
graph.addAccessPoint(ap)
|
||||
target = possibleTargets[i]
|
||||
graph.addTransition(ap.Name, target)
|
||||
# add the connection for animals access
|
||||
bt = getAccessPoint('Bomb Torizo Room Left')
|
||||
btCpy = copy.copy(bt)
|
||||
btCpy.Name += " Animals"
|
||||
btCpy.ExitInfo['DoorPtr'] = 0xAE00
|
||||
graph.addAccessPoint(btCpy)
|
||||
graph.addTransition(animalsAccess, btCpy.Name)
|
||||
|
||||
def isHorizontal(dir):
|
||||
# up: 0x3, 0x7
|
||||
# down: 0x2, 0x6
|
||||
# left: 0x1, 0x5
|
||||
# right: 0x0, 0x4
|
||||
return dir in [0x1, 0x5, 0x0, 0x4]
|
||||
|
||||
def removeCap(dir):
|
||||
if dir < 4:
|
||||
return dir
|
||||
return dir - 4
|
||||
|
||||
def getDirection(src, dst):
|
||||
exitDir = src.ExitInfo['direction']
|
||||
entryDir = dst.EntryInfo['direction']
|
||||
# compatible transition
|
||||
if exitDir == entryDir:
|
||||
return exitDir
|
||||
# if incompatible but horizontal we keep entry dir (looks more natural)
|
||||
if GraphUtils.isHorizontal(exitDir) and GraphUtils.isHorizontal(entryDir):
|
||||
return entryDir
|
||||
# otherwise keep exit direction and remove cap
|
||||
return GraphUtils.removeCap(exitDir)
|
||||
|
||||
def getBitFlag(srcArea, dstArea, origFlag):
|
||||
flags = origFlag
|
||||
if srcArea == dstArea:
|
||||
flags &= 0xBF
|
||||
else:
|
||||
flags |= 0x40
|
||||
return flags
|
||||
|
||||
def getDoorConnections(graph, areas=True, bosses=False,
|
||||
escape=True, escapeAnimals=True):
|
||||
transitions = []
|
||||
if areas:
|
||||
transitions += vanillaTransitions
|
||||
if bosses:
|
||||
transitions += vanillaBossesTransitions
|
||||
if escape:
|
||||
transitions += vanillaEscapeTransitions
|
||||
if escapeAnimals:
|
||||
transitions += vanillaEscapeAnimalsTransitions
|
||||
for srcName, dstName in transitions:
|
||||
src = graph.accessPoints[srcName]
|
||||
dst = graph.accessPoints[dstName]
|
||||
dst.EntryInfo.update(src.ExitInfo)
|
||||
src.EntryInfo.update(dst.ExitInfo)
|
||||
connections = []
|
||||
for src, dst in graph.InterAreaTransitions:
|
||||
if not (escape and src.Escape and dst.Escape):
|
||||
# area only
|
||||
if not bosses and src.Boss:
|
||||
continue
|
||||
# boss only
|
||||
if not areas and not src.Boss:
|
||||
continue
|
||||
# no random escape
|
||||
if not escape and src.Escape:
|
||||
continue
|
||||
|
||||
conn = {}
|
||||
conn['ID'] = str(src) + ' -> ' + str(dst)
|
||||
# remove duplicates (loop transitions)
|
||||
if any(c['ID'] == conn['ID'] for c in connections):
|
||||
continue
|
||||
# print(conn['ID'])
|
||||
# where to write
|
||||
conn['DoorPtr'] = src.ExitInfo['DoorPtr']
|
||||
# door properties
|
||||
conn['RoomPtr'] = dst.RoomInfo['RoomPtr']
|
||||
conn['doorAsmPtr'] = dst.EntryInfo['doorAsmPtr']
|
||||
if 'exitAsmPtr' in src.ExitInfo:
|
||||
conn['exitAsmPtr'] = src.ExitInfo['exitAsmPtr']
|
||||
conn['direction'] = GraphUtils.getDirection(src, dst)
|
||||
conn['bitFlag'] = GraphUtils.getBitFlag(src.RoomInfo['area'], dst.RoomInfo['area'],
|
||||
dst.EntryInfo['bitFlag'])
|
||||
conn['cap'] = dst.EntryInfo['cap']
|
||||
conn['screen'] = dst.EntryInfo['screen']
|
||||
if conn['direction'] != src.ExitInfo['direction']: # incompatible transition
|
||||
conn['distanceToSpawn'] = 0
|
||||
conn['SamusX'] = dst.EntryInfo['SamusX']
|
||||
conn['SamusY'] = dst.EntryInfo['SamusY']
|
||||
if dst.Name == 'RidleyRoomIn': # special case: spawn samus on ridley platform
|
||||
conn['screen'] = (0x0, 0x1)
|
||||
else:
|
||||
conn['distanceToSpawn'] = dst.EntryInfo['distanceToSpawn']
|
||||
if 'song' in dst.EntryInfo:
|
||||
conn['song'] = dst.EntryInfo['song']
|
||||
conn['songs'] = dst.RoomInfo['songs']
|
||||
connections.append(conn)
|
||||
return connections
|
||||
|
||||
def getDoorsPtrs2Aps():
|
||||
ret = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
ret[ap.ExitInfo["DoorPtr"]] = ap.Name
|
||||
return ret
|
||||
|
||||
def getAps2DoorsPtrs():
|
||||
ret = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
ret[ap.Name] = ap.ExitInfo["DoorPtr"]
|
||||
return ret
|
||||
|
||||
def getTransitions(addresses):
|
||||
# build address -> name dict
|
||||
doorsPtrs = GraphUtils.getDoorsPtrs2Aps()
|
||||
|
||||
transitions = []
|
||||
# (src.ExitInfo['DoorPtr'], dst.ExitInfo['DoorPtr'])
|
||||
for (srcDoorPtr, destDoorPtr) in addresses:
|
||||
transitions.append((doorsPtrs[srcDoorPtr], doorsPtrs[destDoorPtr]))
|
||||
|
||||
return transitions
|
||||
|
||||
def hasMixedTransitions(areaTransitions, bossTransitions):
|
||||
vanillaAPs = []
|
||||
for (src, dest) in vanillaTransitions:
|
||||
vanillaAPs += [src, dest]
|
||||
|
||||
vanillaBossesAPs = []
|
||||
for (src, dest) in vanillaBossesTransitions:
|
||||
vanillaBossesAPs += [src, dest]
|
||||
|
||||
for (src, dest) in areaTransitions:
|
||||
if src in vanillaBossesAPs or dest in vanillaBossesAPs:
|
||||
return True
|
||||
|
||||
for (src, dest) in bossTransitions:
|
||||
if src in vanillaAPs or dest in vanillaAPs:
|
||||
return True
|
||||
|
||||
return False
|
||||
1532
worlds/sm/variaRandomizer/graph/location.py
Normal file
1532
worlds/sm/variaRandomizer/graph/location.py
Normal file
File diff suppressed because it is too large
Load Diff
0
worlds/sm/variaRandomizer/graph/vanilla/__init__.py
Normal file
0
worlds/sm/variaRandomizer/graph/vanilla/__init__.py
Normal file
758
worlds/sm/variaRandomizer/graph/vanilla/graph_access.py
Normal file
758
worlds/sm/variaRandomizer/graph/vanilla/graph_access.py
Normal file
@@ -0,0 +1,758 @@
|
||||
from graph.graph import AccessPoint
|
||||
from utils.parameters import Settings
|
||||
from rom.rom_patches import RomPatches
|
||||
from logic.smbool import SMBool
|
||||
from logic.helpers import Bosses
|
||||
from logic.cache import Cache
|
||||
|
||||
# all access points and traverse functions
|
||||
accessPoints = [
|
||||
### Ceres Station
|
||||
AccessPoint('Ceres', 'Ceres', {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn': 0xfffe, 'doors':[0x32], 'patches':[RomPatches.BlueBrinstarBlueDoor], 'solveArea': "Crateria Landing Site"}),
|
||||
### Crateria and Blue Brinstar
|
||||
AccessPoint('Landing Site', 'Crateria', {
|
||||
'Lower Mushrooms Left': Cache.ldeco(lambda sm: sm.wand(sm.canPassTerminatorBombWall(),
|
||||
sm.canPassCrateriaGreenPirates())),
|
||||
'Keyhunter Room Bottom': Cache.ldeco(lambda sm: sm.traverse('LandingSiteRight')),
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn': 0x0000, 'doors':[0x32], 'patches':[RomPatches.BlueBrinstarBlueDoor], 'solveArea': "Crateria Landing Site"}),
|
||||
AccessPoint('Blue Brinstar Elevator Bottom', 'Crateria', {
|
||||
'Morph Ball Room Left': lambda sm: sm.canUsePowerBombs(),
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, internal=True),
|
||||
AccessPoint('Gauntlet Top', 'Crateria', {
|
||||
'Green Pirates Shaft Bottom Right': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'), sm.canPassCrateriaGreenPirates()))
|
||||
}, internal=True,
|
||||
start={'spawn': 0x0006, 'solveArea': "Crateria Gauntlet", 'save':"Save_Gauntlet", 'forcedEarlyMorph':True}),
|
||||
AccessPoint('Lower Mushrooms Left', 'Crateria', {
|
||||
'Landing Site': Cache.ldeco(lambda sm: sm.wand(sm.canPassTerminatorBombWall(False),
|
||||
sm.canPassCrateriaGreenPirates())),
|
||||
'Green Pirates Shaft Bottom Right': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x9969, "area": 0x0, 'songs':[0x997a]},
|
||||
exitInfo = {'DoorPtr':0x8c22, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x36, 'SamusY':0x88, 'song': 0x9},
|
||||
dotOrientation = 'nw'),
|
||||
AccessPoint('Green Pirates Shaft Bottom Right', 'Crateria', {
|
||||
'Lower Mushrooms Left': lambda sm: SMBool(True)
|
||||
}, traverse = Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoMoreBlueDoors),
|
||||
sm.traverse('GreenPiratesShaftBottomRight'))),
|
||||
roomInfo = {'RoomPtr':0x99bd, "area": 0x0, 'songs':[0x99ce]},
|
||||
# the doorAsmPtr 7FE00 is set by the g4_skip.ips patch, we have to call it
|
||||
exitInfo = {'DoorPtr':0x8c52, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xfe00},
|
||||
entryInfo = {'SamusX':0xcc, 'SamusY':0x688, 'song': 0x9},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Moat Right', 'Crateria', {
|
||||
'Moat Left': lambda sm: sm.canPassMoatReverse()
|
||||
}, roomInfo = {'RoomPtr':0x95ff, "area": 0x0, 'songs':[0x9610]},
|
||||
exitInfo = {'DoorPtr':0x8aea, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x1cf, 'SamusY':0x88, 'song': 0xc},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Moat Left', 'Crateria', {
|
||||
'Keyhunter Room Bottom': lambda sm: SMBool(True),
|
||||
'Moat Right': lambda sm: sm.canPassMoatFromMoat()
|
||||
}, internal=True),
|
||||
AccessPoint('Keyhunter Room Bottom', 'Crateria', {
|
||||
'Moat Left': Cache.ldeco(lambda sm: sm.traverse('KihunterRight')),
|
||||
'Moat Right': Cache.ldeco(lambda sm: sm.wand(sm.traverse('KihunterRight'), sm.canPassMoat())),
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, traverse = Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoMoreBlueDoors),
|
||||
sm.traverse('KihunterBottom'))),
|
||||
roomInfo = { 'RoomPtr':0x948c, "area": 0x0, 'songs':[0x949d] },
|
||||
exitInfo = {'DoorPtr':0x8a42, 'direction': 0x6, "cap": (0x6, 0x2), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x14c, 'SamusY':0x2b8, 'song': 0xc},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Morph Ball Room Left', 'Crateria', {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: sm.canUsePowerBombs()
|
||||
}, roomInfo = { 'RoomPtr':0x9e9f, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x8e9e, 'direction': 0x5, "cap": (0x1e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x288},
|
||||
dotOrientation = 'sw'),
|
||||
# Escape APs
|
||||
AccessPoint('Climb Bottom Left', 'Crateria', {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x96ba, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8b6e, 'direction': 0x5, "cap": (0x2e, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x888},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Flyway Right', 'Crateria', {},
|
||||
roomInfo = {'RoomPtr':0x9879, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8bc2, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000,
|
||||
"exitAsmPtr": 0xf030}, # setup_next_escape in rando_escape.asm
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True),
|
||||
AccessPoint('Bomb Torizo Room Left', 'Crateria', {},
|
||||
roomInfo = {'RoomPtr':0x9804, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8baa, 'direction': 0x5, "cap": (0x2e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0xb8},
|
||||
escape = True),
|
||||
### Green and Pink Brinstar
|
||||
AccessPoint('Green Brinstar Elevator', 'GreenPinkBrinstar', {
|
||||
'Big Pink': Cache.ldeco(lambda sm: sm.wand(sm.canPassDachoraRoom(),
|
||||
sm.traverse('MainShaftBottomRight'))),
|
||||
'Etecoons Bottom': lambda sm: sm.canAccessEtecoons()
|
||||
}, roomInfo = {'RoomPtr':0x9938, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8bfe, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xcc, 'SamusY':0x88},
|
||||
start = {'spawn': 0x0108, 'doors':[0x1f, 0x21, 0x26], 'patches':[RomPatches.BrinReserveBlueDoors], 'solveArea': "Green Brinstar"}, # XXX test if it would be better in brin reserve room with custom save
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Big Pink', 'GreenPinkBrinstar', {
|
||||
'Green Hill Zone Top Right': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.traverse('BigPinkBottomRight'))),
|
||||
'Green Brinstar Elevator': lambda sm: sm.canPassDachoraRoom()
|
||||
}, internal=True, start={'spawn': 0x0100, 'solveArea': "Pink Brinstar"}),
|
||||
AccessPoint('Green Hill Zone Top Right', 'GreenPinkBrinstar', {
|
||||
'Noob Bridge Right': lambda sm: SMBool(True),
|
||||
'Big Pink': Cache.ldeco(lambda sm: sm.haveItem('Morph'))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors), sm.traverse('GreenHillZoneTopRight'))),
|
||||
roomInfo = {'RoomPtr':0x9e52, "area": 0x1 },
|
||||
exitInfo = {'DoorPtr':0x8e86, 'direction': 0x4, "cap": (0x1, 0x26), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x2), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x1c7, 'SamusY':0x88},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Noob Bridge Right', 'GreenPinkBrinstar', {
|
||||
'Green Hill Zone Top Right': Cache.ldeco(lambda sm: sm.wor(sm.haveItem('Wave'),
|
||||
sm.wor(sm.canBlueGateGlitch(),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther))))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors), sm.traverse('NoobBridgeRight'))),
|
||||
roomInfo = {'RoomPtr':0x9fba, "area": 0x1 },
|
||||
exitInfo = {'DoorPtr':0x8f0a, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x5ce, 'SamusY':0x88},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Green Brinstar Main Shaft Top Left', 'GreenPinkBrinstar', {
|
||||
'Green Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x9ad9, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x8cb2, 'direction': 0x5, "cap": (0x2e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x488},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Brinstar Pre-Map Room Right', 'GreenPinkBrinstar', {
|
||||
}, roomInfo = {'RoomPtr':0x9b9d, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x8d42, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Etecoons Supers', 'GreenPinkBrinstar', {
|
||||
'Etecoons Bottom': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn': 0x0107, 'doors':[0x34], 'patches':[RomPatches.EtecoonSupersBlueDoor],
|
||||
'save':"Save_Etecoons" ,'solveArea': "Green Brinstar",
|
||||
'forcedEarlyMorph':True, 'needsPreRando': True}),
|
||||
AccessPoint('Etecoons Bottom', 'GreenPinkBrinstar', {
|
||||
'Etecoons Supers': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.EtecoonSupersBlueDoor),
|
||||
sm.traverse('EtecoonEnergyTankLeft'))),
|
||||
'Green Brinstar Elevator': lambda sm: sm.canUsePowerBombs()
|
||||
}, internal=True),
|
||||
### Wrecked Ship
|
||||
AccessPoint('West Ocean Left', 'WreckedShip', {
|
||||
'Wrecked Ship Main': Cache.ldeco(lambda sm: sm.traverse('WestOceanRight'))
|
||||
}, roomInfo = {'RoomPtr':0x93fe, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x89ca, 'direction': 0x5, "cap": (0x1e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x488},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Wrecked Ship Main', 'WreckedShip', {
|
||||
'West Ocean Left': lambda sm: SMBool(True),
|
||||
'Wrecked Ship Back': Cache.ldeco(lambda sm: sm.wor(sm.wand(Bosses.bossDead(sm, 'Phantoon'),
|
||||
sm.canPassSpongeBath()),
|
||||
sm.wand(sm.wnot(Bosses.bossDead(sm, 'Phantoon')),
|
||||
RomPatches.has(sm.player, RomPatches.SpongeBathBlueDoor)))),
|
||||
'PhantoonRoomOut': Cache.ldeco(lambda sm: sm.wand(sm.traverse('WreckedShipMainShaftBottom'), sm.canPassBombPassages()))
|
||||
}, internal=True,
|
||||
start={'spawn':0x0300,
|
||||
'doors':[0x83,0x8b], 'patches':[RomPatches.SpongeBathBlueDoor, RomPatches.WsEtankBlueDoor],
|
||||
'solveArea': "WreckedShip Main",
|
||||
'needsPreRando':True}),
|
||||
AccessPoint('Wrecked Ship Back', 'WreckedShip', {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True),
|
||||
'Crab Maze Left': Cache.ldeco(lambda sm: sm.canPassForgottenHighway(True))
|
||||
}, internal=True),
|
||||
AccessPoint('Crab Maze Left', 'WreckedShip', {
|
||||
'Wrecked Ship Back': Cache.ldeco(lambda sm: sm.canPassForgottenHighway(False))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors),
|
||||
sm.traverse('LeCoudeBottom'))), # it is not exactly coude's door
|
||||
# but it's equivalent in vanilla anyway
|
||||
roomInfo = {'RoomPtr':0x957d, "area": 0x0, 'songs':[0x958e]},
|
||||
exitInfo = {'DoorPtr':0x8aae, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x188, 'song': 0xc},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('PhantoonRoomOut', 'WreckedShip', {
|
||||
'Wrecked Ship Main': lambda sm: sm.canPassBombPassages()
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xcc6f, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2ac, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x49f, 'SamusY':0xb8},
|
||||
traverse=lambda sm: sm.canOpenEyeDoors(),
|
||||
dotOrientation = 's'),
|
||||
AccessPoint('PhantoonRoomIn', 'WreckedShip', {},
|
||||
boss = True,
|
||||
roomInfo = {'RoomPtr':0xcd13, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2c4, 'direction': 0x5, "cap": (0x4e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x4, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe1fe,
|
||||
"exitAsmPtr": 0xf7f0},
|
||||
entryInfo = {'SamusX':0x2e, 'SamusY':0xb8},
|
||||
dotOrientation = 's'),
|
||||
AccessPoint('Basement Left', 'WreckedShip', {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xcc6f, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2a0, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x2e, 'SamusY':0x88},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Wrecked Ship Map Room', 'WreckedShip', {
|
||||
}, roomInfo = {'RoomPtr':0xcccb, "area": 0x3},
|
||||
exitInfo = {'DoorPtr':0xa2b8, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
### Lower Norfair
|
||||
AccessPoint('Lava Dive Right', 'LowerNorfair', {
|
||||
'LN Entrance': lambda sm: sm.canPassLavaPit()
|
||||
}, roomInfo = {'RoomPtr':0xaf14, "area": 0x2, 'songs':[0xaf25]},
|
||||
exitInfo = {'DoorPtr':0x96d2, 'direction': 0x4, "cap": (0x11, 0x26), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x2), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x3d0, 'SamusY':0x88, 'song': 0x15},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('LN Entrance', 'LowerNorfair', {
|
||||
'Lava Dive Right': lambda sm: sm.canPassLavaPitReverse(),
|
||||
'LN Above GT': lambda sm: sm.canPassLowerNorfairChozo(),
|
||||
'Screw Attack Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canGreenGateGlitch(),
|
||||
sm.canDestroyBombWalls())),
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canPassWorstRoom(),
|
||||
sm.canUsePowerBombs()))
|
||||
}, internal=True),
|
||||
AccessPoint('LN Above GT', 'LowerNorfair', {
|
||||
'Screw Attack Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.enoughStuffGT()))
|
||||
}, internal=True),
|
||||
AccessPoint('Screw Attack Bottom', 'LowerNorfair', {
|
||||
'LN Entrance': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canExitScrewAttackArea(),
|
||||
sm.haveItem('Super'),
|
||||
sm.canUsePowerBombs()))
|
||||
}, internal=True),
|
||||
AccessPoint('Firefleas', 'LowerNorfair', {
|
||||
'LN Entrance': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canPassAmphitheaterReverse(),
|
||||
sm.canPassWorstRoomPirates(),
|
||||
sm.canUsePowerBombs())),
|
||||
'Three Muskateers Room Left': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.haveItem('Morph'),
|
||||
# check for only 3 ki hunters this way
|
||||
sm.canPassRedKiHunters())),
|
||||
'Ridley Zone': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.traverse('WastelandLeft'),
|
||||
sm.traverse('RedKihunterShaftBottom'),
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassRedKiHunters(),
|
||||
sm.canPassWastelandDessgeegas(),
|
||||
sm.canPassNinjaPirates())),
|
||||
'Screw Attack Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canPassAmphitheaterReverse(),
|
||||
sm.canDestroyBombWalls(),
|
||||
sm.canGreenGateGlitch())),
|
||||
'Firefleas Top': Cache.ldeco(lambda sm: sm.wand(sm.canPassBombPassages(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Firefleas Top', 'LowerNorfair', {
|
||||
# this weird condition basically says: "if we start here, give heat protection"
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wor(sm.wnot(RomPatches.has(sm.player, RomPatches.LowerNorfairPBRoomHeatDisable)),
|
||||
sm.heatProof()))
|
||||
}, internal=True,
|
||||
start={'spawn':0x0207,
|
||||
'rom_patches': ['LN_PB_Heat_Disable', 'LN_Firefleas_Remove_Fune','firefleas_shot_block.ips'],
|
||||
'patches':[RomPatches.LowerNorfairPBRoomHeatDisable, RomPatches.FirefleasRemoveFune],
|
||||
'knows': ["FirefleasWalljump"],
|
||||
'save': "Save_Firefleas", 'needsPreRando': True,
|
||||
'solveArea': "Lower Norfair After Amphitheater",
|
||||
'forcedEarlyMorph':True}),
|
||||
AccessPoint('Ridley Zone', 'LowerNorfair', {
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canGetBackFromRidleyZone(),
|
||||
sm.canPassWastelandDessgeegas(),
|
||||
sm.canPassRedKiHunters())),
|
||||
'RidleyRoomOut': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']))
|
||||
}, internal=True),
|
||||
AccessPoint('Three Muskateers Room Left', 'LowerNorfair', {
|
||||
'Firefleas': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.haveItem('Morph'),
|
||||
sm.canPassThreeMuskateers()))
|
||||
}, roomInfo = {'RoomPtr':0xb656, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x9a4a, 'direction': 0x5, "cap": (0x5e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x5, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x134, 'SamusY':0x88},
|
||||
dotOrientation = 'n'),
|
||||
AccessPoint('RidleyRoomOut', 'LowerNorfair', {
|
||||
'Ridley Zone': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']))
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xb37a, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x98ca, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x2e, 'SamusY':0x98},
|
||||
traverse=Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canOpenEyeDoors())),
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('RidleyRoomIn', 'LowerNorfair', {},
|
||||
boss = True,
|
||||
roomInfo = {'RoomPtr':0xb32e, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x98be, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0xbf, 'SamusY':0x198}, # on Ridley's platform. entry screen has to be changed (see getDoorConnections)
|
||||
dotOrientation = 'e'),
|
||||
### Kraid
|
||||
AccessPoint('Warehouse Zeela Room Left', 'Kraid', {
|
||||
'KraidRoomOut': lambda sm: sm.canPassBombPassages()
|
||||
}, roomInfo = {'RoomPtr': 0xa471, "area": 0x1, 'songs':[0xa482]},
|
||||
exitInfo = {'DoorPtr': 0x913e, 'direction': 0x5, "cap": (0x2e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x2, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xbd3f},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88, 'song':0x12},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('KraidRoomOut', 'Kraid', {
|
||||
'Warehouse Zeela Room Left': lambda sm: sm.canPassBombPassages()
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xa56b, "area": 0x1,
|
||||
# put red brin song in both pre-kraid rooms,
|
||||
# (vanilla music only makes sense if kraid is
|
||||
# vanilla)
|
||||
"songs":[0xa57c,0xa537,0xa551]},
|
||||
exitInfo = {'DoorPtr':0x91b6, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x1cd, 'SamusY':0x188, 'song':0x12},
|
||||
traverse=lambda sm: sm.canOpenEyeDoors(),
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('KraidRoomIn', 'Kraid', {},
|
||||
boss = True,
|
||||
roomInfo = {'RoomPtr':0xa59f, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x91ce, 'direction': 0x5, "cap": (0x1e, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x188},
|
||||
dotOrientation = 'e'),
|
||||
### Norfair
|
||||
AccessPoint('Warehouse Entrance Left', 'Norfair', {
|
||||
'Warehouse Entrance Right': lambda sm: sm.canAccessKraidsLair(),
|
||||
'Business Center': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xa6a1, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x922e, 'direction': 0x5, "cap": (0xe, 0x16), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xbdd1},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88},
|
||||
dotOrientation = 'sw'),
|
||||
AccessPoint('Warehouse Entrance Right', 'Norfair', {
|
||||
'Warehouse Entrance Left': Cache.ldeco(lambda sm: sm.haveItem('Super'))
|
||||
}, roomInfo = {'RoomPtr': 0xa6a1, "area": 0x1},
|
||||
exitInfo = {'DoorPtr': 0x923a, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX': 0x2c7, 'SamusY': 0x98},
|
||||
dotOrientation = 'nw'),
|
||||
AccessPoint('Business Center', 'Norfair', {
|
||||
'Cathedral': Cache.ldeco(lambda sm: sm.canEnterCathedral(Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Cathedral Missiles']['mult'])),
|
||||
'Bubble Mountain': Cache.ldeco(# go through cathedral
|
||||
lambda sm: sm.wand(sm.traverse('CathedralRight'),
|
||||
sm.canEnterCathedral(Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Bubble']['mult']))),
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.haveItem('SpeedBooster')), # frog speedway
|
||||
'Crocomire Speedway Bottom': Cache.ldeco(lambda sm: sm.wor(sm.wand(sm.haveItem('SpeedBooster'), # frog speedway
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Croc via Frog w/Wave' if sm.haveItem('Wave') else 'Norfair Entrance -> Croc via Frog']),
|
||||
sm.wor(sm.canBlueGateGlitch(),
|
||||
sm.haveItem('Wave'))),
|
||||
# below ice
|
||||
sm.wand(sm.traverse('BusinessCenterTopLeft'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['Ice']['Norfair Entrance -> Croc via Ice'])))),
|
||||
'Warehouse Entrance Left': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start={'spawn':0x0208, 'doors':[0x4d], 'patches':[RomPatches.HiJumpAreaBlueDoor], 'solveArea': "Norfair Entrance", 'needsPreRando':True}),
|
||||
AccessPoint('Single Chamber Top Right', 'Norfair', {
|
||||
'Bubble Mountain Top': Cache.ldeco(lambda sm: sm.wand(sm.canDestroyBombWalls(),
|
||||
sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Single Chamber <-> Bubble Mountain'])))
|
||||
}, roomInfo = {'RoomPtr':0xad5e, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x95fa, 'direction': 0x4, "cap": (0x11, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x5cf, 'SamusY':0x88},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Cathedral', 'Norfair', {
|
||||
'Business Center': Cache.ldeco(lambda sm: sm.canExitCathedral(Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Cathedral Missiles'])),
|
||||
'Bubble Mountain': Cache.ldeco(lambda sm: sm.wand(sm.traverse('CathedralRight'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Norfair Entrance -> Cathedral Missiles'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Kronic Boost Room Bottom Left', 'Norfair', {
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Single Chamber <-> Bubble Mountain'])),
|
||||
'Bubble Mountain Top': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Kronic Boost Room -> Bubble Mountain Top']))), # go all the way around
|
||||
'Crocomire Speedway Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Kronic Boost Room <-> Croc']),
|
||||
sm.wor(sm.haveItem('Wave'),
|
||||
sm.canBlueGateGlitch()))),
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoBlueDoors), sm.traverse('KronicBoostBottomLeft'))),
|
||||
roomInfo = {'RoomPtr':0xae74, "area": 0x2, 'songs':[0xae85]},
|
||||
exitInfo = {'DoorPtr':0x967e, 'direction': 0x5, "cap": (0x3e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x3, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x134, 'SamusY':0x288, 'song': 0x15},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Crocomire Speedway Bottom', 'Norfair', {
|
||||
'Business Center': Cache.ldeco(lambda sm: sm.wor(sm.wand(sm.canPassFrogSpeedwayRightToLeft(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['Ice']['Croc -> Norfair Entrance'])),
|
||||
sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Croc -> Norfair Entrance']),
|
||||
sm.canGrappleEscape(),
|
||||
sm.haveItem('Super')))),
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['Ice']['Croc -> Bubble Mountain'])),
|
||||
'Kronic Boost Room Bottom Left': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Kronic Boost Room <-> Croc']),
|
||||
sm.haveItem('Morph')))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.CrocBlueDoors), sm.traverse('CrocomireSpeedwayBottom'))),
|
||||
roomInfo = {'RoomPtr':0xa923, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x93d2, 'direction': 0x6, "cap": (0x36, 0x2), "bitFlag": 0x0,
|
||||
"screen": (0x3, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xc57, 'SamusY':0x2b8},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Bubble Mountain', 'Norfair', {
|
||||
'Business Center': lambda sm: sm.canExitCathedral(Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Entrance']),
|
||||
'Bubble Mountain Top': lambda sm: sm.canClimbBubbleMountain(),
|
||||
'Cathedral': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Cathedral Missiles'])),
|
||||
'Bubble Mountain Bottom': lambda sm: sm.canPassBombPassages()
|
||||
}, internal=True,
|
||||
start={'spawn':0x0201, 'doors':[0x54,0x55], 'patches':[RomPatches.SpeedAreaBlueDoors], 'knows':['BubbleMountainWallJump'], 'solveArea': "Bubble Norfair Bottom"}),
|
||||
AccessPoint('Bubble Mountain Top', 'Norfair', {
|
||||
'Kronic Boost Room Bottom Left': Cache.ldeco(# go all the way around
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Kronic Boost Room wo/Bomb']))),
|
||||
'Single Chamber Top Right': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Single Chamber <-> Bubble Mountain']),
|
||||
sm.canDestroyBombWalls(),
|
||||
sm.haveItem('Morph'),
|
||||
RomPatches.has(sm.player, RomPatches.SingleChamberNoCrumble))),
|
||||
'Bubble Mountain': lambda sm: SMBool(True),
|
||||
# all the way around
|
||||
'Bubble Mountain Bottom': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble Top <-> Bubble Bottom'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Bubble Mountain Bottom', 'Norfair', {
|
||||
'Bubble Mountain': lambda sm: sm.canPassBombPassages(),
|
||||
'Crocomire Speedway Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Croc']),
|
||||
sm.wor(sm.canBlueGateGlitch(),
|
||||
sm.haveItem('Wave')))),
|
||||
'Kronic Boost Room Bottom Left': Cache.ldeco(lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Kronic Boost Room'])),
|
||||
'Business Center': lambda sm: sm.canPassFrogSpeedwayRightToLeft(),
|
||||
# all the way around
|
||||
'Bubble Mountain Top': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble Top <-> Bubble Bottom'])))
|
||||
}, internal=True),
|
||||
AccessPoint('Business Center Mid Left', 'Norfair', {
|
||||
'Warehouse Entrance Left': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xa7de, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x9306, 'direction': 0x5, "cap": (0xe, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x488},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Norfair Map Room', 'Norfair', {
|
||||
}, roomInfo = {'RoomPtr':0xb0b4, "area": 0x2},
|
||||
exitInfo = {'DoorPtr':0x97c2, 'direction': 0x4, "cap": (0x1, 0x46), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x4), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
### Croc
|
||||
AccessPoint('Crocomire Room Top', 'Crocomire', {
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.CrocBlueDoors), sm.enoughStuffCroc())),
|
||||
roomInfo = {'RoomPtr':0xa98d, "area": 0x2, 'songs':[0xa9bd]},
|
||||
exitInfo = {'DoorPtr':0x93ea, 'direction': 0x7, "cap": (0xc6, 0x2d), "bitFlag": 0x0,
|
||||
"screen": (0xc, 0x2), "distanceToSpawn": 0x1c0, "doorAsmPtr": 0x0000,
|
||||
"exitAsmPtr": 0xf7f0},
|
||||
entryInfo = {'SamusX':0x383, 'SamusY':0x98, 'song': 0x15},
|
||||
dotOrientation = 'se'),
|
||||
### West Maridia
|
||||
AccessPoint('Main Street Bottom', 'WestMaridia', {
|
||||
'Red Fish Room Left': Cache.ldeco(lambda sm: sm.wand(sm.canGoUpMtEverest(),
|
||||
sm.haveItem('Morph'))),
|
||||
'Crab Hole Bottom Left': Cache.ldeco(lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canTraverseCrabTunnelLeftToRight())),
|
||||
# this transition leads to EastMaridia directly
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.traverse('MainStreetBottomRight'),
|
||||
sm.wor(sm.haveItem('Super'),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)),
|
||||
sm.canTraverseWestSandHallLeftToRight())),
|
||||
'Crab Shaft Left': lambda sm: sm.canPassMtEverest()
|
||||
}, roomInfo = {'RoomPtr':0xcfc9, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa39c, 'direction': 0x6, "cap": (0x6, 0x2), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x170, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x14a, 'SamusY':0x7a8},
|
||||
dotOrientation = 's'),
|
||||
AccessPoint('Mama Turtle', 'WestMaridia', {
|
||||
'Main Street Bottom': lambda sm: sm.canJumpUnderwater()
|
||||
}, internal=True,
|
||||
start = {'spawn': 0x0406, 'solveArea': "Maridia Green",
|
||||
'save':"Save_Mama", 'needsPreRando':True,
|
||||
'patches':[RomPatches.MamaTurtleBlueDoor],
|
||||
'rom_patches':['mama_save.ips'], 'doors': [0x8e]}),
|
||||
AccessPoint('Crab Hole Bottom Left', 'WestMaridia', {
|
||||
'Main Street Bottom': Cache.ldeco(lambda sm: sm.wand(sm.canExitCrabHole(),
|
||||
sm.wor(sm.canGreenGateGlitch(),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)))),
|
||||
# this transition leads to EastMaridia directly
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.canExitCrabHole(),
|
||||
sm.canTraverseWestSandHallLeftToRight()))
|
||||
}, roomInfo = {'RoomPtr':0xd21c, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa510, 'direction': 0x5,
|
||||
"cap": (0x3e, 0x6), "screen": (0x3, 0x0), "bitFlag": 0x0,
|
||||
"distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x28, 'SamusY':0x188},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('Red Fish Room Left', 'WestMaridia', {
|
||||
'Main Street Bottom': Cache.ldeco(lambda sm: sm.haveItem('Morph')) # just go down
|
||||
}, roomInfo = {'RoomPtr':0xd104, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa480, 'direction': 0x5, "cap": (0x2e, 0x36), "bitFlag": 0x40,
|
||||
"screen": (0x2, 0x3), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe367},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Crab Shaft Left', 'WestMaridia', {
|
||||
'Main Street Bottom': lambda sm: SMBool(True), # fall down
|
||||
'Beach': lambda sm: sm.canDoOuterMaridia(),
|
||||
'Crab Shaft Right': lambda sm: SMBool(True)
|
||||
}, internal=True),
|
||||
AccessPoint('Watering Hole', 'WestMaridia', {
|
||||
'Beach': lambda sm: sm.haveItem('Morph'),
|
||||
'Watering Hole Bottom': lambda sm: SMBool(True)
|
||||
}, internal=True,
|
||||
start = {'spawn': 0x0407, 'solveArea': "Maridia Pink Bottom", 'save':"Save_Watering_Hole",
|
||||
'patches':[RomPatches.MaridiaTubeOpened], 'rom_patches':['wh_open_tube.ips'],
|
||||
'forcedEarlyMorph':True}),
|
||||
AccessPoint('Watering Hole Bottom', 'WestMaridia', {
|
||||
'Watering Hole': lambda sm: sm.canJumpUnderwater()
|
||||
}, internal=True),
|
||||
AccessPoint('Beach', 'WestMaridia', {
|
||||
'Crab Shaft Left': lambda sm: SMBool(True), # fall down
|
||||
'Watering Hole': Cache.ldeco(lambda sm: sm.wand(sm.wor(sm.canPassBombPassages(),
|
||||
sm.canUseSpringBall()),
|
||||
sm.canDoOuterMaridia()))
|
||||
}, internal=True),
|
||||
AccessPoint('Crab Shaft Right', 'WestMaridia', {
|
||||
'Crab Shaft Left': lambda sm: sm.canJumpUnderwater()
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.CrabShaftBlueDoor),
|
||||
sm.traverse('CrabShaftRight'))),
|
||||
roomInfo = {'RoomPtr':0xd1a3, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa4c8, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x1ca, 'SamusY':0x388},
|
||||
dotOrientation = 'e'),
|
||||
# escape APs
|
||||
AccessPoint('Crab Hole Bottom Right', 'WestMaridia', {
|
||||
'Crab Hole Bottom Left': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xd21c, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa51c, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xd7, 'SamusY':0x188},
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Maridia Map Room', 'WestMaridia', {
|
||||
}, roomInfo = {'RoomPtr':0xd3b6, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa5e8, 'direction': 0x5, "cap": (0xe, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe356},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
### East Maridia
|
||||
AccessPoint('Aqueduct Top Left', 'EastMaridia', {
|
||||
'Aqueduct Bottom': lambda sm: sm.canUsePowerBombs()
|
||||
}, roomInfo = {'RoomPtr':0xd5a7, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa708, 'direction': 0x5, "cap": (0x1e, 0x36), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x3), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe398},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x188},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Aqueduct Bottom', 'EastMaridia', {
|
||||
'Aqueduct Top Left': Cache.ldeco(lambda sm: sm.wand(sm.canDestroyBombWallsUnderwater(), # top left bomb blocks
|
||||
sm.canJumpUnderwater())),
|
||||
'Post Botwoon': Cache.ldeco(lambda sm: sm.wand(sm.canJumpUnderwater(),
|
||||
sm.canDefeatBotwoon())), # includes botwoon hallway conditions
|
||||
'Left Sandpit': lambda sm: sm.canAccessSandPits(),
|
||||
'Right Sandpit': lambda sm: sm.canAccessSandPits(),
|
||||
'Aqueduct': Cache.ldeco(lambda sm: sm.wand(sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.wand(sm.knowsSnailClip(),
|
||||
sm.haveItem('Morph'))),
|
||||
sm.haveItem('Gravity')))
|
||||
}, internal=True),
|
||||
AccessPoint('Aqueduct', 'EastMaridia', {
|
||||
'Aqueduct Bottom': lambda sm: SMBool(True) # go down
|
||||
}, internal=True,
|
||||
start = {'spawn': 0x0405, 'solveArea': "Maridia Pink Bottom",
|
||||
'save':"Save_Aqueduct", 'needsPreRando':True,
|
||||
'doors': [0x96]}),
|
||||
AccessPoint('Post Botwoon', 'EastMaridia', {
|
||||
'Aqueduct Bottom': Cache.ldeco(lambda sm: sm.wor(sm.wand(sm.canJumpUnderwater(), # can't access the sand pits from the right side of the room
|
||||
sm.haveItem('Morph')),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpeedBooster')))),
|
||||
'Colosseum Top Right': lambda sm: sm.canBotwoonExitToColosseum(),
|
||||
'Toilet Top': Cache.ldeco(lambda sm: sm.wand(sm.canReachCacatacAlleyFromBotowoon(),
|
||||
sm.canPassCacatacAlley()))
|
||||
}, internal=True),
|
||||
AccessPoint('West Sand Hall Left', 'EastMaridia', {
|
||||
# XXX there might be some tech to do this suitless, but HJ+ice is not enough
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.haveItem('Gravity')),
|
||||
'Aqueduct Bottom': Cache.ldeco(lambda sm: RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
# this goes directly to WestMaridia
|
||||
'Main Street Bottom': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.wor(sm.canGreenGateGlitch(),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)))),
|
||||
# this goes directly to WestMaridia
|
||||
'Crab Hole Bottom Left': Cache.ldeco(lambda sm: sm.wand(sm.wnot(RomPatches.has(sm.player, RomPatches.MaridiaSandWarp)),
|
||||
sm.haveItem('Morph')))
|
||||
}, internal=True),
|
||||
AccessPoint('Left Sandpit', 'EastMaridia', {
|
||||
'West Sand Hall Left': lambda sm: sm.canAccessSandPits(),
|
||||
'Oasis Bottom': lambda sm: sm.canAccessSandPits()
|
||||
}, internal=True),
|
||||
AccessPoint('Oasis Bottom', 'EastMaridia', {
|
||||
'Toilet Top': Cache.ldeco(lambda sm: sm.wand(sm.traverse('OasisTop'), sm.canDestroyBombWallsUnderwater())),
|
||||
'West Sand Hall Left': lambda sm: sm.canAccessSandPits()
|
||||
}, internal=True),
|
||||
AccessPoint('Right Sandpit', 'EastMaridia', {
|
||||
'Oasis Bottom': lambda sm: sm.canAccessSandPits()
|
||||
}, internal=True),
|
||||
AccessPoint('Le Coude Right', 'EastMaridia', {
|
||||
'Toilet Top': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0x95a8, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8aa2, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xd1, 'SamusY':0x88},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Toilet Top', 'EastMaridia', {
|
||||
'Oasis Bottom': Cache.ldeco(lambda sm: sm.wand(sm.traverse('PlasmaSparkBottom'), sm.canDestroyBombWallsUnderwater())),
|
||||
'Le Coude Right': lambda sm: SMBool(True),
|
||||
'Colosseum Top Right': Cache.ldeco(lambda sm: sm.wand(Bosses.bossDead(sm, 'Draygon'),
|
||||
# suitless could be possible with this but unreasonable: https://youtu.be/rtLwytH-u8o
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('Morph')))
|
||||
}, internal=True),
|
||||
AccessPoint('Colosseum Top Right', 'EastMaridia', {
|
||||
'Post Botwoon': lambda sm: sm.canColosseumToBotwoonExit(),
|
||||
'Precious Room Top': Cache.ldeco(lambda sm: sm.traverse('ColosseumBottomRight')), # go down
|
||||
}, internal = True),
|
||||
AccessPoint('Precious Room Top', 'EastMaridia', {
|
||||
'Colosseum Top Right': lambda sm: sm.canClimbColosseum(),
|
||||
'DraygonRoomOut': lambda sm: SMBool(True) # go down
|
||||
}, internal = True),
|
||||
# boss APs
|
||||
AccessPoint('DraygonRoomOut', 'EastMaridia', {
|
||||
'Precious Room Top': lambda sm: sm.canExitPreciousRoom()
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xd78f, "area": 0x4, "songs":[0xd7a5]},
|
||||
exitInfo = {'DoorPtr':0xa840, 'direction': 0x5, "cap": (0x1e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x288, 'song':0x1b},
|
||||
traverse=lambda sm: sm.canOpenEyeDoors(),
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('DraygonRoomIn', 'EastMaridia', {
|
||||
'Draygon Room Bottom': Cache.ldeco(lambda sm: sm.wor(Bosses.bossDead(sm, "Draygon"),
|
||||
sm.wand(sm.canFightDraygon(),
|
||||
sm.enoughStuffsDraygon())))
|
||||
}, boss = True,
|
||||
roomInfo = {'RoomPtr':0xda60, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa96c, 'direction': 0x4, "cap": (0x1, 0x26), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x2), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe3d9,
|
||||
"exitAsmPtr": 0xf7f0},
|
||||
entryInfo = {'SamusX':0x1c8, 'SamusY':0x88},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Draygon Room Bottom', 'EastMaridia', {
|
||||
'DraygonRoomIn': Cache.ldeco(lambda sm: sm.wand(Bosses.bossDead(sm, 'Draygon'), sm.canExitDraygon()))
|
||||
}, internal = True),
|
||||
### Red Brinstar. Main nodes: Red Tower Top Left, East Tunnel Right
|
||||
AccessPoint('Red Tower Top Left', 'RedBrinstar', {
|
||||
# go up
|
||||
'Red Brinstar Elevator': lambda sm: sm.canClimbRedTower(),
|
||||
'Caterpillar Room Top Right': Cache.ldeco(lambda sm: sm.wand(sm.canPassRedTowerToMaridiaNode(),
|
||||
sm.canClimbRedTower())),
|
||||
# go down
|
||||
'East Tunnel Right': lambda sm: SMBool(True)
|
||||
}, roomInfo = {'RoomPtr':0xa253, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x902a, 'direction': 0x5, "cap": (0x5e, 0x6), "bitFlag": 0x0,
|
||||
"screen": (0x5, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x2f, 'SamusY':0x488},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Caterpillar Room Top Right', 'RedBrinstar', {
|
||||
'Red Brinstar Elevator': lambda sm: sm.canPassMaridiaToRedTowerNode()
|
||||
}, roomInfo = {'RoomPtr':0xa322, "area": 0x1},
|
||||
exitInfo = {'DoorPtr':0x90c6, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xbdaf},
|
||||
entryInfo = {'SamusX':0x2cd, 'SamusY':0x388},
|
||||
dotOrientation = 'ne'),
|
||||
AccessPoint('Red Brinstar Elevator', 'RedBrinstar', {
|
||||
'Caterpillar Room Top Right': lambda sm: sm.canPassRedTowerToMaridiaNode(),
|
||||
'Red Tower Top Left': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HellwayBlueDoor), sm.traverse('RedTowerElevatorLeft')))
|
||||
}, traverse=Cache.ldeco(lambda sm:sm.wor(RomPatches.has(sm.player, RomPatches.RedTowerBlueDoors), sm.traverse('RedBrinstarElevatorTop'))),
|
||||
roomInfo = {'RoomPtr':0x962a, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x8af6, 'direction': 0x7, "cap": (0x16, 0x2d), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x2), "distanceToSpawn": 0x1c0, "doorAsmPtr": 0xb9f1},
|
||||
entryInfo = {'SamusX':0x80, 'SamusY':0x58},
|
||||
start={'spawn':0x010a, 'doors':[0x3c], 'patches':[RomPatches.HellwayBlueDoor], 'solveArea': "Red Brinstar Top", 'areaMode':True},
|
||||
dotOrientation = 'n'),
|
||||
AccessPoint('East Tunnel Right', 'RedBrinstar', {
|
||||
'East Tunnel Top Right': lambda sm: SMBool(True), # handled by room traverse function
|
||||
'Glass Tunnel Top': Cache.ldeco(lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.haveItem('HiJump')))),
|
||||
'Red Tower Top Left': lambda sm: sm.canClimbBottomRedTower()
|
||||
}, roomInfo = {'RoomPtr':0xcf80, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa384, 'direction': 0x4, "cap": (0x1, 0x6), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x0), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0xce, 'SamusY':0x188},
|
||||
dotOrientation = 'se'),
|
||||
AccessPoint('East Tunnel Top Right', 'RedBrinstar', {
|
||||
'East Tunnel Right': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase),
|
||||
sm.haveItem('Super')))
|
||||
}, traverse=Cache.ldeco(lambda sm: RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase)),
|
||||
roomInfo = {'RoomPtr':0xcf80, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa390, 'direction': 0x4, "cap": (0x1, 0x16), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x1), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe356},
|
||||
entryInfo = {'SamusX':0x3c6, 'SamusY':0x88},
|
||||
dotOrientation = 'e'),
|
||||
AccessPoint('Glass Tunnel Top', 'RedBrinstar', {
|
||||
'East Tunnel Right': Cache.ldeco(lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.MaridiaTubeOpened),
|
||||
sm.canUsePowerBombs()))
|
||||
}, traverse=Cache.ldeco(lambda sm: sm.wand(sm.wor(sm.haveItem('Gravity'),
|
||||
sm.haveItem('HiJump')),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.MaridiaTubeOpened),
|
||||
sm.canUsePowerBombs()))),
|
||||
roomInfo = {'RoomPtr':0xcefb, "area": 0x4},
|
||||
exitInfo = {'DoorPtr':0xa330, 'direction': 0x7, "cap": (0x16, 0x7d), "bitFlag": 0x0,
|
||||
"screen": (0x1, 0x7), "distanceToSpawn": 0x200, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x81, 'SamusY':0x78},
|
||||
dotOrientation = 's'),
|
||||
### Tourian
|
||||
AccessPoint('Golden Four', 'Tourian', {},
|
||||
roomInfo = {'RoomPtr':0xa5ed, "area": 0x0},
|
||||
exitInfo = {'DoorPtr':0x91e6, 'direction': 0x5, "cap": (0xe, 0x66), "bitFlag": 0x0,
|
||||
"screen": (0x0, 0x6), "distanceToSpawn": 0x8000, "doorAsmPtr": 0x0000},
|
||||
entryInfo = {'SamusX':0x34, 'SamusY':0x88},
|
||||
start={'spawn':0x0007, 'solveArea': "Tourian", "save": "Save_G4", 'areaMode':True},
|
||||
dotOrientation = 'w'),
|
||||
AccessPoint('Tourian Escape Room 4 Top Right', 'Tourian', {},
|
||||
roomInfo = {'RoomPtr':0xdede, "area": 0x5},
|
||||
exitInfo = {'DoorPtr':0xab34, 'direction': 0x4, "cap": (0x1, 0x86), "bitFlag": 0x40,
|
||||
"screen": (0x0, 0x8), "distanceToSpawn": 0x8000, "doorAsmPtr": 0xe4cf},
|
||||
entryInfo = {'SamusX':0xffff, 'SamusY':0xffff}, # unused
|
||||
escape = True,
|
||||
dotOrientation = 'ne'),
|
||||
]
|
||||
766
worlds/sm/variaRandomizer/graph/vanilla/graph_helpers.py
Normal file
766
worlds/sm/variaRandomizer/graph/vanilla/graph_helpers.py
Normal file
@@ -0,0 +1,766 @@
|
||||
from math import ceil
|
||||
|
||||
from logic.smbool import SMBool
|
||||
from logic.helpers import Helpers, Bosses
|
||||
from logic.cache import Cache
|
||||
from rom.rom_patches import RomPatches
|
||||
from graph.graph_utils import getAccessPoint
|
||||
from utils.parameters import Settings
|
||||
|
||||
class HelpersGraph(Helpers):
|
||||
def __init__(self, smbm):
|
||||
self.smbm = smbm
|
||||
|
||||
def canEnterAndLeaveGauntletQty(self, nPB, nTanksSpark):
|
||||
sm = self.smbm
|
||||
# EXPLAINED: to access Gauntlet Entrance from Landing site we can either:
|
||||
# -fly to it (infinite bomb jumps or space jump)
|
||||
# -shinespark to it
|
||||
# -wall jump with high jump boots
|
||||
# -wall jump without high jump boots
|
||||
# then inside it to break the bomb wals:
|
||||
# -use screw attack (easy way)
|
||||
# -use power bombs
|
||||
# -use bombs
|
||||
# -perform a simple short charge on the way in
|
||||
# and use power bombs on the way out
|
||||
return sm.wand(sm.wor(sm.canFly(),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wand(sm.knowsHiJumpGauntletAccess(),
|
||||
sm.haveItem('HiJump')),
|
||||
sm.knowsHiJumpLessGauntletAccess()),
|
||||
sm.wor(sm.haveItem('ScrewAttack'),
|
||||
sm.wor(sm.wand(sm.energyReserveCountOkHardRoom('Gauntlet'),
|
||||
sm.wand(sm.canUsePowerBombs(),
|
||||
sm.wor(sm.itemCountOk('PowerBomb', nPB),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.energyReserveCountOk(nTanksSpark))))),
|
||||
sm.wand(sm.energyReserveCountOkHardRoom('Gauntlet', 0.51),
|
||||
sm.canUseBombs()))))
|
||||
|
||||
@Cache.decorator
|
||||
def canEnterAndLeaveGauntlet(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.canShortCharge(),
|
||||
sm.canEnterAndLeaveGauntletQty(2, 2)),
|
||||
sm.canEnterAndLeaveGauntletQty(2, 3))
|
||||
|
||||
def canPassTerminatorBombWall(self, fromLandingSite=True):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(SMBool(not fromLandingSite, 0), sm.knowsSimpleShortCharge(), sm.knowsShortCharge())),
|
||||
sm.canDestroyBombWalls())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassCrateriaGreenPirates(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.canPassBombPassages(),
|
||||
sm.haveMissileOrSuper(),
|
||||
sm.energyReserveCountOk(1),
|
||||
sm.wor(sm.haveItem('Charge'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.haveItem('Wave'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Plasma'),
|
||||
sm.haveItem('ScrewAttack'))))
|
||||
|
||||
# from blue brin elevator
|
||||
@Cache.decorator
|
||||
def canAccessBillyMays(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarBlueDoor),
|
||||
sm.traverse('ConstructionZoneRight')),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.wor(sm.knowsBillyMays(),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpaceJump')))
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessKraidsLair(self):
|
||||
sm = self.smbm
|
||||
# EXPLAINED: access the upper right platform with either:
|
||||
# -hijump boots (easy regular way)
|
||||
# -fly (space jump or infinite bomb jump)
|
||||
# -know how to wall jump on the platform without the hijump boots
|
||||
return sm.wand(sm.haveItem('Super'),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.knowsEarlyKraid()))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMoat(self):
|
||||
sm = self.smbm
|
||||
# EXPLAINED: In the Moat we can either:
|
||||
# -use grapple or space jump (easy way)
|
||||
# -do a continuous wall jump (https://www.youtube.com/watch?v=4HVhTwwax6g)
|
||||
# -do a diagonal bomb jump from the middle platform (https://www.youtube.com/watch?v=5NRqQ7RbK3A&t=10m58s)
|
||||
# -do a short charge from the Keyhunter room (https://www.youtube.com/watch?v=kFAYji2gFok)
|
||||
# -do a gravity jump from below the right platform
|
||||
# -do a mock ball and a bounce ball (https://www.youtube.com/watch?v=WYxtRF--834)
|
||||
# -with gravity, either hijump or IBJ
|
||||
return sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.knowsContinuousWallJump(),
|
||||
sm.wand(sm.knowsDiagonalBombJump(), sm.canUseBombs()),
|
||||
sm.canSimpleShortCharge(),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.knowsGravityJump(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canInfiniteBombJump())),
|
||||
sm.wand(sm.knowsMockballWs(), sm.canUseSpringBall()))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMoatFromMoat(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.knowsDiagonalBombJump(), sm.canUseBombs()),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.knowsGravityJump(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canInfiniteBombJump())))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMoatReverse(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(RomPatches.has(sm.player, RomPatches.MoatShotBlock),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.canPassBombPassages())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassSpongeBath(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.canPassBombPassages(),
|
||||
sm.knowsSpongeBathBombJump()),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsSpongeBathHiJump()),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.knowsSpongeBathSpeed()),
|
||||
sm.canSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassBowling(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(Bosses.bossDead(sm, 'Phantoon'),
|
||||
sm.wor(SMBool(sm.getDmgReduction()[0] >= 2),
|
||||
sm.energyReserveCountOk(1),
|
||||
sm.haveItem("SpaceJump"),
|
||||
sm.haveItem("Grapple")))
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessEtecoons(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.canUsePowerBombs(),
|
||||
sm.wand(sm.knowsMoondance(), sm.canUseBombs(), sm.traverse('MainShaftBottomRight')))
|
||||
|
||||
@Cache.decorator
|
||||
def canKillBeetoms(self):
|
||||
sm = self.smbm
|
||||
# can technically be killed with bomb, but it's harder
|
||||
return sm.wor(sm.haveMissileOrSuper(), sm.canUsePowerBombs(), sm.haveItem('ScrewAttack'))
|
||||
|
||||
# the water zone east of WS
|
||||
def canPassForgottenHighway(self, fromWs):
|
||||
sm = self.smbm
|
||||
suitless = sm.wand(sm.haveItem('HiJump'), sm.knowsGravLessLevel1())
|
||||
if fromWs is True and RomPatches.has(sm.player, RomPatches.EastOceanPlatforms).bool is False:
|
||||
suitless = sm.wand(suitless,
|
||||
sm.wor(sm.canSpringBallJump(), # two sbj on the far right
|
||||
# to break water line and go through the door on the right
|
||||
sm.haveItem('SpaceJump')))
|
||||
return sm.wand(sm.wor(sm.haveItem('Gravity'),
|
||||
suitless),
|
||||
sm.haveItem('Morph')) # for crab maze
|
||||
|
||||
@Cache.decorator
|
||||
def canExitCrabHole(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'), # morph to exit the hole
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'), # even with gravity you need some way to climb...
|
||||
sm.wor(sm.haveItem('Ice'), # ...on crabs...
|
||||
sm.wand(sm.haveItem('HiJump'), sm.knowsMaridiaWallJumps()), # ...or by jumping
|
||||
sm.knowsGravityJump(),
|
||||
sm.canFly())),
|
||||
sm.wand(sm.haveItem('Ice'), sm.canDoSuitlessOuterMaridia()), # climbing crabs
|
||||
sm.canDoubleSpringBallJump()))
|
||||
|
||||
# bottom sandpits with the evirs except west sand hall left to right
|
||||
@Cache.decorator
|
||||
def canTraverseSandPits(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel3(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('Ice')))
|
||||
|
||||
@Cache.decorator
|
||||
def canTraverseWestSandHallLeftToRight(self):
|
||||
sm = self.smbm
|
||||
return sm.haveItem('Gravity') # FIXME find suitless condition
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMaridiaToRedTowerNode(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase),
|
||||
sm.haveItem('Super')))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassRedTowerToMaridiaNode(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Morph'),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesBase))
|
||||
|
||||
def canEnterCathedral(self, mult=1.0):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('CathedralEntranceRight'),
|
||||
sm.wor(sm.wand(sm.canHellRun('MainUpperNorfair', mult),
|
||||
sm.wor(sm.wor(RomPatches.has(sm.player, RomPatches.CathedralEntranceWallJump),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canFly()),
|
||||
sm.wor(sm.haveItem('SpeedBooster'), # spark
|
||||
sm.canSpringBallJump()))),
|
||||
sm.wand(sm.canHellRun('MainUpperNorfair', 0.5*mult),
|
||||
sm.haveItem('Morph'),
|
||||
sm.knowsNovaBoost())))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbBubbleMountain(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.haveItem('Ice'),
|
||||
sm.knowsBubbleMountainWallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canHellRunToSpeedBooster(self):
|
||||
sm = self.smbm
|
||||
return sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Speed Booster w/Speed' if sm.haveItem('SpeedBooster') else 'Bubble -> Speed Booster'])
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessDoubleChamberItems(self):
|
||||
sm = self.smbm
|
||||
hellRun = Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Wave']
|
||||
return sm.wor(sm.wand(sm.traverse('SingleChamberRight'),
|
||||
sm.canHellRun(**hellRun)),
|
||||
sm.wand(sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canSimpleShortCharge(),
|
||||
sm.canFly(),
|
||||
sm.knowsDoubleChamberWallJump()),
|
||||
sm.canHellRun(hellRun['hellRun'], hellRun['mult']*0.8, hellRun['minE'])))
|
||||
|
||||
def canExitCathedral(self, hellRun):
|
||||
# from top: can use bomb/powerbomb jumps
|
||||
# from bottom: can do a shinespark or use space jump
|
||||
# can do it with highjump + wall jump
|
||||
# can do it with only two wall jumps (the first one is delayed like on alcatraz)
|
||||
# can do it with a spring ball jump from wall
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.wor(sm.canHellRun(**hellRun),
|
||||
sm.heatProof()),
|
||||
sm.wor(sm.wor(sm.canPassBombPassages(),
|
||||
sm.haveItem("SpeedBooster")),
|
||||
sm.wor(sm.haveItem("SpaceJump"),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.knowsWallJumpCathedralExit(),
|
||||
sm.wand(sm.knowsSpringBallJumpFromWall(), sm.canUseSpringBall()))))
|
||||
|
||||
@Cache.decorator
|
||||
def canGrappleEscape(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.canInfiniteBombJump(), # IBJ from lava...either have grav or freeze the enemy there if hellrunning (otherwise single DBJ at the end)
|
||||
sm.wor(sm.heatProof(),
|
||||
sm.haveItem('Gravity'),
|
||||
sm.haveItem('Ice')))),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.haveItem('HiJump'), # jump from the blocks below
|
||||
sm.knowsShortCharge())), # spark from across the grapple blocks
|
||||
sm.wand(sm.haveItem('HiJump'), sm.canSpringBallJump())) # jump from the blocks below
|
||||
|
||||
@Cache.decorator
|
||||
def canPassFrogSpeedwayRightToLeft(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.wand(sm.knowsFrogSpeedwayWithoutSpeed(),
|
||||
sm.haveItem('Wave'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Plasma'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canEnterNorfairReserveAreaFromBubbleMoutain(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('BubbleMountainTopLeft'),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.haveItem('Ice'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGetAroundWallJump()),
|
||||
sm.wand(sm.canUseSpringBall(),
|
||||
sm.knowsSpringBallJumpFromWall())))
|
||||
|
||||
@Cache.decorator
|
||||
def canEnterNorfairReserveAreaFromBubbleMoutainTop(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('BubbleMountainTopLeft'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.knowsNorfairReserveDBoost()))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassLavaPit(self):
|
||||
sm = self.smbm
|
||||
nTanks4Dive = 8 / sm.getDmgReduction()[0]
|
||||
if sm.haveItem('HiJump').bool == False:
|
||||
nTanks4Dive = ceil(nTanks4Dive * 1.25)
|
||||
return sm.wand(sm.wor(sm.wand(sm.haveItem('Gravity'), sm.haveItem('SpaceJump')),
|
||||
sm.wand(sm.knowsGravityJump(), sm.haveItem('Gravity'), sm.wor(sm.haveItem('HiJump'), sm.knowsLavaDive())),
|
||||
sm.wand(sm.wor(sm.wand(sm.knowsLavaDive(), sm.haveItem('HiJump')),
|
||||
sm.knowsLavaDiveNoHiJump()),
|
||||
sm.energyReserveCountOk(nTanks4Dive))),
|
||||
sm.canUsePowerBombs()) # power bomb blocks left and right of LN entrance without any items before
|
||||
|
||||
@Cache.decorator
|
||||
def canPassLavaPitReverse(self):
|
||||
sm = self.smbm
|
||||
nTanks = 2
|
||||
if sm.heatProof().bool == False:
|
||||
nTanks = 6
|
||||
return sm.energyReserveCountOk(nTanks)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassLowerNorfairChozo(self):
|
||||
sm = self.smbm
|
||||
# to require one more CF if no heat protection because of distance to cover, wait times, acid...
|
||||
return sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Entrance -> GT via Chozo']),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.LNChozoSJCheckDisabled), sm.haveItem('SpaceJump')))
|
||||
|
||||
@Cache.decorator
|
||||
def canExitScrewAttackArea(self):
|
||||
sm = self.smbm
|
||||
|
||||
return sm.wand(sm.canDestroyBombWalls(),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.wand(sm.haveItem('ScrewAttack'), sm.knowsScrewAttackExit()),
|
||||
sm.knowsScrewAttackExitWithoutScrew())),
|
||||
sm.wand(sm.canUseSpringBall(),
|
||||
sm.knowsSpringBallJumpFromWall()),
|
||||
sm.wand(sm.canSimpleShortCharge(), # fight GT and spark out
|
||||
sm.enoughStuffGT())))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassWorstRoom(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canDestroyBombWalls(),
|
||||
sm.canPassWorstRoomPirates(),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.wand(sm.knowsWorstRoomIceCharge(), sm.haveItem('Ice'), sm.canFireChargedShots()),
|
||||
sm.wor(sm.wand(sm.knowsGetAroundWallJump(), sm.haveItem('HiJump')),
|
||||
sm.knowsWorstRoomWallJump()),
|
||||
sm.wand(sm.knowsSpringBallJumpFromWall(), sm.canUseSpringBall())))
|
||||
|
||||
# checks mix of super missiles/health
|
||||
def canGoThroughLowerNorfairEnemy(self, nmyHealth, nbNmy, nmyHitDmg, supDmg=300.0):
|
||||
sm = self.smbm
|
||||
# supers only
|
||||
if sm.itemCount('Super')*5*supDmg >= nbNmy*nmyHealth:
|
||||
return SMBool(True, 0, items=['Super'])
|
||||
|
||||
# - or with taking damage as well?
|
||||
(dmgRed, redItems) = sm.getDmgReduction(envDmg=False)
|
||||
dmg = nmyHitDmg / dmgRed
|
||||
if sm.heatProof() and (sm.itemCount('Super')*5*supDmg)/nmyHealth + (sm.energyReserveCount()*100 - 2)/dmg >= nbNmy:
|
||||
# require heat proof as long as taking damage is necessary.
|
||||
# display all the available energy in the solver.
|
||||
return sm.wand(sm.heatProof(), SMBool(True, 0, items=redItems+['Super', '{}-ETank - {}-Reserve'.format(self.smbm.itemCount('ETank'), self.smbm.itemCount('Reserve'))]))
|
||||
|
||||
return sm.knowsDodgeLowerNorfairEnemies()
|
||||
|
||||
def canKillRedKiHunters(self, n):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Plasma'),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.wand(sm.heatProof(), # this takes a loooong time ...
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.haveItem('Wave')))),
|
||||
sm.canGoThroughLowerNorfairEnemy(1800.0, float(n), 200.0))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassThreeMuskateers(self):
|
||||
sm = self.smbm
|
||||
return sm.canKillRedKiHunters(6)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassRedKiHunters(self):
|
||||
sm = self.smbm
|
||||
return sm.canKillRedKiHunters(3)
|
||||
|
||||
@Cache.decorator
|
||||
def canPassWastelandDessgeegas(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Plasma'),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.wand(sm.heatProof(), # this takes a loooong time ...
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.haveItem('Wave')))),
|
||||
sm.itemCountOk('PowerBomb', 4),
|
||||
sm.canGoThroughLowerNorfairEnemy(800.0, 3.0, 160.0))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassNinjaPirates(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.itemCountOk('Missile', 10),
|
||||
sm.itemCountOk('Super', 2),
|
||||
sm.haveItem('Plasma'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.wor(sm.haveItem('Wave'),
|
||||
sm.haveItem('Ice')))),
|
||||
sm.canShortCharge()) # echoes kill
|
||||
|
||||
@Cache.decorator
|
||||
def canPassWorstRoomPirates(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('ScrewAttack'),
|
||||
sm.itemCountOk('Missile', 6),
|
||||
sm.itemCountOk('Super', 3),
|
||||
sm.wand(sm.canFireChargedShots(), sm.haveItem('Plasma')),
|
||||
sm.wand(sm.haveItem('Charge'),
|
||||
sm.wor(sm.haveItem('Spazer'),
|
||||
sm.haveItem('Wave'),
|
||||
sm.haveItem('Ice'))),
|
||||
sm.knowsDodgeLowerNorfairEnemies())
|
||||
|
||||
# go though the pirates room filled with acid
|
||||
@Cache.decorator
|
||||
def canPassAmphitheaterReverse(self):
|
||||
sm = self.smbm
|
||||
dmgRed = sm.getDmgReduction()[0]
|
||||
nTanksGrav = 4 * 4/dmgRed
|
||||
nTanksNoGrav = 6 * 4/dmgRed
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.energyReserveCountOk(nTanksGrav)),
|
||||
sm.wand(sm.energyReserveCountOk(nTanksNoGrav),
|
||||
sm.knowsLavaDive())) # should be a good enough skill filter for acid wall jumps with no grav...
|
||||
|
||||
@Cache.decorator
|
||||
def canGetBackFromRidleyZone(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canUsePowerBombs(),
|
||||
sm.wor(sm.canUseSpringBall(),
|
||||
sm.canUseBombs(),
|
||||
sm.itemCountOk('PowerBomb', 2),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.canShortCharge()), # speedball
|
||||
# in escape you don't have PBs and can't shoot bomb blocks in long tunnels
|
||||
# in wasteland and ki hunter room
|
||||
sm.wnot(sm.canUseHyperBeam()))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbRedTower(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.knowsRedTowerClimb(),
|
||||
sm.haveItem('Ice'),
|
||||
sm.haveItem('SpaceJump'))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbBottomRedTower(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(RomPatches.has(sm.player, RomPatches.RedTowerLeftPassage),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.canFly(),
|
||||
sm.canShortCharge())
|
||||
|
||||
@Cache.decorator
|
||||
def canGoUpMtEverest(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canFly(),
|
||||
sm.wand(sm.knowsGravityJump(),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.knowsMtEverestGravJump())))),
|
||||
sm.wand(sm.canDoSuitlessOuterMaridia(),
|
||||
sm.haveItem('Grapple')))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassMtEverest(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canFly(),
|
||||
sm.knowsGravityJump())),
|
||||
sm.wand(sm.canDoSuitlessOuterMaridia(),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.wand(sm.haveItem('Ice'), sm.knowsTediousMountEverest(), sm.haveItem('Super')),
|
||||
sm.canDoubleSpringBallJump())))
|
||||
|
||||
@Cache.decorator
|
||||
def canJumpUnderwater(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel1(),
|
||||
sm.haveItem('HiJump')))
|
||||
|
||||
@Cache.decorator
|
||||
def canDoSuitlessOuterMaridia(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.knowsGravLessLevel1(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.wor(sm.haveItem('Ice'),
|
||||
sm.canSpringBallJump()))
|
||||
|
||||
@Cache.decorator
|
||||
def canDoOuterMaridia(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.canDoSuitlessOuterMaridia())
|
||||
|
||||
@Cache.decorator
|
||||
def canPassBotwoonHallway(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.haveItem('Gravity')),
|
||||
sm.wand(sm.knowsMochtroidClip(), sm.haveItem('Ice')),
|
||||
sm.canCrystalFlashClip())
|
||||
|
||||
@Cache.decorator
|
||||
def canDefeatBotwoon(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.enoughStuffBotwoon(),
|
||||
sm.canPassBotwoonHallway())
|
||||
|
||||
# the sandpits from aqueduct
|
||||
@Cache.decorator
|
||||
def canAccessSandPits(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGravLessLevel3()))
|
||||
|
||||
@Cache.decorator
|
||||
def canReachCacatacAlleyFromBotowoon(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.canDoubleSpringBallJump())))
|
||||
|
||||
@Cache.decorator
|
||||
def canPassCacatacAlley(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(Bosses.bossDead(sm, 'Draygon'),
|
||||
sm.haveItem('Morph'),
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpaceJump'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canGoThroughColosseumSuitless(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.haveItem('Ice'),
|
||||
sm.energyReserveCountOk(int(7.0/sm.getDmgReduction(False)[0])), # mochtroid dmg
|
||||
sm.knowsBotwoonToDraygonWithIce()))
|
||||
|
||||
@Cache.decorator
|
||||
def canBotwoonExitToColosseum(self):
|
||||
sm = self.smbm
|
||||
# traverse Botwoon Energy Tank Room
|
||||
return sm.wand(sm.wor(sm.wand(sm.haveItem('Gravity'), sm.haveItem('SpeedBooster')),
|
||||
sm.wand(sm.haveItem('Morph'), sm.canJumpUnderwater())),
|
||||
# after Botwoon Energy Tank Room
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
# get to top right door
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('Ice'), # climb mochtroids
|
||||
sm.wand(sm.canDoubleSpringBallJump(),
|
||||
sm.haveItem('SpaceJump'))),
|
||||
sm.canGoThroughColosseumSuitless())))
|
||||
|
||||
@Cache.decorator
|
||||
def canColosseumToBotwoonExit(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.canGoThroughColosseumSuitless()))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbColosseum(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel2(),
|
||||
sm.haveItem("HiJump"),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.knowsPreciousRoomGravJumpExit())))
|
||||
|
||||
@Cache.decorator
|
||||
def canClimbWestSandHole(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGravLessLevel3(),
|
||||
sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.canSpringBallJump(),
|
||||
sm.knowsWestSandHoleSuitlessWallJumps())))
|
||||
|
||||
@Cache.decorator
|
||||
def canAccessItemsInWestSandHole(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.wand(sm.haveItem('HiJump'), # vanilla strat
|
||||
sm.canUseSpringBall()),
|
||||
sm.wand(sm.haveItem('SpaceJump'), # alternate strat with possible double bomb jump but no difficult wj
|
||||
sm.wor(sm.canUseSpringBall(),
|
||||
sm.canUseBombs())),
|
||||
sm.wand(sm.canPassBombPassages(), # wjs and/or 3 tile mid air morph
|
||||
sm.knowsMaridiaWallJumps()))
|
||||
|
||||
@Cache.decorator
|
||||
def getDraygonConnection(self):
|
||||
return getAccessPoint('DraygonRoomOut').ConnectedTo
|
||||
|
||||
@Cache.decorator
|
||||
def isVanillaDraygon(self):
|
||||
return SMBool(self.getDraygonConnection() == 'DraygonRoomIn')
|
||||
|
||||
@Cache.decorator
|
||||
def canUseCrocRoomToChargeSpeed(self):
|
||||
sm = self.smbm
|
||||
crocRoom = getAccessPoint('Crocomire Room Top')
|
||||
speedway = getAccessPoint('Crocomire Speedway Bottom')
|
||||
return sm.wand(SMBool(crocRoom.ConnectedTo == 'Crocomire Speedway Bottom'),
|
||||
crocRoom.traverse(sm),
|
||||
speedway.traverse(sm))
|
||||
|
||||
@Cache.decorator
|
||||
def canFightDraygon(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.wor(sm.knowsGravLessLevel2(),
|
||||
sm.knowsGravLessLevel3())))
|
||||
|
||||
@Cache.decorator
|
||||
def canDraygonCrystalFlashSuit(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.canCrystalFlash(),
|
||||
sm.knowsDraygonRoomCrystalFlash(),
|
||||
# ask for 4 PB pack as an ugly workaround for
|
||||
# a rando bug which can place a PB at space
|
||||
# jump to "get you out" (this check is in
|
||||
# PostAvailable condition of the Dray/Space
|
||||
# Jump locs)
|
||||
sm.itemCountOk('PowerBomb', 4))
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygonRoomWithGravity(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.knowsGravityJump(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpeedBooster'))))
|
||||
|
||||
@Cache.decorator
|
||||
def canGrappleExitDraygon(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.haveItem('Grapple'),
|
||||
sm.knowsDraygonRoomGrappleExit())
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygonVanilla(self):
|
||||
sm = self.smbm
|
||||
# to get out of draygon room:
|
||||
# with gravity but without highjump/bomb/space jump: gravity jump
|
||||
# to exit draygon room: grapple or crystal flash (for free shine spark)
|
||||
# to exit precious room: spring ball jump, xray scope glitch or stored spark
|
||||
return sm.wor(sm.canExitDraygonRoomWithGravity(),
|
||||
sm.wand(sm.canDraygonCrystalFlashSuit(),
|
||||
# use the spark either to exit draygon room or precious room
|
||||
sm.wor(sm.canGrappleExitDraygon(),
|
||||
sm.wand(sm.haveItem('XRayScope'),
|
||||
sm.knowsPreciousRoomXRayExit()),
|
||||
sm.canSpringBallJump())),
|
||||
# spark-less exit (no CF)
|
||||
sm.wand(sm.canGrappleExitDraygon(),
|
||||
sm.wor(sm.wand(sm.haveItem('XRayScope'),
|
||||
sm.knowsPreciousRoomXRayExit()),
|
||||
sm.canSpringBallJump())),
|
||||
sm.canDoubleSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygonRandomized(self):
|
||||
sm = self.smbm
|
||||
# disregard precious room
|
||||
return sm.wor(sm.canExitDraygonRoomWithGravity(),
|
||||
sm.canDraygonCrystalFlashSuit(),
|
||||
sm.canGrappleExitDraygon(),
|
||||
sm.canDoubleSpringBallJump())
|
||||
|
||||
@Cache.decorator
|
||||
def canExitDraygon(self):
|
||||
sm = self.smbm
|
||||
if self.isVanillaDraygon():
|
||||
return self.canExitDraygonVanilla()
|
||||
else:
|
||||
return self.canExitDraygonRandomized()
|
||||
|
||||
@Cache.decorator
|
||||
def canExitPreciousRoomVanilla(self):
|
||||
return SMBool(True) # handled by canExitDraygonVanilla
|
||||
|
||||
@Cache.decorator
|
||||
def canExitPreciousRoomRandomized(self):
|
||||
sm = self.smbm
|
||||
suitlessRoomExit = sm.canSpringBallJump()
|
||||
if suitlessRoomExit.bool == False:
|
||||
if self.getDraygonConnection() == 'KraidRoomIn':
|
||||
suitlessRoomExit = sm.canShortCharge() # charge spark in kraid's room
|
||||
elif self.getDraygonConnection() == 'RidleyRoomIn':
|
||||
suitlessRoomExit = sm.wand(sm.haveItem('XRayScope'), # get doorstuck in compatible transition
|
||||
sm.knowsPreciousRoomXRayExit())
|
||||
return sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.knowsGravityJump(),
|
||||
sm.haveItem('HiJump'))),
|
||||
suitlessRoomExit)
|
||||
|
||||
@Cache.decorator
|
||||
def canExitPreciousRoom(self):
|
||||
if self.isVanillaDraygon():
|
||||
return self.canExitPreciousRoomVanilla()
|
||||
else:
|
||||
return self.canExitPreciousRoomRandomized()
|
||||
|
||||
@Cache.decorator
|
||||
def canPassDachoraRoom(self):
|
||||
sm = self.smbm
|
||||
return sm.wor(sm.haveItem('SpeedBooster'), sm.canDestroyBombWalls())
|
||||
|
||||
@Cache.decorator
|
||||
def canTraverseCrabTunnelLeftToRight(self):
|
||||
sm = self.smbm
|
||||
return sm.wand(sm.traverse('MainStreetBottomRight'),
|
||||
sm.wor(sm.haveItem('Super'),
|
||||
RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther)))
|
||||
994
worlds/sm/variaRandomizer/graph/vanilla/graph_locations.py
Normal file
994
worlds/sm/variaRandomizer/graph/vanilla/graph_locations.py
Normal file
@@ -0,0 +1,994 @@
|
||||
from logic.helpers import Bosses
|
||||
from utils.parameters import Settings
|
||||
from rom.rom_patches import RomPatches
|
||||
from logic.smbool import SMBool
|
||||
from graph.location import locationsDict
|
||||
|
||||
locationsDict["Energy Tank, Gauntlet"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Gauntlet"].Available = (
|
||||
lambda sm: sm.wor(sm.canEnterAndLeaveGauntlet(),
|
||||
sm.wand(sm.canShortCharge(),
|
||||
sm.canEnterAndLeaveGauntletQty(1, 0)), # thanks ponk! https://youtu.be/jil5zTBCF1s
|
||||
sm.canDoLowGauntlet())
|
||||
)
|
||||
locationsDict["Bomb"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Bomb"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.traverse('FlywayRight'))
|
||||
)
|
||||
locationsDict["Bomb"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.knowsAlcatrazEscape(),
|
||||
sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Energy Tank, Terminator"].AccessFrom = {
|
||||
'Landing Site': lambda sm: sm.canPassTerminatorBombWall(),
|
||||
'Lower Mushrooms Left': lambda sm: sm.canPassCrateriaGreenPirates(),
|
||||
'Gauntlet Top': lambda sm: sm.haveItem('Morph')
|
||||
}
|
||||
locationsDict["Energy Tank, Terminator"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Reserve Tank, Brinstar"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Reserve Tank, Brinstar"].Available = (
|
||||
lambda sm: sm.wand(sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('EarlySupersRight')))
|
||||
)
|
||||
locationsDict["Charge Beam"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Charge Beam"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Morphing Ball"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Morphing Ball"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Energy Tank, Brinstar Ceiling"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarBlueDoor), sm.traverse('ConstructionZoneRight'))
|
||||
}
|
||||
locationsDict["Energy Tank, Brinstar Ceiling"].Available = (
|
||||
|
||||
lambda sm: sm.wor(sm.knowsCeilingDBoost(),
|
||||
sm.canFly(),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.haveItem('Ice'),
|
||||
sm.wand(sm.canUsePowerBombs(),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.canSimpleShortCharge()))
|
||||
)
|
||||
locationsDict["Energy Tank, Etecoons"].AccessFrom = {
|
||||
'Etecoons Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Etecoons"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Energy Tank, Waterway"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Waterway"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.traverse('BigPinkBottomLeft'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.haveItem('Gravity'),
|
||||
sm.canSimpleShortCharge())) # from the blocks above the water
|
||||
)
|
||||
locationsDict["Energy Tank, Brinstar Gate"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Brinstar Gate"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('BigPinkRight'),
|
||||
sm.wor(sm.haveItem('Wave'),
|
||||
sm.wand(sm.haveItem('Super'),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.knowsReverseGateGlitch()),
|
||||
sm.wand(sm.haveItem('Super'),
|
||||
sm.knowsReverseGateGlitchHiJumpLess())))
|
||||
)
|
||||
locationsDict["X-Ray Scope"].AccessFrom = {
|
||||
'Red Tower Top Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["X-Ray Scope"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.traverse('RedTowerLeft'),
|
||||
sm.traverse('RedBrinstarFirefleaLeft'),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.energyReserveCountOkHardRoom('X-Ray'),
|
||||
sm.wor(sm.knowsXrayDboost(),
|
||||
sm.wand(sm.haveItem('Ice'),
|
||||
sm.wor(sm.haveItem('HiJump'), sm.knowsXrayIce())),
|
||||
sm.canInfiniteBombJump(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.canSpringBallJump()))))))
|
||||
)
|
||||
locationsDict["Spazer"].AccessFrom = {
|
||||
'East Tunnel Right': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Spazer"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('BelowSpazerTopRight'),
|
||||
sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
RomPatches.has(sm.player, RomPatches.SpazerShotBlock))))
|
||||
)
|
||||
locationsDict["Energy Tank, Kraid"].AccessFrom = {
|
||||
'Warehouse Zeela Room Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Kraid"].Available = (
|
||||
lambda sm: sm.wand(Bosses.bossDead(sm, 'Kraid'),
|
||||
# kill the beetoms to unlock the door to get out
|
||||
sm.canKillBeetoms())
|
||||
)
|
||||
locationsDict["Kraid"].AccessFrom = {
|
||||
'KraidRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Kraid"].Available = (
|
||||
lambda sm: sm.enoughStuffsKraid()
|
||||
)
|
||||
locationsDict["Varia Suit"].AccessFrom = {
|
||||
'KraidRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Varia Suit"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Kraid')
|
||||
)
|
||||
locationsDict["Ice Beam"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.traverse('BusinessCenterTopLeft')
|
||||
}
|
||||
locationsDict["Ice Beam"].Available = (
|
||||
lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['Ice']['Norfair Entrance -> Ice Beam']),
|
||||
sm.wor(sm.canPassBombPassages(), # to exit, or if you fail entrance
|
||||
sm.wand(sm.haveItem('Ice'), # harder strat
|
||||
sm.haveItem('Morph'),
|
||||
sm.knowsIceEscape())),
|
||||
sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.knowsMockball()),
|
||||
sm.haveItem('SpeedBooster')))
|
||||
)
|
||||
locationsDict["Energy Tank, Crocomire"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Crocomire"].Available = (
|
||||
lambda sm: sm.wand(sm.enoughStuffCroc(),
|
||||
sm.wor(sm.haveItem('Grapple'),
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.energyReserveCountOk(3/sm.getDmgReduction()[0])))
|
||||
)
|
||||
locationsDict["Hi-Jump Boots"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HiJumpAreaBlueDoor), sm.traverse('BusinessCenterBottomLeft'))
|
||||
}
|
||||
locationsDict["Hi-Jump Boots"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Hi-Jump Boots"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.haveItem('Morph'), RomPatches.has(sm.player, RomPatches.HiJumpShotBlock)))
|
||||
)
|
||||
locationsDict["Grapple Beam"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Grapple Beam"].Available = (
|
||||
lambda sm: sm.wand(sm.enoughStuffCroc(),
|
||||
sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.canFly()),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.knowsShortCharge(),
|
||||
sm.canUsePowerBombs())),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.canSpringBallJump()),
|
||||
sm.haveItem('HiJump')), # jump from the yellow plateform ennemy
|
||||
sm.canGreenGateGlitch()))
|
||||
)
|
||||
locationsDict["Grapple Beam"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.haveItem('Morph'), # regular exit
|
||||
sm.wand(sm.haveItem('Super'), # grapple escape reverse
|
||||
sm.wor(sm.canFly(), # Grapple Tutorial Room 2
|
||||
sm.haveItem('HiJump'),
|
||||
sm.haveItem('Grapple')),
|
||||
sm.wor(sm.haveItem('Gravity'), # Grapple Tutorial Room 3
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.haveItem('Grapple'))))
|
||||
)
|
||||
locationsDict["Reserve Tank, Norfair"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutain(),
|
||||
'Bubble Mountain Top': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutainTop(),
|
||||
}
|
||||
locationsDict["Reserve Tank, Norfair"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'), sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Reserve']))
|
||||
)
|
||||
locationsDict["Speed Booster"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.SpeedAreaBlueDoors),
|
||||
sm.wand(sm.traverse('BubbleMountainTopRight'),
|
||||
sm.traverse('SpeedBoosterHallRight')))
|
||||
}
|
||||
locationsDict["Speed Booster"].Available = (
|
||||
lambda sm: sm.canHellRunToSpeedBooster()
|
||||
)
|
||||
locationsDict["Wave Beam"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.canAccessDoubleChamberItems()
|
||||
}
|
||||
locationsDict["Wave Beam"].Available = (
|
||||
lambda sm: sm.traverse('DoubleChamberRight')
|
||||
)
|
||||
locationsDict["Wave Beam"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.haveItem('Morph'), # exit through lower passage under the spikes
|
||||
sm.wand(sm.wor(sm.haveItem('SpaceJump'), # exit through blue gate
|
||||
sm.haveItem('Grapple')),
|
||||
sm.wor(sm.wand(sm.canBlueGateGlitch(), sm.heatProof()), # hell run + green gate glitch is too much
|
||||
sm.haveItem('Wave'))))
|
||||
)
|
||||
locationsDict["Ridley"].AccessFrom = {
|
||||
'RidleyRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Ridley"].Available = (
|
||||
lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']), sm.enoughStuffsRidley())
|
||||
)
|
||||
locationsDict["Energy Tank, Ridley"].AccessFrom = {
|
||||
'RidleyRoomIn': lambda sm: sm.haveItem('Ridley')
|
||||
}
|
||||
locationsDict["Energy Tank, Ridley"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Screw Attack"].AccessFrom = {
|
||||
'Screw Attack Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Screw Attack"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Screw Attack"].PostAvailable = (
|
||||
lambda sm: sm.canExitScrewAttackArea()
|
||||
)
|
||||
locationsDict["Energy Tank, Firefleas"].AccessFrom = {
|
||||
'Firefleas': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Firefleas"].Available = (
|
||||
lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.FirefleasRemoveFune),
|
||||
# get past the fune
|
||||
sm.haveItem('Super'),
|
||||
sm.canPassBombPassages(),
|
||||
sm.canUseSpringBall())
|
||||
)
|
||||
locationsDict["Energy Tank, Firefleas"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.knowsFirefleasWalljump(),
|
||||
sm.wor(sm.haveItem('Ice'),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.canSpringBallJump()))
|
||||
)
|
||||
locationsDict["Reserve Tank, Wrecked Ship"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Reserve Tank, Wrecked Ship"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.canPassBowling())
|
||||
)
|
||||
locationsDict["Energy Tank, Wrecked Ship"].AccessFrom = {
|
||||
'Wrecked Ship Back': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.WsEtankBlueDoor),
|
||||
sm.traverse('ElectricDeathRoomTopLeft'))
|
||||
}
|
||||
locationsDict["Energy Tank, Wrecked Ship"].Available = (
|
||||
lambda sm: sm.wor(Bosses.bossDead(sm, 'Phantoon'),
|
||||
RomPatches.has(sm.player, RomPatches.WsEtankPhantoonAlive))
|
||||
)
|
||||
locationsDict["Phantoon"].AccessFrom = {
|
||||
'PhantoonRoomIn': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Phantoon"].Available = (
|
||||
lambda sm: sm.enoughStuffsPhantoon()
|
||||
)
|
||||
locationsDict["Right Super, Wrecked Ship"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
}
|
||||
locationsDict["Right Super, Wrecked Ship"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Gravity Suit"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Gravity Suit"].Available = (
|
||||
lambda sm: sm.wand(sm.canPassBombPassages(),
|
||||
sm.canPassBowling())
|
||||
)
|
||||
locationsDict["Energy Tank, Mama turtle"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: sm.wand(sm.canDoOuterMaridia(),
|
||||
sm.wor(sm.traverse('FishTankRight'),
|
||||
RomPatches.has(sm.player, RomPatches.MamaTurtleBlueDoor)),
|
||||
sm.wor(sm.wor(sm.canFly(),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.knowsHiJumpMamaTurtle())),
|
||||
sm.wor(sm.wand(sm.canUseSpringBall(),
|
||||
sm.wor(sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsSpringBallJump()),
|
||||
sm.knowsSpringBallJumpFromWall())),
|
||||
sm.haveItem('Grapple')))),
|
||||
'Mama Turtle': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Energy Tank, Mama turtle"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Plasma Beam"].AccessFrom = {
|
||||
'Toilet Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Plasma Beam"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Draygon')
|
||||
)
|
||||
locationsDict["Plasma Beam"].PostAvailable = (
|
||||
lambda sm: sm.wand(sm.wor(sm.wand(sm.canShortCharge(),
|
||||
sm.knowsKillPlasmaPiratesWithSpark()),
|
||||
sm.wand(sm.canFireChargedShots(),
|
||||
sm.knowsKillPlasmaPiratesWithCharge(),
|
||||
# 160/80/40 dmg * 4 ground plasma pirates
|
||||
# => 640/320/160 damage take required
|
||||
# check below is 1099/599/299 (give margin for taking dmg a bit)
|
||||
# (* 4 for nerfed charge, since you need to take hits 4 times instead of one)
|
||||
sm.energyReserveCountOk(int(10.0 * sm.getPiratesPseudoScrewCoeff()/sm.getDmgReduction(False)[0]))),
|
||||
sm.haveItem('ScrewAttack'),
|
||||
sm.haveItem('Plasma')),
|
||||
sm.wor(sm.canFly(),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGetAroundWallJump()),
|
||||
sm.canShortCharge(),
|
||||
sm.wand(sm.canSpringBallJump(),
|
||||
sm.knowsSpringBallJumpFromWall())))
|
||||
)
|
||||
locationsDict["Reserve Tank, Maridia"].AccessFrom = {
|
||||
'Left Sandpit': lambda sm: sm.canClimbWestSandHole()
|
||||
}
|
||||
locationsDict["Reserve Tank, Maridia"].Available = (
|
||||
lambda sm: sm.canAccessItemsInWestSandHole()
|
||||
)
|
||||
locationsDict["Spring Ball"].AccessFrom = {
|
||||
'Oasis Bottom': lambda sm: sm.canTraverseSandPits()
|
||||
}
|
||||
locationsDict["Spring Ball"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(), # in Shaktool room to let Shaktool access the sand blocks
|
||||
sm.wor(sm.wand(sm.haveItem('Ice'), # puyo clip
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.knowsPuyoClip()),
|
||||
sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('XRayScope'),
|
||||
sm.knowsPuyoClipXRay()),
|
||||
sm.knowsSuitlessPuyoClip())),
|
||||
sm.wand(sm.haveItem('Grapple'), # go through grapple block
|
||||
sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.wor(sm.wand(sm.haveItem('HiJump'), sm.knowsAccessSpringBallWithHiJump()),
|
||||
sm.haveItem('SpaceJump')),
|
||||
sm.knowsAccessSpringBallWithGravJump(),
|
||||
sm.wand(sm.haveItem('Bomb'),
|
||||
sm.wor(sm.knowsAccessSpringBallWithBombJumps(),
|
||||
sm.wand(sm.haveItem('SpringBall'),
|
||||
sm.knowsAccessSpringBallWithSpringBallBombJumps()))),
|
||||
sm.wand(sm.haveItem('SpringBall'), sm.knowsAccessSpringBallWithSpringBallJump()))),
|
||||
sm.wand(sm.haveItem('SpaceJump'), sm.knowsAccessSpringBallWithFlatley()))),
|
||||
sm.wand(sm.haveItem('XRayScope'), sm.knowsAccessSpringBallWithXRayClimb()), # XRay climb
|
||||
sm.canCrystalFlashClip()),
|
||||
sm.wor(sm.haveItem('Gravity'), sm.canUseSpringBall())) # acess the item in spring ball room
|
||||
)
|
||||
locationsDict["Spring Ball"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.wand(sm.haveItem('Gravity'),
|
||||
sm.wor(sm.haveItem('HiJump'),
|
||||
sm.canFly(),
|
||||
sm.knowsMaridiaWallJumps())),
|
||||
sm.canSpringBallJump())
|
||||
)
|
||||
locationsDict["Energy Tank, Botwoon"].AccessFrom = {
|
||||
'Post Botwoon': lambda sm: sm.canJumpUnderwater()
|
||||
}
|
||||
locationsDict["Energy Tank, Botwoon"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Draygon"].AccessFrom = {
|
||||
'Draygon Room Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Draygon"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Space Jump"].AccessFrom = {
|
||||
'Draygon Room Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Space Jump"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Space Jump"].PostAvailable = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Draygon')
|
||||
)
|
||||
locationsDict["Mother Brain"].AccessFrom = {
|
||||
'Golden Four': lambda sm: Bosses.allBossesDead(sm)
|
||||
}
|
||||
locationsDict["Mother Brain"].Available = (
|
||||
lambda sm: sm.enoughStuffTourian()
|
||||
)
|
||||
locationsDict["Power Bomb (Crateria surface)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (Crateria surface)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('LandingSiteTopRight'),
|
||||
sm.wor(sm.haveItem('SpeedBooster'),
|
||||
sm.canFly()))
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship bottom)"].AccessFrom = {
|
||||
'West Ocean Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (outside Wrecked Ship bottom)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship bottom)"].PostAvailable = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship top)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (outside Wrecked Ship top)"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
)
|
||||
locationsDict["Missile (outside Wrecked Ship middle)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (outside Wrecked Ship middle)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Super'), sm.haveItem('Morph'), Bosses.bossDead(sm, 'Phantoon'))
|
||||
)
|
||||
locationsDict["Missile (Crateria moat)"].AccessFrom = {
|
||||
'Moat Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria moat)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Crateria bottom)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria bottom)"].Available = (
|
||||
lambda sm: sm.wor(sm.canDestroyBombWalls(),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.knowsOldMBWithSpeed()))
|
||||
)
|
||||
locationsDict["Missile (Crateria gauntlet right)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: sm.wor(sm.wand(sm.canEnterAndLeaveGauntlet(),
|
||||
sm.canPassBombPassages()),
|
||||
sm.canDoLowGauntlet()),
|
||||
'Gauntlet Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria gauntlet right)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Crateria gauntlet left)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: sm.wor(sm.wand(sm.canEnterAndLeaveGauntlet(),
|
||||
sm.canPassBombPassages()),
|
||||
sm.canDoLowGauntlet()),
|
||||
'Gauntlet Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria gauntlet left)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (Crateria)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (Crateria)"].Available = (
|
||||
lambda sm: sm.wand(sm.canPassBombPassages(),
|
||||
sm.traverse("ClimbRight"),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
# reserves are hard to trigger midspark when not having ETanks
|
||||
sm.wor(sm.wand(sm.energyReserveCountOk(2), sm.itemCountOk('ETank', 1)), # need energy to get out
|
||||
sm.wand(sm.itemCountOk('ETank', 1),
|
||||
sm.wor(sm.haveItem('Grapple'), # use grapple/space or dmg protection to get out
|
||||
sm.haveItem('SpaceJump'),
|
||||
sm.heatProof()))),
|
||||
sm.wor(sm.haveItem('Ice'),
|
||||
sm.wand(sm.canSimpleShortCharge(), sm.canUsePowerBombs()))) # there's also a dboost involved in simple short charge or you have to kill the yellow enemies with some power bombs
|
||||
)
|
||||
locationsDict["Missile (Crateria middle)"].AccessFrom = {
|
||||
'Landing Site': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Crateria middle)"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Power Bomb (green Brinstar bottom)"].AccessFrom = {
|
||||
'Etecoons Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (green Brinstar bottom)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.canKillBeetoms())
|
||||
)
|
||||
locationsDict["Super Missile (pink Brinstar)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (pink Brinstar)"].Available = (
|
||||
lambda sm: sm.wor(sm.wand(sm.traverse('BigPinkTopRight'),
|
||||
sm.enoughStuffSporeSpawn()),
|
||||
# back way into spore spawn
|
||||
sm.wand(sm.canOpenGreenDoors(),
|
||||
sm.canPassBombPassages()))
|
||||
)
|
||||
locationsDict["Super Missile (pink Brinstar)"].PostAvailable = (
|
||||
lambda sm: sm.wand(sm.canOpenGreenDoors(),
|
||||
sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Missile (green Brinstar below super missile)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Missile (green Brinstar below super missile)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (green Brinstar below super missile)"].PostAvailable = (
|
||||
lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.EarlySupersShotBlock), sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Super Missile (green Brinstar top)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Super Missile (green Brinstar top)"].Available = (
|
||||
lambda sm: sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster'))
|
||||
)
|
||||
locationsDict["Missile (green Brinstar behind missile)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Missile (green Brinstar behind missile)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'),
|
||||
sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster')),
|
||||
sm.traverse('EarlySupersRight'),
|
||||
sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.knowsRonPopeilScrew(),
|
||||
sm.haveItem('ScrewAttack'))))
|
||||
)
|
||||
locationsDict["Missile (green Brinstar behind reserve tank)"].AccessFrom = {
|
||||
'Green Brinstar Elevator': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.BrinReserveBlueDoors), sm.traverse('MainShaftRight'))
|
||||
}
|
||||
locationsDict["Missile (green Brinstar behind reserve tank)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('EarlySupersRight'),
|
||||
sm.haveItem('Morph'),
|
||||
sm.wor(sm.canMockball(),
|
||||
sm.haveItem('SpeedBooster')))
|
||||
)
|
||||
locationsDict["Missile (pink Brinstar top)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (pink Brinstar top)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (pink Brinstar bottom)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (pink Brinstar bottom)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (pink Brinstar)"].AccessFrom = {
|
||||
'Big Pink': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (pink Brinstar)"].Available = (
|
||||
lambda sm: sm.wand(sm.canUsePowerBombs(),
|
||||
sm.haveItem('Super'))
|
||||
)
|
||||
locationsDict["Missile (green Brinstar pipe)"].AccessFrom = {
|
||||
'Green Hill Zone Top Right': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (green Brinstar pipe)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Power Bomb (blue Brinstar)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: sm.canUsePowerBombs(),
|
||||
'Morph Ball Room Left': lambda sm: sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(sm.haveItem('Morph'),
|
||||
sm.canShortCharge())) # speedball
|
||||
}
|
||||
locationsDict["Power Bomb (blue Brinstar)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar middle)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar middle)"].Available = (
|
||||
lambda sm: sm.wand(sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarMissile), sm.haveItem('Morph')),
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.BlueBrinstarBlueDoor), sm.traverse('ConstructionZoneRight')))
|
||||
)
|
||||
locationsDict["Super Missile (green Brinstar bottom)"].AccessFrom = {
|
||||
'Etecoons Supers': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (green Brinstar bottom)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar bottom)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar bottom)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar top)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar top)"].Available = (
|
||||
lambda sm: sm.canAccessBillyMays()
|
||||
)
|
||||
locationsDict["Missile (blue Brinstar behind missile)"].AccessFrom = {
|
||||
'Blue Brinstar Elevator Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (blue Brinstar behind missile)"].Available = (
|
||||
lambda sm: sm.canAccessBillyMays()
|
||||
)
|
||||
locationsDict["Power Bomb (red Brinstar sidehopper room)"].AccessFrom = {
|
||||
'Red Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (red Brinstar sidehopper room)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('RedTowerElevatorTopLeft'),
|
||||
sm.canUsePowerBombs())
|
||||
)
|
||||
locationsDict["Power Bomb (red Brinstar spike room)"].AccessFrom = {
|
||||
'Red Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (red Brinstar spike room)"].Available = (
|
||||
lambda sm: sm.traverse('RedTowerElevatorBottomLeft')
|
||||
)
|
||||
locationsDict["Missile (red Brinstar spike room)"].AccessFrom = {
|
||||
'Red Brinstar Elevator': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (red Brinstar spike room)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('RedTowerElevatorBottomLeft'),
|
||||
sm.canUsePowerBombs())
|
||||
)
|
||||
locationsDict["Missile (Kraid)"].AccessFrom = {
|
||||
'Warehouse Zeela Room Left': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Kraid)"].Available = (
|
||||
lambda sm: sm.canUsePowerBombs()
|
||||
)
|
||||
locationsDict["Missile (lava room)"].AccessFrom = {
|
||||
'Cathedral': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (lava room)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (below Ice Beam)"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wand(sm.traverse('BusinessCenterTopLeft'),
|
||||
sm.canUsePowerBombs(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['Ice']['Norfair Entrance -> Ice Beam']),
|
||||
sm.wor(sm.wand(sm.haveItem('Morph'),
|
||||
sm.knowsMockball()),
|
||||
sm.haveItem('SpeedBooster'))),
|
||||
'Crocomire Speedway Bottom': lambda sm: sm.wand(sm.canUseCrocRoomToChargeSpeed(),
|
||||
sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Croc -> Ice Missiles']),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.knowsIceMissileFromCroc())
|
||||
}
|
||||
locationsDict["Missile (below Ice Beam)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (above Crocomire)"].AccessFrom = {
|
||||
'Crocomire Speedway Bottom': lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Croc -> Grapple Escape Missiles'])
|
||||
}
|
||||
locationsDict["Missile (above Crocomire)"].Available = (
|
||||
lambda sm: sm.canGrappleEscape()
|
||||
)
|
||||
locationsDict["Missile (Hi-Jump Boots)"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HiJumpAreaBlueDoor), sm.traverse('BusinessCenterBottomLeft'))
|
||||
}
|
||||
locationsDict["Missile (Hi-Jump Boots)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (Hi-Jump Boots)"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.canPassBombPassages(),
|
||||
sm.wand(RomPatches.has(sm.player, RomPatches.HiJumpShotBlock), sm.haveItem('Morph')))
|
||||
)
|
||||
locationsDict["Energy Tank (Hi-Jump Boots)"].AccessFrom = {
|
||||
'Business Center': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.HiJumpAreaBlueDoor), sm.traverse('BusinessCenterBottomLeft'))
|
||||
}
|
||||
locationsDict["Energy Tank (Hi-Jump Boots)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (Crocomire)"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (Crocomire)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('PostCrocomireUpperLeft'),
|
||||
sm.enoughStuffCroc(),
|
||||
sm.wor(sm.wor(sm.canFly(),
|
||||
sm.haveItem('Grapple'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.heatProof(),
|
||||
sm.energyReserveCountOk(1)))), # spark from the room before
|
||||
sm.wor(sm.haveItem('HiJump'), # run and jump from yellow platform
|
||||
sm.wand(sm.haveItem('Ice'),
|
||||
sm.knowsCrocPBsIce()),
|
||||
sm.knowsCrocPBsDBoost())))
|
||||
)
|
||||
locationsDict["Missile (below Crocomire)"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (below Crocomire)"].Available = (
|
||||
lambda sm: sm.wand(sm.traverse('PostCrocomireShaftRight'), sm.enoughStuffCroc(), sm.haveItem('Morph'))
|
||||
)
|
||||
locationsDict["Missile (Grapple Beam)"].AccessFrom = {
|
||||
'Crocomire Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Grapple Beam)"].Available = (
|
||||
lambda sm: sm.wand(sm.enoughStuffCroc(),
|
||||
sm.wor(sm.wor(sm.wand(sm.haveItem('Morph'), # from below
|
||||
sm.canFly()),
|
||||
sm.wand(sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.knowsShortCharge(),
|
||||
sm.canUsePowerBombs()))),
|
||||
sm.wand(sm.canGreenGateGlitch(), # from grapple room
|
||||
sm.canFly()))) # TODO::test if accessible with a spark (short charge), and how many etanks required
|
||||
)
|
||||
locationsDict["Missile (Grapple Beam)"].PostAvailable = (
|
||||
lambda sm: sm.wor(sm.haveItem('Morph'), # normal exit
|
||||
sm.wand(sm.haveItem('Super'), # go back to grapple room
|
||||
sm.wor(sm.haveItem('SpaceJump'),
|
||||
sm.wand(sm.haveItem('SpeedBooster'), sm.haveItem('HiJump'))))) # jump from the yellow plateform ennemy
|
||||
)
|
||||
locationsDict["Missile (Norfair Reserve Tank)"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutain(),
|
||||
'Bubble Mountain Top': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutainTop()
|
||||
}
|
||||
locationsDict["Missile (Norfair Reserve Tank)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Morph'), sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Reserve']))
|
||||
)
|
||||
locationsDict["Missile (bubble Norfair green door)"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutain(),
|
||||
'Bubble Mountain Top': lambda sm: sm.canEnterNorfairReserveAreaFromBubbleMoutainTop()
|
||||
}
|
||||
locationsDict["Missile (bubble Norfair green door)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['MainUpperNorfair']['Bubble -> Norfair Reserve Missiles'])
|
||||
)
|
||||
locationsDict["Missile (bubble Norfair)"].AccessFrom = {
|
||||
'Bubble Mountain': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (bubble Norfair)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Speed Booster)"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.wor(RomPatches.has(sm.player, RomPatches.SpeedAreaBlueDoors),
|
||||
sm.traverse('BubbleMountainTopRight'))
|
||||
}
|
||||
locationsDict["Missile (Speed Booster)"].Available = (
|
||||
lambda sm: sm.canHellRunToSpeedBooster()
|
||||
)
|
||||
locationsDict["Missile (Wave Beam)"].AccessFrom = {
|
||||
'Bubble Mountain Top': lambda sm: sm.canAccessDoubleChamberItems()
|
||||
}
|
||||
locationsDict["Missile (Wave Beam)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Gold Torizo)"].AccessFrom = {
|
||||
'LN Above GT': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Gold Torizo)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
)
|
||||
locationsDict["Missile (Gold Torizo)"].PostAvailable = (
|
||||
lambda sm: sm.enoughStuffGT()
|
||||
)
|
||||
locationsDict["Super Missile (Gold Torizo)"].AccessFrom = {
|
||||
'Screw Attack Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (Gold Torizo)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (Gold Torizo)"].PostAvailable = (
|
||||
lambda sm: sm.enoughStuffGT()
|
||||
)
|
||||
locationsDict["Missile (Mickey Mouse room)"].AccessFrom = {
|
||||
'LN Entrance': lambda sm: sm.wand(sm.canUsePowerBombs(), sm.canPassWorstRoom()),
|
||||
}
|
||||
locationsDict["Missile (Mickey Mouse room)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
)
|
||||
locationsDict["Missile (lower Norfair above fire flea room)"].AccessFrom = {
|
||||
'Firefleas': lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
}
|
||||
locationsDict["Missile (lower Norfair above fire flea room)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (lower Norfair above fire flea room)"].AccessFrom = {
|
||||
'Firefleas Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Power Bomb (lower Norfair above fire flea room)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Power Bomb (Power Bombs of shame)"].AccessFrom = {
|
||||
'Ridley Zone': lambda sm: sm.canUsePowerBombs()
|
||||
}
|
||||
locationsDict["Power Bomb (Power Bombs of shame)"].Available = (
|
||||
lambda sm: sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main'])
|
||||
)
|
||||
locationsDict["Missile (lower Norfair near Wave Beam)"].AccessFrom = {
|
||||
'Firefleas': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (lower Norfair near Wave Beam)"].Available = (
|
||||
lambda sm: sm.wand(sm.canHellRun(**Settings.hellRunsTable['LowerNorfair']['Main']),
|
||||
sm.canDestroyBombWalls(),
|
||||
sm.haveItem('Morph'))
|
||||
)
|
||||
locationsDict["Missile (Wrecked Ship middle)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Wrecked Ship middle)"].Available = (
|
||||
lambda sm: sm.canPassBombPassages()
|
||||
)
|
||||
locationsDict["Missile (Gravity Suit)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Gravity Suit)"].Available = (
|
||||
lambda sm: sm.wand(sm.canPassBowling(),
|
||||
sm.canPassBombPassages())
|
||||
)
|
||||
locationsDict["Missile (Wrecked Ship top)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Wrecked Ship top)"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
)
|
||||
locationsDict["Super Missile (Wrecked Ship left)"].AccessFrom = {
|
||||
'Wrecked Ship Main': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (Wrecked Ship left)"].Available = (
|
||||
lambda sm: Bosses.bossDead(sm, 'Phantoon')
|
||||
)
|
||||
locationsDict["Missile (green Maridia shinespark)"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (green Maridia shinespark)"].Available = (
|
||||
lambda sm: sm.wand(sm.haveItem('Gravity'),
|
||||
sm.haveItem('SpeedBooster'),
|
||||
sm.wor(sm.wand(sm.traverse('MainStreetBottomRight'), # run from room on the right
|
||||
sm.wor(RomPatches.has(sm.player, RomPatches.AreaRandoGatesOther),
|
||||
sm.haveItem('Super')),
|
||||
sm.itemCountOk('ETank', 1)), # etank for the spark since sparking from low ground
|
||||
sm.canSimpleShortCharge())) # run from above
|
||||
)
|
||||
locationsDict["Super Missile (green Maridia)"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: sm.canDoOuterMaridia()
|
||||
}
|
||||
locationsDict["Super Missile (green Maridia)"].Available = (
|
||||
lambda sm: sm.haveItem('Morph')
|
||||
)
|
||||
locationsDict["Missile (green Maridia tatori)"].AccessFrom = {
|
||||
'Main Street Bottom': lambda sm: sm.wand(sm.wor(sm.traverse('FishTankRight'),
|
||||
RomPatches.has(sm.player, RomPatches.MamaTurtleBlueDoor)),
|
||||
sm.canDoOuterMaridia()),
|
||||
'Mama Turtle': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (green Maridia tatori)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (yellow Maridia)"].AccessFrom = {
|
||||
'Watering Hole Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (yellow Maridia)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (yellow Maridia super missile)"].AccessFrom = {
|
||||
'Watering Hole Bottom': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (yellow Maridia super missile)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (yellow Maridia false wall)"].AccessFrom = {
|
||||
'Beach': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (yellow Maridia false wall)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (left Maridia sand pit room)"].AccessFrom = {
|
||||
'Left Sandpit': lambda sm: sm.canClimbWestSandHole()
|
||||
}
|
||||
locationsDict["Missile (left Maridia sand pit room)"].Available = (
|
||||
lambda sm: sm.canAccessItemsInWestSandHole()
|
||||
)
|
||||
locationsDict["Missile (right Maridia sand pit room)"].AccessFrom = {
|
||||
'Right Sandpit': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (right Maridia sand pit room)"].Available = (
|
||||
lambda sm: sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.haveItem('HiJump'),
|
||||
sm.knowsGravLessLevel3()))
|
||||
)
|
||||
locationsDict["Power Bomb (right Maridia sand pit room)"].AccessFrom = {
|
||||
'Right Sandpit': lambda sm: sm.haveItem('Morph')
|
||||
}
|
||||
locationsDict["Power Bomb (right Maridia sand pit room)"].Available = (
|
||||
lambda sm: sm.wor(sm.haveItem('Gravity'),
|
||||
sm.wand(sm.knowsGravLessLevel3(),
|
||||
sm.haveItem('HiJump'),
|
||||
sm.canSpringBallJump())) # https://www.youtube.com/watch?v=7LYYxphRRT0
|
||||
)
|
||||
locationsDict["Missile (pink Maridia)"].AccessFrom = {
|
||||
'Aqueduct': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (pink Maridia)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Super Missile (pink Maridia)"].AccessFrom = {
|
||||
'Aqueduct': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Super Missile (pink Maridia)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
locationsDict["Missile (Draygon)"].AccessFrom = {
|
||||
'Precious Room Top': lambda sm: SMBool(True)
|
||||
}
|
||||
locationsDict["Missile (Draygon)"].Available = (
|
||||
lambda sm: SMBool(True)
|
||||
)
|
||||
|
||||
# TODO::use the dict in solver/randomizer
|
||||
# create the list that the solver/randomizer use
|
||||
locations = [loc for loc in locationsDict.values()]
|
||||
|
||||
class LocationsHelper:
|
||||
# used by FillerRandom to know how many front fill steps it must perform
|
||||
def getRandomFillHelp(startLocation):
|
||||
helpByAp = {
|
||||
"Firefleas Top": 3,
|
||||
"Aqueduct": 1,
|
||||
"Mama Turtle": 1,
|
||||
"Watering Hole": 2,
|
||||
"Etecoons Supers": 2,
|
||||
"Gauntlet Top":1,
|
||||
"Bubble Mountain":1
|
||||
}
|
||||
return helpByAp[startLocation] if startLocation in helpByAp else 0
|
||||
|
||||
# for a given start AP, gives:
|
||||
# - locations that can be used as majors/chozo in the start area
|
||||
# - locations to preserve in the split
|
||||
# - number of necessary majors locations to add in the start area,
|
||||
# - number of necessary chozo locations to add in the start area
|
||||
# locs are taken in the first n in the list
|
||||
def getStartMajors(startLocation):
|
||||
majLocsByAp = {
|
||||
'Gauntlet Top': ([
|
||||
"Missile (Crateria gauntlet right)",
|
||||
"Missile (Crateria gauntlet left)"
|
||||
], ["Energy Tank, Terminator"], 1, 2),
|
||||
'Green Brinstar Elevator': ([
|
||||
"Missile (green Brinstar below super missile)"
|
||||
], ["Reserve Tank, Brinstar"], 1, 1),
|
||||
'Big Pink': ([
|
||||
"Missile (pink Brinstar top)",
|
||||
"Missile (pink Brinstar bottom)"
|
||||
], ["Charge Beam"], 1, 2),
|
||||
'Etecoons Supers': ([
|
||||
"Energy Tank, Etecoons",
|
||||
"Super Missile (green Brinstar bottom)",
|
||||
], ["Energy Tank, Etecoons"], 1, 2),
|
||||
'Firefleas Top': ([
|
||||
"Power Bomb (lower Norfair above fire flea room)",
|
||||
"Energy Tank, Firefleas",
|
||||
"Missile (lower Norfair near Wave Beam)",
|
||||
"Missile (lower Norfair above fire flea room)"
|
||||
], ["Energy Tank, Firefleas"], 3, 4),
|
||||
'Business Center': ([
|
||||
"Energy Tank (Hi-Jump Boots)",
|
||||
], ["Hi-Jump Boots"], 1, 1),
|
||||
'Bubble Mountain': ([
|
||||
"Missile (bubble Norfair)"
|
||||
], ["Speed Booster", "Wave Beam"], 1, 1),
|
||||
'Mama Turtle': ([
|
||||
"Energy Tank, Mama turtle",
|
||||
"Missile (green Maridia tatori)",
|
||||
"Super Missile (green Maridia)"
|
||||
], ["Energy Tank, Mama turtle"], 2, 3),
|
||||
'Watering Hole': ([
|
||||
"Missile (yellow Maridia super missile)",
|
||||
"Super Missile (yellow Maridia)",
|
||||
"Missile (yellow Maridia false wall)"
|
||||
], [], 2, 3),
|
||||
'Aqueduct': ([
|
||||
"Missile (pink Maridia)",
|
||||
"Super Missile (pink Maridia)",
|
||||
"Missile (right Maridia sand pit room)"
|
||||
], ["Reserve Tank, Maridia"], 2, 3)
|
||||
}
|
||||
return majLocsByAp[startLocation] if startLocation in majLocsByAp else ([],[],0,0)
|
||||
Reference in New Issue
Block a user