Added Super Metroid support (#46)

Varia Randomizer based implementation
LttPClient -> SNIClient
This commit is contained in:
lordlou
2021-11-12 08:00:11 -05:00
committed by GitHub
parent 61ae51b30c
commit 77ec8d4141
141 changed files with 43859 additions and 106 deletions

View 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)

View 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

File diff suppressed because it is too large Load Diff

View 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'),
]

View 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)))

View 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)