mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
Added Super Metroid support (#46)
Varia Randomizer based implementation LttPClient -> SNIClient
This commit is contained in:
575
worlds/sm/variaRandomizer/graph/graph_utils.py
Normal file
575
worlds/sm/variaRandomizer/graph/graph_utils.py
Normal file
@@ -0,0 +1,575 @@
|
||||
import copy
|
||||
import random
|
||||
from logic.logic import Logic
|
||||
from utils.parameters import Knows
|
||||
from graph.location import locationsDict
|
||||
from rom.rom import snes_to_pc
|
||||
import utils.log
|
||||
|
||||
# order expected by ROM patches
|
||||
graphAreas = [
|
||||
"Ceres",
|
||||
"Crateria",
|
||||
"GreenPinkBrinstar",
|
||||
"RedBrinstar",
|
||||
"WreckedShip",
|
||||
"Kraid",
|
||||
"Norfair",
|
||||
"Crocomire",
|
||||
"LowerNorfair",
|
||||
"WestMaridia",
|
||||
"EastMaridia",
|
||||
"Tourian"
|
||||
]
|
||||
|
||||
vanillaTransitions = [
|
||||
('Lower Mushrooms Left', 'Green Brinstar Elevator'),
|
||||
('Morph Ball Room Left', 'Green Hill Zone Top Right'),
|
||||
('Moat Right', 'West Ocean Left'),
|
||||
('Keyhunter Room Bottom', 'Red Brinstar Elevator'),
|
||||
('Noob Bridge Right', 'Red Tower Top Left'),
|
||||
('Crab Maze Left', 'Le Coude Right'),
|
||||
('Kronic Boost Room Bottom Left', 'Lava Dive Right'),
|
||||
('Crocomire Speedway Bottom', 'Crocomire Room Top'),
|
||||
('Three Muskateers Room Left', 'Single Chamber Top Right'),
|
||||
('Warehouse Entrance Left', 'East Tunnel Right'),
|
||||
('East Tunnel Top Right', 'Crab Hole Bottom Left'),
|
||||
('Caterpillar Room Top Right', 'Red Fish Room Left'),
|
||||
('Glass Tunnel Top', 'Main Street Bottom'),
|
||||
('Green Pirates Shaft Bottom Right', 'Golden Four'),
|
||||
('Warehouse Entrance Right', 'Warehouse Zeela Room Left'),
|
||||
('Crab Shaft Right', 'Aqueduct Top Left')
|
||||
]
|
||||
|
||||
vanillaBossesTransitions = [
|
||||
('KraidRoomOut', 'KraidRoomIn'),
|
||||
('PhantoonRoomOut', 'PhantoonRoomIn'),
|
||||
('DraygonRoomOut', 'DraygonRoomIn'),
|
||||
('RidleyRoomOut', 'RidleyRoomIn')
|
||||
]
|
||||
|
||||
# vanilla escape transition in first position
|
||||
vanillaEscapeTransitions = [
|
||||
('Tourian Escape Room 4 Top Right', 'Climb Bottom Left'),
|
||||
('Brinstar Pre-Map Room Right', 'Green Brinstar Main Shaft Top Left'),
|
||||
('Wrecked Ship Map Room', 'Basement Left'),
|
||||
('Norfair Map Room', 'Business Center Mid Left'),
|
||||
('Maridia Map Room', 'Crab Hole Bottom Right')
|
||||
]
|
||||
|
||||
vanillaEscapeAnimalsTransitions = [
|
||||
('Flyway Right 0', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 1', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 2', 'Bomb Torizo Room Left'),
|
||||
('Flyway Right 3', 'Bomb Torizo Room Left'),
|
||||
('Bomb Torizo Room Left Animals', 'Flyway Right')
|
||||
]
|
||||
|
||||
escapeSource = 'Tourian Escape Room 4 Top Right'
|
||||
escapeTargets = ['Green Brinstar Main Shaft Top Left', 'Basement Left', 'Business Center Mid Left', 'Crab Hole Bottom Right']
|
||||
|
||||
locIdsByAreaAddresses = {
|
||||
"Ceres": snes_to_pc(0xA1F568),
|
||||
"Crateria": snes_to_pc(0xA1F569),
|
||||
"GreenPinkBrinstar": snes_to_pc(0xA1F57B),
|
||||
"RedBrinstar": snes_to_pc(0xA1F58C),
|
||||
"WreckedShip": snes_to_pc(0xA1F592),
|
||||
"Kraid": snes_to_pc(0xA1F59E),
|
||||
"Norfair": snes_to_pc(0xA1F5A2),
|
||||
"Crocomire": snes_to_pc(0xA1F5B2),
|
||||
"LowerNorfair": snes_to_pc(0xA1F5B8),
|
||||
"WestMaridia": snes_to_pc(0xA1F5C3),
|
||||
"EastMaridia": snes_to_pc(0xA1F5CB),
|
||||
"Tourian": snes_to_pc(0xA1F5D7)
|
||||
}
|
||||
|
||||
def getAccessPoint(apName, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
return next(ap for ap in apList if ap.Name == apName)
|
||||
|
||||
class GraphUtils:
|
||||
log = utils.log.get('GraphUtils')
|
||||
|
||||
def getStartAccessPointNames():
|
||||
return [ap.Name for ap in Logic.accessPoints if ap.Start is not None]
|
||||
|
||||
def getStartAccessPointNamesCategory():
|
||||
ret = {'regular': [], 'custom': [], 'area': []}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Start == None:
|
||||
continue
|
||||
elif 'areaMode' in ap.Start and ap.Start['areaMode'] == True:
|
||||
ret['area'].append(ap.Name)
|
||||
elif GraphUtils.isStandardStart(ap.Name):
|
||||
ret['regular'].append(ap.Name)
|
||||
else:
|
||||
ret['custom'].append(ap.Name)
|
||||
return ret
|
||||
|
||||
def isStandardStart(startApName):
|
||||
return startApName == 'Ceres' or startApName == 'Landing Site'
|
||||
|
||||
def getPossibleStartAPs(areaMode, maxDiff, morphPlacement, player):
|
||||
ret = []
|
||||
refused = {}
|
||||
allStartAPs = GraphUtils.getStartAccessPointNames()
|
||||
for apName in allStartAPs:
|
||||
start = getAccessPoint(apName).Start
|
||||
ok = True
|
||||
cause = ""
|
||||
if 'knows' in start:
|
||||
for k in start['knows']:
|
||||
if not Knows.knowsDict[player].knows(k, maxDiff):
|
||||
ok = False
|
||||
cause += Knows.desc[k]['display']+" is not known. "
|
||||
break
|
||||
if 'areaMode' in start and start['areaMode'] != areaMode:
|
||||
ok = False
|
||||
cause += "Start location available only with area randomization enabled. "
|
||||
if 'forcedEarlyMorph' in start and start['forcedEarlyMorph'] == True and morphPlacement == 'late':
|
||||
ok = False
|
||||
cause += "Start location unavailable with late morph placement. "
|
||||
if ok:
|
||||
ret.append(apName)
|
||||
else:
|
||||
refused[apName] = cause
|
||||
return ret, refused
|
||||
|
||||
def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs):
|
||||
locs = locationsDict
|
||||
preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)]
|
||||
possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs]
|
||||
GraphUtils.log.debug("possLocs="+str([loc.Name for loc in possLocs]))
|
||||
candidates = [loc for loc in locs.values() if loc.GraphArea == startGraphArea and loc.isClass(split) and loc not in preserveMajLocs]
|
||||
remLocs = [loc for loc in locs.values() if loc not in possLocs and loc not in candidates and loc.isClass(split)]
|
||||
newLocs = []
|
||||
while len(newLocs) < nLocs:
|
||||
if len(candidates) == 0:
|
||||
candidates = remLocs
|
||||
loc = possLocs.pop(random.randint(0,len(possLocs)-1))
|
||||
newLocs.append(loc)
|
||||
loc.setClass([split])
|
||||
if not loc in preserveMajLocs:
|
||||
GraphUtils.log.debug("newMajor="+loc.Name)
|
||||
loc = candidates.pop(random.randint(0,len(candidates)-1))
|
||||
loc.setClass(["Minor"])
|
||||
GraphUtils.log.debug("replaced="+loc.Name)
|
||||
|
||||
def getGraphPatches(startApName):
|
||||
ap = getAccessPoint(startApName)
|
||||
return ap.Start['patches'] if 'patches' in ap.Start else []
|
||||
|
||||
def createBossesTransitions():
|
||||
transitions = vanillaBossesTransitions
|
||||
def isVanilla():
|
||||
for t in vanillaBossesTransitions:
|
||||
if t not in transitions:
|
||||
return False
|
||||
return True
|
||||
while isVanilla():
|
||||
transitions = []
|
||||
srcs = []
|
||||
dsts = []
|
||||
for (src,dst) in vanillaBossesTransitions:
|
||||
srcs.append(src)
|
||||
dsts.append(dst)
|
||||
while len(srcs) > 0:
|
||||
src = srcs.pop(random.randint(0,len(srcs)-1))
|
||||
dst = dsts.pop(random.randint(0,len(dsts)-1))
|
||||
transitions.append((src,dst))
|
||||
return transitions
|
||||
|
||||
def createAreaTransitions(lightAreaRando=False):
|
||||
if lightAreaRando:
|
||||
return GraphUtils.createLightAreaTransitions()
|
||||
else:
|
||||
return GraphUtils.createRegularAreaTransitions()
|
||||
|
||||
def createRegularAreaTransitions(apList=None, apPred=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
if apPred is None:
|
||||
apPred = lambda ap: ap.isArea()
|
||||
tFrom = []
|
||||
tTo = []
|
||||
apNames = [ap.Name for ap in apList if apPred(ap) == True]
|
||||
transitions = []
|
||||
|
||||
def findTo(trFrom):
|
||||
ap = getAccessPoint(trFrom, apList)
|
||||
fromArea = ap.GraphArea
|
||||
targets = [apName for apName in apNames if apName not in tTo and getAccessPoint(apName, apList).GraphArea != fromArea]
|
||||
if len(targets) == 0: # fallback if no area transition is found
|
||||
targets = [apName for apName in apNames if apName != ap.Name]
|
||||
if len(targets) == 0: # extreme fallback: loop on itself
|
||||
targets = [ap.Name]
|
||||
return random.choice(targets)
|
||||
|
||||
def addTransition(src, dst):
|
||||
tFrom.append(src)
|
||||
tTo.append(dst)
|
||||
|
||||
while len(apNames) > 0:
|
||||
sources = [apName for apName in apNames if apName not in tFrom]
|
||||
src = random.choice(sources)
|
||||
dst = findTo(src)
|
||||
transitions.append((src, dst))
|
||||
addTransition(src, dst)
|
||||
addTransition(dst, src)
|
||||
toRemove = [apName for apName in apNames if apName in tFrom and apName in tTo]
|
||||
for apName in toRemove:
|
||||
apNames.remove(apName)
|
||||
return transitions
|
||||
|
||||
def getAPs(apPredicate, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
return [ap for ap in apList if apPredicate(ap) == True]
|
||||
|
||||
def loopUnusedTransitions(transitions, apList=None):
|
||||
if apList is None:
|
||||
apList = Logic.accessPoints
|
||||
usedAPs = set()
|
||||
for (src,dst) in transitions:
|
||||
usedAPs.add(getAccessPoint(src, apList))
|
||||
usedAPs.add(getAccessPoint(dst, apList))
|
||||
unusedAPs = [ap for ap in apList if not ap.isInternal() and ap not in usedAPs]
|
||||
for ap in unusedAPs:
|
||||
transitions.append((ap.Name, ap.Name))
|
||||
|
||||
def createMinimizerTransitions(startApName, locLimit):
|
||||
if startApName == 'Ceres':
|
||||
startApName = 'Landing Site'
|
||||
startAp = getAccessPoint(startApName)
|
||||
def getNLocs(locsPredicate, locList=None):
|
||||
if locList is None:
|
||||
locList = Logic.locations
|
||||
# leave out bosses and count post boss locs systematically
|
||||
return len([loc for loc in locList if locsPredicate(loc) == True and not loc.SolveArea.endswith(" Boss") and not loc.isBoss()])
|
||||
availAreas = list(sorted({ap.GraphArea for ap in Logic.accessPoints if ap.GraphArea != startAp.GraphArea and getNLocs(lambda loc: loc.GraphArea == ap.GraphArea) > 0}))
|
||||
areas = [startAp.GraphArea]
|
||||
GraphUtils.log.debug("availAreas: {}".format(availAreas))
|
||||
GraphUtils.log.debug("areas: {}".format(areas))
|
||||
inBossCheck = lambda ap: ap.Boss and ap.Name.endswith("In")
|
||||
nLocs = 0
|
||||
transitions = []
|
||||
usedAPs = []
|
||||
trLimit = 5
|
||||
locLimit -= 3 # 3 "post boss" locs will always be available, and are filtered out in getNLocs
|
||||
def openTransitions():
|
||||
nonlocal areas, inBossCheck, usedAPs
|
||||
return GraphUtils.getAPs(lambda ap: ap.GraphArea in areas and not ap.isInternal() and not inBossCheck(ap) and not ap in usedAPs)
|
||||
while nLocs < locLimit or len(openTransitions()) < trLimit:
|
||||
GraphUtils.log.debug("openTransitions="+str([ap.Name for ap in openTransitions()]))
|
||||
fromAreas = availAreas
|
||||
if nLocs >= locLimit:
|
||||
GraphUtils.log.debug("not enough open transitions")
|
||||
# we just need transitions, avoid adding a huge area
|
||||
fromAreas = []
|
||||
n = trLimit - len(openTransitions())
|
||||
while len(fromAreas) == 0:
|
||||
fromAreas = [area for area in availAreas if len(GraphUtils.getAPs(lambda ap: not ap.isInternal())) > n]
|
||||
n -= 1
|
||||
minLocs = min([getNLocs(lambda loc: loc.GraphArea == area) for area in fromAreas])
|
||||
fromAreas = [area for area in fromAreas if getNLocs(lambda loc: loc.GraphArea == area) == minLocs]
|
||||
elif len(openTransitions()) <= 1: # dont' get stuck by adding dead ends
|
||||
fromAreas = [area for area in fromAreas if len(GraphUtils.getAPs(lambda ap: ap.GraphArea == area and not ap.isInternal())) > 1]
|
||||
nextArea = random.choice(fromAreas)
|
||||
GraphUtils.log.debug("nextArea="+str(nextArea))
|
||||
apCheck = lambda ap: not ap.isInternal() and not inBossCheck(ap) and ap not in usedAPs
|
||||
possibleSources = GraphUtils.getAPs(lambda ap: ap.GraphArea in areas and apCheck(ap))
|
||||
possibleTargets = GraphUtils.getAPs(lambda ap: ap.GraphArea == nextArea and apCheck(ap))
|
||||
src = random.choice(possibleSources)
|
||||
dst = random.choice(possibleTargets)
|
||||
usedAPs += [src,dst]
|
||||
GraphUtils.log.debug("add transition: (src: {}, dst: {})".format(src.Name, dst.Name))
|
||||
transitions.append((src.Name,dst.Name))
|
||||
availAreas.remove(nextArea)
|
||||
areas.append(nextArea)
|
||||
GraphUtils.log.debug("areas: {}".format(areas))
|
||||
nLocs = getNLocs(lambda loc:loc.GraphArea in areas)
|
||||
GraphUtils.log.debug("nLocs: {}".format(nLocs))
|
||||
# we picked the areas, add transitions (bosses and tourian first)
|
||||
sourceAPs = openTransitions()
|
||||
random.shuffle(sourceAPs)
|
||||
targetAPs = GraphUtils.getAPs(lambda ap: (inBossCheck(ap) or ap.Name == "Golden Four") and not ap in usedAPs)
|
||||
random.shuffle(targetAPs)
|
||||
assert len(sourceAPs) >= len(targetAPs), "Minimizer: less source than target APs"
|
||||
while len(targetAPs) > 0:
|
||||
transitions.append((sourceAPs.pop().Name, targetAPs.pop().Name))
|
||||
transitions += GraphUtils.createRegularAreaTransitions(sourceAPs, lambda ap: not ap.isInternal())
|
||||
GraphUtils.log.debug("FINAL MINIMIZER transitions: {}".format(transitions))
|
||||
GraphUtils.loopUnusedTransitions(transitions)
|
||||
GraphUtils.log.debug("FINAL MINIMIZER nLocs: "+str(nLocs+3))
|
||||
GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas))
|
||||
return transitions
|
||||
|
||||
def createLightAreaTransitions():
|
||||
# group APs by area
|
||||
aps = {}
|
||||
totalCount = 0
|
||||
for ap in Logic.accessPoints:
|
||||
if not ap.isArea():
|
||||
continue
|
||||
if not ap.GraphArea in aps:
|
||||
aps[ap.GraphArea] = {'totalCount': 0, 'transCount': {}, 'apNames': []}
|
||||
aps[ap.GraphArea]['apNames'].append(ap.Name)
|
||||
# count number of vanilla transitions between each area
|
||||
for (srcName, destName) in vanillaTransitions:
|
||||
srcAP = getAccessPoint(srcName)
|
||||
destAP = getAccessPoint(destName)
|
||||
aps[srcAP.GraphArea]['transCount'][destAP.GraphArea] = aps[srcAP.GraphArea]['transCount'].get(destAP.GraphArea, 0) + 1
|
||||
aps[srcAP.GraphArea]['totalCount'] += 1
|
||||
aps[destAP.GraphArea]['transCount'][srcAP.GraphArea] = aps[destAP.GraphArea]['transCount'].get(srcAP.GraphArea, 0) + 1
|
||||
aps[destAP.GraphArea]['totalCount'] += 1
|
||||
totalCount += 1
|
||||
|
||||
transitions = []
|
||||
while totalCount > 0:
|
||||
# choose transition
|
||||
srcArea = random.choice(list(aps.keys()))
|
||||
srcName = random.choice(aps[srcArea]['apNames'])
|
||||
src = getAccessPoint(srcName)
|
||||
destArea = random.choice(list(aps[src.GraphArea]['transCount'].keys()))
|
||||
destName = random.choice(aps[destArea]['apNames'])
|
||||
transitions.append((srcName, destName))
|
||||
|
||||
# update counts
|
||||
totalCount -= 1
|
||||
aps[srcArea]['totalCount'] -= 1
|
||||
aps[destArea]['totalCount'] -= 1
|
||||
aps[srcArea]['transCount'][destArea] -= 1
|
||||
if aps[srcArea]['transCount'][destArea] == 0:
|
||||
del aps[srcArea]['transCount'][destArea]
|
||||
aps[destArea]['transCount'][srcArea] -= 1
|
||||
if aps[destArea]['transCount'][srcArea] == 0:
|
||||
del aps[destArea]['transCount'][srcArea]
|
||||
aps[srcArea]['apNames'].remove(srcName)
|
||||
aps[destArea]['apNames'].remove(destName)
|
||||
|
||||
if aps[srcArea]['totalCount'] == 0:
|
||||
del aps[srcArea]
|
||||
if aps[destArea]['totalCount'] == 0:
|
||||
del aps[destArea]
|
||||
|
||||
return transitions
|
||||
|
||||
def getVanillaExit(apName):
|
||||
allVanillaTransitions = vanillaTransitions + vanillaBossesTransitions + vanillaEscapeTransitions
|
||||
for (src,dst) in allVanillaTransitions:
|
||||
if apName == src:
|
||||
return dst
|
||||
if apName == dst:
|
||||
return src
|
||||
return None
|
||||
|
||||
def isEscapeAnimals(apName):
|
||||
return 'Flyway Right' in apName or 'Bomb Torizo Room Left' in apName
|
||||
|
||||
# gets dict like
|
||||
# (RoomPtr, (vanilla entry screen X, vanilla entry screen Y)): AP
|
||||
def getRooms():
|
||||
rooms = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
# special ap for random escape animals surprise
|
||||
if GraphUtils.isEscapeAnimals(ap.Name):
|
||||
continue
|
||||
|
||||
roomPtr = ap.RoomInfo['RoomPtr']
|
||||
|
||||
vanillaExitName = GraphUtils.getVanillaExit(ap.Name)
|
||||
# special ap for random escape animals surprise
|
||||
if GraphUtils.isEscapeAnimals(vanillaExitName):
|
||||
continue
|
||||
|
||||
connAP = getAccessPoint(vanillaExitName)
|
||||
entryInfo = connAP.ExitInfo
|
||||
rooms[(roomPtr, entryInfo['screen'], entryInfo['direction'])] = ap
|
||||
rooms[(roomPtr, entryInfo['screen'], (ap.EntryInfo['SamusX'], ap.EntryInfo['SamusY']))] = ap
|
||||
# for boss rando with incompatible ridley transition, also register this one
|
||||
if ap.Name == 'RidleyRoomIn':
|
||||
rooms[(roomPtr, (0x0, 0x1), 0x5)] = ap
|
||||
rooms[(roomPtr, (0x0, 0x1), (0xbf, 0x198))] = ap
|
||||
|
||||
return rooms
|
||||
|
||||
def escapeAnimalsTransitions(graph, possibleTargets, firstEscape):
|
||||
n = len(possibleTargets)
|
||||
assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets)
|
||||
# first get our list of 4 entries for escape patch
|
||||
if n >= 1:
|
||||
# get actual animals: pick one of the remaining targets
|
||||
animalsAccess = possibleTargets.pop()
|
||||
graph.EscapeAttributes['Animals'] = animalsAccess
|
||||
# we now have at most 3 targets left, fill up to fill cycling 4 targets for animals suprise
|
||||
possibleTargets.append('Climb Bottom Left')
|
||||
if firstEscape is not None:
|
||||
possibleTargets.append(firstEscape)
|
||||
poss = possibleTargets[:]
|
||||
while len(possibleTargets) < 4:
|
||||
possibleTargets.append(poss.pop(random.randint(0, len(poss)-1)))
|
||||
else:
|
||||
# failsafe: if not enough targets left, abort and do vanilla animals
|
||||
animalsAccess = 'Flyway Right'
|
||||
possibleTargets = ['Bomb Torizo Room Left'] * 4
|
||||
GraphUtils.log.debug("escapeAnimalsTransitions. animalsAccess="+animalsAccess)
|
||||
assert len(possibleTargets) == 4, "Invalid possibleTargets list: " + str(possibleTargets)
|
||||
# actually add the 4 connections for successive escapes challenge
|
||||
basePtr = 0xADAC
|
||||
btDoor = getAccessPoint('Flyway Right')
|
||||
for i in range(len(possibleTargets)):
|
||||
ap = copy.copy(btDoor)
|
||||
ap.Name += " " + str(i)
|
||||
ap.ExitInfo['DoorPtr'] = basePtr + i*24
|
||||
graph.addAccessPoint(ap)
|
||||
target = possibleTargets[i]
|
||||
graph.addTransition(ap.Name, target)
|
||||
# add the connection for animals access
|
||||
bt = getAccessPoint('Bomb Torizo Room Left')
|
||||
btCpy = copy.copy(bt)
|
||||
btCpy.Name += " Animals"
|
||||
btCpy.ExitInfo['DoorPtr'] = 0xAE00
|
||||
graph.addAccessPoint(btCpy)
|
||||
graph.addTransition(animalsAccess, btCpy.Name)
|
||||
|
||||
def isHorizontal(dir):
|
||||
# up: 0x3, 0x7
|
||||
# down: 0x2, 0x6
|
||||
# left: 0x1, 0x5
|
||||
# right: 0x0, 0x4
|
||||
return dir in [0x1, 0x5, 0x0, 0x4]
|
||||
|
||||
def removeCap(dir):
|
||||
if dir < 4:
|
||||
return dir
|
||||
return dir - 4
|
||||
|
||||
def getDirection(src, dst):
|
||||
exitDir = src.ExitInfo['direction']
|
||||
entryDir = dst.EntryInfo['direction']
|
||||
# compatible transition
|
||||
if exitDir == entryDir:
|
||||
return exitDir
|
||||
# if incompatible but horizontal we keep entry dir (looks more natural)
|
||||
if GraphUtils.isHorizontal(exitDir) and GraphUtils.isHorizontal(entryDir):
|
||||
return entryDir
|
||||
# otherwise keep exit direction and remove cap
|
||||
return GraphUtils.removeCap(exitDir)
|
||||
|
||||
def getBitFlag(srcArea, dstArea, origFlag):
|
||||
flags = origFlag
|
||||
if srcArea == dstArea:
|
||||
flags &= 0xBF
|
||||
else:
|
||||
flags |= 0x40
|
||||
return flags
|
||||
|
||||
def getDoorConnections(graph, areas=True, bosses=False,
|
||||
escape=True, escapeAnimals=True):
|
||||
transitions = []
|
||||
if areas:
|
||||
transitions += vanillaTransitions
|
||||
if bosses:
|
||||
transitions += vanillaBossesTransitions
|
||||
if escape:
|
||||
transitions += vanillaEscapeTransitions
|
||||
if escapeAnimals:
|
||||
transitions += vanillaEscapeAnimalsTransitions
|
||||
for srcName, dstName in transitions:
|
||||
src = graph.accessPoints[srcName]
|
||||
dst = graph.accessPoints[dstName]
|
||||
dst.EntryInfo.update(src.ExitInfo)
|
||||
src.EntryInfo.update(dst.ExitInfo)
|
||||
connections = []
|
||||
for src, dst in graph.InterAreaTransitions:
|
||||
if not (escape and src.Escape and dst.Escape):
|
||||
# area only
|
||||
if not bosses and src.Boss:
|
||||
continue
|
||||
# boss only
|
||||
if not areas and not src.Boss:
|
||||
continue
|
||||
# no random escape
|
||||
if not escape and src.Escape:
|
||||
continue
|
||||
|
||||
conn = {}
|
||||
conn['ID'] = str(src) + ' -> ' + str(dst)
|
||||
# remove duplicates (loop transitions)
|
||||
if any(c['ID'] == conn['ID'] for c in connections):
|
||||
continue
|
||||
# print(conn['ID'])
|
||||
# where to write
|
||||
conn['DoorPtr'] = src.ExitInfo['DoorPtr']
|
||||
# door properties
|
||||
conn['RoomPtr'] = dst.RoomInfo['RoomPtr']
|
||||
conn['doorAsmPtr'] = dst.EntryInfo['doorAsmPtr']
|
||||
if 'exitAsmPtr' in src.ExitInfo:
|
||||
conn['exitAsmPtr'] = src.ExitInfo['exitAsmPtr']
|
||||
conn['direction'] = GraphUtils.getDirection(src, dst)
|
||||
conn['bitFlag'] = GraphUtils.getBitFlag(src.RoomInfo['area'], dst.RoomInfo['area'],
|
||||
dst.EntryInfo['bitFlag'])
|
||||
conn['cap'] = dst.EntryInfo['cap']
|
||||
conn['screen'] = dst.EntryInfo['screen']
|
||||
if conn['direction'] != src.ExitInfo['direction']: # incompatible transition
|
||||
conn['distanceToSpawn'] = 0
|
||||
conn['SamusX'] = dst.EntryInfo['SamusX']
|
||||
conn['SamusY'] = dst.EntryInfo['SamusY']
|
||||
if dst.Name == 'RidleyRoomIn': # special case: spawn samus on ridley platform
|
||||
conn['screen'] = (0x0, 0x1)
|
||||
else:
|
||||
conn['distanceToSpawn'] = dst.EntryInfo['distanceToSpawn']
|
||||
if 'song' in dst.EntryInfo:
|
||||
conn['song'] = dst.EntryInfo['song']
|
||||
conn['songs'] = dst.RoomInfo['songs']
|
||||
connections.append(conn)
|
||||
return connections
|
||||
|
||||
def getDoorsPtrs2Aps():
|
||||
ret = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
ret[ap.ExitInfo["DoorPtr"]] = ap.Name
|
||||
return ret
|
||||
|
||||
def getAps2DoorsPtrs():
|
||||
ret = {}
|
||||
for ap in Logic.accessPoints:
|
||||
if ap.Internal == True:
|
||||
continue
|
||||
ret[ap.Name] = ap.ExitInfo["DoorPtr"]
|
||||
return ret
|
||||
|
||||
def getTransitions(addresses):
|
||||
# build address -> name dict
|
||||
doorsPtrs = GraphUtils.getDoorsPtrs2Aps()
|
||||
|
||||
transitions = []
|
||||
# (src.ExitInfo['DoorPtr'], dst.ExitInfo['DoorPtr'])
|
||||
for (srcDoorPtr, destDoorPtr) in addresses:
|
||||
transitions.append((doorsPtrs[srcDoorPtr], doorsPtrs[destDoorPtr]))
|
||||
|
||||
return transitions
|
||||
|
||||
def hasMixedTransitions(areaTransitions, bossTransitions):
|
||||
vanillaAPs = []
|
||||
for (src, dest) in vanillaTransitions:
|
||||
vanillaAPs += [src, dest]
|
||||
|
||||
vanillaBossesAPs = []
|
||||
for (src, dest) in vanillaBossesTransitions:
|
||||
vanillaBossesAPs += [src, dest]
|
||||
|
||||
for (src, dest) in areaTransitions:
|
||||
if src in vanillaBossesAPs or dest in vanillaBossesAPs:
|
||||
return True
|
||||
|
||||
for (src, dest) in bossTransitions:
|
||||
if src in vanillaAPs or dest in vanillaAPs:
|
||||
return True
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user