Added Super Metroid support (#46)
Varia Randomizer based implementation LttPClient -> SNIClient
This commit is contained in:
0
worlds/sm/variaRandomizer/utils/__init__.py
Normal file
0
worlds/sm/variaRandomizer/utils/__init__.py
Normal file
397
worlds/sm/variaRandomizer/utils/doorsmanager.py
Normal file
397
worlds/sm/variaRandomizer/utils/doorsmanager.py
Normal file
@@ -0,0 +1,397 @@
|
||||
import random
|
||||
import copy
|
||||
from logic.smbool import SMBool
|
||||
from rom.rom_patches import RomPatches
|
||||
import utils.log, logging
|
||||
|
||||
LOG = utils.log.get('DoorsManager')
|
||||
|
||||
colorsList = ['red', 'green', 'yellow', 'wave', 'spazer', 'plasma', 'ice']
|
||||
# 1/15 chance to have the door set to grey
|
||||
colorsListGrey = colorsList * 2 + ['grey']
|
||||
|
||||
class Facing:
|
||||
Left = 0
|
||||
Right = 1
|
||||
Top = 2
|
||||
Bottom = 3
|
||||
|
||||
# door facing left - right - top - bottom
|
||||
plmRed = [0xc88a, 0xc890, 0xc896, 0xc89c]
|
||||
plmGreen = [0xc872, 0xc878, 0xc87e, 0xc884]
|
||||
plmYellow = [0xc85a, 0xc860, 0xc866, 0xc86c]
|
||||
plmGrey = [0xc842, 0xc848, 0xc84e, 0xc854]
|
||||
plmWave = [0xf763, 0xf769, 0xf70f, 0xf715]
|
||||
plmSpazer = [0xf733, 0xf739, 0xf73f, 0xf745]
|
||||
plmPlasma = [0xf74b, 0xf751, 0xf757, 0xf75d]
|
||||
plmIce = [0xf71b, 0xf721, 0xf727, 0xf72d]
|
||||
|
||||
colors2plm = {
|
||||
'red': plmRed,
|
||||
'green': plmGreen,
|
||||
'yellow': plmYellow,
|
||||
'grey': plmGrey,
|
||||
'wave': plmWave,
|
||||
'spazer': plmSpazer,
|
||||
'plasma': plmPlasma,
|
||||
'ice': plmIce
|
||||
}
|
||||
|
||||
class Door(object):
|
||||
__slots__ = ('name', 'address', 'vanillaColor', 'color', 'forced', 'facing', 'hidden', 'id', 'canGrey', 'forbiddenColors')
|
||||
def __init__(self, name, address, vanillaColor, facing, id=None, canGrey=False, forbiddenColors=None):
|
||||
self.name = name
|
||||
self.address = address
|
||||
self.vanillaColor = vanillaColor
|
||||
self.setColor(vanillaColor)
|
||||
self.forced = False
|
||||
self.facing = facing
|
||||
self.hidden = False
|
||||
self.canGrey = canGrey
|
||||
self.id = id
|
||||
# list of forbidden colors
|
||||
self.forbiddenColors = forbiddenColors
|
||||
|
||||
def forceBlue(self):
|
||||
# custom start location, area, patches can force doors to blue
|
||||
self.setColor('blue')
|
||||
self.forced = True
|
||||
|
||||
def setColor(self, color):
|
||||
self.color = color
|
||||
|
||||
def getColor(self):
|
||||
if self.hidden:
|
||||
return 'grey'
|
||||
else:
|
||||
return self.color
|
||||
|
||||
def isRandom(self):
|
||||
return self.color != self.vanillaColor and not self.isBlue()
|
||||
|
||||
def isBlue(self):
|
||||
return self.color == 'blue'
|
||||
|
||||
def canRandomize(self):
|
||||
return not self.forced and self.id is None
|
||||
|
||||
def filterColorList(self, colorsList):
|
||||
if self.forbiddenColors is None:
|
||||
return colorsList
|
||||
else:
|
||||
return [color for color in colorsList if color not in self.forbiddenColors]
|
||||
|
||||
def randomize(self, allowGreyDoors):
|
||||
if self.canRandomize():
|
||||
if self.canGrey and allowGreyDoors:
|
||||
self.setColor(random.choice(self.filterColorList(colorsListGrey)))
|
||||
else:
|
||||
self.setColor(random.choice(self.filterColorList(colorsList)))
|
||||
|
||||
def traverse(self, smbm):
|
||||
if self.hidden or self.color == 'grey':
|
||||
return SMBool(False)
|
||||
elif self.color == 'red':
|
||||
return smbm.canOpenRedDoors()
|
||||
elif self.color == 'green':
|
||||
return smbm.canOpenGreenDoors()
|
||||
elif self.color == 'yellow':
|
||||
return smbm.canOpenYellowDoors()
|
||||
elif self.color == 'wave':
|
||||
return smbm.haveItem('Wave')
|
||||
elif self.color == 'spazer':
|
||||
return smbm.haveItem('Spazer')
|
||||
elif self.color == 'plasma':
|
||||
return smbm.haveItem('Plasma')
|
||||
elif self.color == 'ice':
|
||||
return smbm.haveItem('Ice')
|
||||
else:
|
||||
return SMBool(True)
|
||||
|
||||
def __repr__(self):
|
||||
return "Door({}, {})".format(self.name, self.color)
|
||||
|
||||
def isRefillSave(self):
|
||||
return self.address is None
|
||||
|
||||
def writeColor(self, rom):
|
||||
if self.isBlue() or self.isRefillSave():
|
||||
return
|
||||
|
||||
rom.writeWord(colors2plm[self.color][self.facing], self.address)
|
||||
|
||||
# also set plm args high byte to never opened, even during escape
|
||||
if self.color == 'grey':
|
||||
rom.writeByte(0x90, self.address+5)
|
||||
|
||||
def readColor(self, rom):
|
||||
if self.forced or self.isRefillSave():
|
||||
return
|
||||
|
||||
plm = rom.readWord(self.address)
|
||||
if plm in plmRed:
|
||||
self.setColor('red')
|
||||
elif plm in plmGreen:
|
||||
self.setColor('green')
|
||||
elif plm in plmYellow:
|
||||
self.setColor('yellow')
|
||||
elif plm in plmGrey:
|
||||
self.setColor('grey')
|
||||
elif plm in plmWave:
|
||||
self.setColor('wave')
|
||||
elif plm in plmSpazer:
|
||||
self.setColor('spazer')
|
||||
elif plm in plmPlasma:
|
||||
self.setColor('plasma')
|
||||
elif plm in plmIce:
|
||||
self.setColor('ice')
|
||||
else:
|
||||
raise Exception("Unknown color {} for {}".format(hex(plm), self.name))
|
||||
|
||||
# for tracker
|
||||
def canHide(self):
|
||||
return self.color != 'blue'
|
||||
|
||||
def hide(self):
|
||||
if self.canHide():
|
||||
self.hidden = True
|
||||
|
||||
def reveal(self):
|
||||
self.hidden = False
|
||||
|
||||
def switch(self):
|
||||
if self.hidden:
|
||||
self.reveal()
|
||||
else:
|
||||
self.hide()
|
||||
|
||||
# to send/receive state to tracker/plando
|
||||
def serialize(self):
|
||||
return (self.color, self.facing, self.hidden)
|
||||
|
||||
def unserialize(self, data):
|
||||
self.setColor(data[0])
|
||||
self.facing = data[1]
|
||||
self.hidden = data[2]
|
||||
|
||||
class DoorsManager():
|
||||
doorsDict = {}
|
||||
doors = {
|
||||
# crateria
|
||||
'LandingSiteRight': Door('LandingSiteRight', 0x78018, 'green', Facing.Left, canGrey=True),
|
||||
'LandingSiteTopRight': Door('LandingSiteTopRight', 0x07801e, 'yellow', Facing.Left),
|
||||
'KihunterBottom': Door('KihunterBottom', 0x78228, 'yellow', Facing.Top, canGrey=True),
|
||||
'KihunterRight': Door('KihunterRight', 0x78222, 'yellow', Facing.Left, canGrey=True),
|
||||
'FlywayRight': Door('FlywayRight', 0x78420, 'red', Facing.Left),
|
||||
'GreenPiratesShaftBottomRight': Door('GreenPiratesShaftBottomRight', 0x78470, 'red', Facing.Left, canGrey=True),
|
||||
'RedBrinstarElevatorTop': Door('RedBrinstarElevatorTop', 0x78256, 'yellow', Facing.Bottom),
|
||||
'ClimbRight': Door('ClimbRight', 0x78304, 'yellow', Facing.Left),
|
||||
# blue brinstar
|
||||
'ConstructionZoneRight': Door('ConstructionZoneRight', 0x78784, 'red', Facing.Left),
|
||||
# green brinstar
|
||||
'GreenHillZoneTopRight': Door('GreenHillZoneTopRight', 0x78670, 'yellow', Facing.Left, canGrey=True),
|
||||
'NoobBridgeRight': Door('NoobBridgeRight', 0x787a6, 'green', Facing.Left, canGrey=True),
|
||||
'MainShaftRight': Door('MainShaftRight', 0x784be, 'red', Facing.Left),
|
||||
'MainShaftBottomRight': Door('MainShaftBottomRight', 0x784c4, 'red', Facing.Left, canGrey=True),
|
||||
'EarlySupersRight': Door('EarlySupersRight', 0x78512, 'red', Facing.Left),
|
||||
'EtecoonEnergyTankLeft': Door('EtecoonEnergyTankLeft', 0x787c8, 'green', Facing.Right),
|
||||
# pink brinstar
|
||||
'BigPinkTopRight': Door('BigPinkTopRight', 0x78626, 'red', Facing.Left),
|
||||
'BigPinkRight': Door('BigPinkRight', 0x7861a, 'yellow', Facing.Left),
|
||||
'BigPinkBottomRight': Door('BigPinkBottomRight', 0x78620, 'green', Facing.Left, canGrey=True),
|
||||
'BigPinkBottomLeft': Door('BigPinkBottomLeft', 0x7862c, 'red', Facing.Right),
|
||||
# red brinstar
|
||||
'RedTowerLeft': Door('RedTowerLeft', 0x78866, 'yellow', Facing.Right),
|
||||
'RedBrinstarFirefleaLeft': Door('RedBrinstarFirefleaLeft', 0x7886e, 'red', Facing.Right),
|
||||
'RedTowerElevatorTopLeft': Door('RedTowerElevatorTopLeft', 0x788aa, 'green', Facing.Right),
|
||||
'RedTowerElevatorLeft': Door('RedTowerElevatorLeft', 0x788b0, 'yellow', Facing.Right),
|
||||
'RedTowerElevatorBottomLeft': Door('RedTowerElevatorBottomLeft', 0x788b6, 'green', Facing.Right),
|
||||
'BelowSpazerTopRight': Door('BelowSpazerTopRight', 0x78966, 'green', Facing.Left),
|
||||
# Wrecked ship
|
||||
'WestOceanRight': Door('WestOceanRight', 0x781e2, 'green', Facing.Left, canGrey=True),
|
||||
'LeCoudeBottom': Door('LeCoudeBottom', 0x7823e, 'yellow', Facing.Top, canGrey=True),
|
||||
'WreckedShipMainShaftBottom': Door('WreckedShipMainShaftBottom', 0x7c277, 'green', Facing.Top),
|
||||
'ElectricDeathRoomTopLeft': Door('ElectricDeathRoomTopLeft', 0x7c32f, 'red', Facing.Right),
|
||||
# Upper Norfair
|
||||
'BusinessCenterTopLeft': Door('BusinessCenterTopLeft', 0x78b00, 'green', Facing.Right),
|
||||
'BusinessCenterBottomLeft': Door('BusinessCenterBottomLeft', 0x78b0c, 'red', Facing.Right),
|
||||
'CathedralEntranceRight': Door('CathedralEntranceRight', 0x78af2, 'red', Facing.Left, canGrey=True),
|
||||
'CathedralRight': Door('CathedralRight', 0x78aea, 'green', Facing.Left),
|
||||
'BubbleMountainTopRight': Door('BubbleMountainTopRight', 0x78c60, 'green', Facing.Left),
|
||||
'BubbleMountainTopLeft': Door('BubbleMountainTopLeft', 0x78c5a, 'green', Facing.Right),
|
||||
'SpeedBoosterHallRight': Door('SpeedBoosterHallRight', 0x78c7a, 'red', Facing.Left),
|
||||
'SingleChamberRight': Door('SingleChamberRight', 0x78ca8, 'red', Facing.Left),
|
||||
'DoubleChamberRight': Door('DoubleChamberRight', 0x78cc2, 'red', Facing.Left),
|
||||
'KronicBoostBottomLeft': Door('KronicBoostBottomLeft', 0x78d4e, 'yellow', Facing.Right, canGrey=True),
|
||||
'CrocomireSpeedwayBottom': Door('CrocomireSpeedwayBottom', 0x78b96, 'green', Facing.Top, canGrey=True),
|
||||
# Crocomire
|
||||
'PostCrocomireUpperLeft': Door('PostCrocomireUpperLeft', 0x78bf4, 'red', Facing.Right),
|
||||
'PostCrocomireShaftRight': Door('PostCrocomireShaftRight', 0x78c0c, 'red', Facing.Left),
|
||||
# Lower Norfair
|
||||
'RedKihunterShaftBottom': Door('RedKihunterShaftBottom', 0x7902e, 'yellow', Facing.Top),
|
||||
'WastelandLeft': Door('WastelandLeft', 0x790ba, 'green', Facing.Right, forbiddenColors=['yellow']),
|
||||
# Maridia
|
||||
'MainStreetBottomRight': Door('MainStreetBottomRight', 0x7c431, 'red', Facing.Left),
|
||||
'FishTankRight': Door('FishTankRight', 0x7c475, 'red', Facing.Left),
|
||||
'CrabShaftRight': Door('CrabShaftRight', 0x7c4fb, 'green', Facing.Left),
|
||||
'ColosseumBottomRight': Door('ColosseumBottomRight', 0x7c6fb, 'green', Facing.Left),
|
||||
'PlasmaSparkBottom': Door('PlasmaSparkBottom', 0x7c577, 'green', Facing.Top),
|
||||
'OasisTop': Door('OasisTop', 0x7c5d3, 'green', Facing.Bottom),
|
||||
# refill/save
|
||||
'GreenBrinstarSaveStation': Door('GreenBrinstarSaveStation', None, 'red', Facing.Right, id=0x1f),
|
||||
'MaridiaBottomSaveStation': Door('MaridiaBottomSaveStation', None, 'red', Facing.Left, id=0x8c),
|
||||
'MaridiaAqueductSaveStation': Door('MaridiaAqueductSaveStation', None, 'red', Facing.Right, id=0x96),
|
||||
'ForgottenHighwaySaveStation': Door('ForgottenHighwaySaveStation', None, 'red', Facing.Left, id=0x92),
|
||||
'DraygonSaveRefillStation': Door('DraygonSaveRefillStation', None, 'red', Facing.Left, id=0x98),
|
||||
'KraidRefillStation': Door('KraidRefillStation', None, 'green', Facing.Left, id=0x44),
|
||||
'RedBrinstarEnergyRefill': Door('RedBrinstarEnergyRefill', None, 'green', Facing.Right, id=0x38),
|
||||
'GreenBrinstarMissileRefill': Door('GreenBrinstarMissileRefill', None, 'red', Facing.Right, id=0x23)
|
||||
}
|
||||
|
||||
# call from logic
|
||||
def traverse(self, smbm, doorName):
|
||||
return DoorsManager.doorsDict[smbm.player][doorName].traverse(smbm)
|
||||
|
||||
@staticmethod
|
||||
def setDoorsColor(player=0):
|
||||
if player not in DoorsManager.doorsDict.keys():
|
||||
DoorsManager.doorsDict[player] = copy.deepcopy(DoorsManager.doors)
|
||||
currentDoors = DoorsManager.doorsDict[player]
|
||||
|
||||
# depending on loaded patches, force some doors to blue, excluding them from randomization
|
||||
if RomPatches.has(player, RomPatches.BlueBrinstarBlueDoor):
|
||||
currentDoors['ConstructionZoneRight'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.BrinReserveBlueDoors):
|
||||
currentDoors['MainShaftRight'].forceBlue()
|
||||
currentDoors['EarlySupersRight'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.EtecoonSupersBlueDoor):
|
||||
currentDoors['EtecoonEnergyTankLeft'].forceBlue()
|
||||
#if RomPatches.has(player, RomPatches.SpongeBathBlueDoor):
|
||||
# currentDoors[''].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.HiJumpAreaBlueDoor):
|
||||
currentDoors['BusinessCenterBottomLeft'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.SpeedAreaBlueDoors):
|
||||
currentDoors['BubbleMountainTopRight'].forceBlue()
|
||||
currentDoors['SpeedBoosterHallRight'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.MamaTurtleBlueDoor):
|
||||
currentDoors['FishTankRight'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.HellwayBlueDoor):
|
||||
currentDoors['RedTowerElevatorLeft'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.RedTowerBlueDoors):
|
||||
currentDoors['RedBrinstarElevatorTop'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.AreaRandoBlueDoors):
|
||||
currentDoors['GreenHillZoneTopRight'].forceBlue()
|
||||
currentDoors['NoobBridgeRight'].forceBlue()
|
||||
currentDoors['LeCoudeBottom'].forceBlue()
|
||||
currentDoors['KronicBoostBottomLeft'].forceBlue()
|
||||
else:
|
||||
# no area rando, prevent some doors to be in the grey doors pool
|
||||
currentDoors['GreenPiratesShaftBottomRight'].canGrey = False
|
||||
currentDoors['CrocomireSpeedwayBottom'].canGrey = False
|
||||
currentDoors['KronicBoostBottomLeft'].canGrey = False
|
||||
if RomPatches.has(player, RomPatches.AreaRandoMoreBlueDoors):
|
||||
currentDoors['KihunterBottom'].forceBlue()
|
||||
currentDoors['GreenPiratesShaftBottomRight'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.CrocBlueDoors):
|
||||
currentDoors['CrocomireSpeedwayBottom'].forceBlue()
|
||||
if RomPatches.has(player, RomPatches.CrabShaftBlueDoor):
|
||||
currentDoors['CrabShaftRight'].forceBlue()
|
||||
|
||||
@staticmethod
|
||||
def randomize(allowGreyDoors, player):
|
||||
for door in DoorsManager.doorsDict[player].values():
|
||||
door.randomize(allowGreyDoors)
|
||||
# set both ends of toilet to the same color to avoid soft locking in area rando
|
||||
toiletTop = DoorsManager.doorsDict[player]['PlasmaSparkBottom']
|
||||
toiletBottom = DoorsManager.doorsDict[player]['OasisTop']
|
||||
if toiletTop.color != toiletBottom.color:
|
||||
toiletBottom.setColor(toiletTop.color)
|
||||
DoorsManager.debugDoorsColor()
|
||||
|
||||
# call from rom loader
|
||||
@staticmethod
|
||||
def loadDoorsColor(rom):
|
||||
# force to blue some doors depending on patches
|
||||
DoorsManager.setDoorsColor()
|
||||
# for each door store it's color
|
||||
for door in DoorsManager.doors.values():
|
||||
door.readColor(rom)
|
||||
DoorsManager.debugDoorsColor()
|
||||
|
||||
# tell that we have randomized doors
|
||||
isRandom = DoorsManager.isRandom()
|
||||
if isRandom:
|
||||
DoorsManager.setRefillSaveToBlue()
|
||||
return isRandom
|
||||
|
||||
@staticmethod
|
||||
def isRandom(player):
|
||||
return any(door.isRandom() for door in DoorsManager.doorsDict[player].values())
|
||||
|
||||
@staticmethod
|
||||
def setRefillSaveToBlue(player):
|
||||
for door in DoorsManager.doorsDict[player].values():
|
||||
if door.id is not None:
|
||||
door.forceBlue()
|
||||
|
||||
@staticmethod
|
||||
def debugDoorsColor():
|
||||
if LOG.getEffectiveLevel() == logging.DEBUG:
|
||||
for door in DoorsManager.doors.values():
|
||||
LOG.debug("{:>32}: {:>6}".format(door.name, door.color))
|
||||
|
||||
# call from rom patcher
|
||||
@staticmethod
|
||||
def writeDoorsColor(rom, doors, player):
|
||||
for door in DoorsManager.doorsDict[player].values():
|
||||
door.writeColor(rom)
|
||||
# also set save/refill doors to blue
|
||||
if door.id is not None:
|
||||
doors.append(door.id)
|
||||
|
||||
# call from web
|
||||
@staticmethod
|
||||
def getAddressesToRead():
|
||||
return [door.address for door in DoorsManager.doors.values() if door.address is not None] + [door.address+1 for door in DoorsManager.doors.values() if door.address is not None]
|
||||
|
||||
# for isolver state
|
||||
@staticmethod
|
||||
def serialize():
|
||||
return {door.name: door.serialize() for door in DoorsManager.doors.values()}
|
||||
|
||||
@staticmethod
|
||||
def unserialize(state):
|
||||
for name, data in state.items():
|
||||
DoorsManager.doors[name].unserialize(data)
|
||||
|
||||
@staticmethod
|
||||
def allDoorsRevealed():
|
||||
for door in DoorsManager.doors.values():
|
||||
if door.hidden:
|
||||
return False
|
||||
return True
|
||||
|
||||
# when using the tracker, first set all colored doors to grey until the user clicks on it
|
||||
@staticmethod
|
||||
def initTracker():
|
||||
for door in DoorsManager.doors.values():
|
||||
door.hide()
|
||||
|
||||
# when the user clicks on a door in the tracker
|
||||
@staticmethod
|
||||
def switchVisibility(name):
|
||||
DoorsManager.doors[name].switch()
|
||||
|
||||
# when the user clicks on a door in the race tracker or the plando
|
||||
@staticmethod
|
||||
def setColor(name, color):
|
||||
# in race mode the doors are hidden
|
||||
DoorsManager.doors[name].reveal()
|
||||
DoorsManager.doors[name].setColor(color)
|
||||
|
||||
# in autotracker we need the current doors state
|
||||
@staticmethod
|
||||
def getDoorsState():
|
||||
hiddenDoors = set([door.name for door in DoorsManager.doors.values() if door.hidden])
|
||||
revealedDoor = set([door.name for door in DoorsManager.doors.values() if (not door.hidden) and door.canHide()])
|
||||
return (hiddenDoors, revealedDoor)
|
||||
16
worlds/sm/variaRandomizer/utils/log.py
Normal file
16
worlds/sm/variaRandomizer/utils/log.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import logging, sys
|
||||
|
||||
# store the debug flag at module level
|
||||
debug = False
|
||||
|
||||
def init(pdebug):
|
||||
global debug
|
||||
debug = pdebug
|
||||
|
||||
if debug == True:
|
||||
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def get(name):
|
||||
return logging.getLogger(name)
|
||||
1163
worlds/sm/variaRandomizer/utils/parameters.py
Normal file
1163
worlds/sm/variaRandomizer/utils/parameters.py
Normal file
File diff suppressed because it is too large
Load Diff
450
worlds/sm/variaRandomizer/utils/utils.py
Normal file
450
worlds/sm/variaRandomizer/utils/utils.py
Normal file
@@ -0,0 +1,450 @@
|
||||
import os, json, sys, re, random
|
||||
|
||||
from utils.parameters import Knows, Settings, Controller, isKnows, isSettings, isButton
|
||||
from utils.parameters import easy, medium, hard, harder, hardcore, mania, text2diff
|
||||
from logic.smbool import SMBool
|
||||
|
||||
from Utils import is_frozen
|
||||
|
||||
def isStdPreset(preset):
|
||||
return preset in ['newbie', 'casual', 'regular', 'veteran', 'expert', 'master', 'samus', 'solution', 'Season_Races', 'SMRAT2021']
|
||||
|
||||
def getPresetDir(preset):
|
||||
if isStdPreset(preset):
|
||||
return 'lib/worlds/sm/variaRandomizer/standard_presets' if is_frozen() else 'worlds/sm/variaRandomizer/standard_presets'
|
||||
else:
|
||||
return 'lib/worlds/sm/variaRandomizer/community_presets' if is_frozen() else 'worlds/sm/variaRandomizer/community_presets'
|
||||
|
||||
def removeChars(string, toRemove):
|
||||
return re.sub('[{}]+'.format(toRemove), '', string)
|
||||
|
||||
def range_union(ranges):
|
||||
ret = []
|
||||
for rg in sorted([[r.start, r.stop] for r in ranges]):
|
||||
begin, end = rg[0], rg[-1]
|
||||
if ret and ret[-1][1] > begin:
|
||||
ret[-1][1] = max(ret[-1][1], end)
|
||||
else:
|
||||
ret.append([begin, end])
|
||||
return [range(r[0], r[1]) for r in ret]
|
||||
|
||||
# https://github.com/robotools/fontParts/commit/7cb561033929cfb4a723d274672e7257f5e68237
|
||||
def normalizeRounding(n):
|
||||
# Normalizes rounding as Python 2 and Python 3 handing the rounding of halves (0.5, 1.5, etc) differently.
|
||||
# This normalizes rounding to be the same in both environments.
|
||||
if round(0.5) != 1 and n % 1 == .5 and not int(n) % 2:
|
||||
return int((round(n) + (abs(n) / n) * 1))
|
||||
else:
|
||||
return int(round(n))
|
||||
|
||||
# gauss random in [0, r] range
|
||||
# the higher the slope, the less probable extreme values are.
|
||||
def randGaussBounds(r, slope=5):
|
||||
r = float(r)
|
||||
n = normalizeRounding(random.gauss(r/2, r/slope))
|
||||
if n < 0:
|
||||
n = 0
|
||||
if n > r:
|
||||
n = int(r)
|
||||
return n
|
||||
|
||||
# from a relative weight dictionary, gives a normalized range dictionary
|
||||
# example :
|
||||
# { 'a' : 10, 'b' : 17, 'c' : 3 } => {'c': 0.1, 'a':0.4333333, 'b':1 }
|
||||
def getRangeDict(weightDict):
|
||||
total = float(sum(weightDict.values()))
|
||||
rangeDict = {}
|
||||
current = 0.0
|
||||
for k in sorted(weightDict, key=weightDict.get):
|
||||
w = float(weightDict[k]) / total
|
||||
current += w
|
||||
rangeDict[k] = current
|
||||
|
||||
return rangeDict
|
||||
|
||||
def chooseFromRange(rangeDict):
|
||||
r = random.random()
|
||||
val = None
|
||||
for v in sorted(rangeDict, key=rangeDict.get):
|
||||
val = v
|
||||
if r < rangeDict[v]:
|
||||
return v
|
||||
return val
|
||||
|
||||
class PresetLoader(object):
|
||||
@staticmethod
|
||||
def factory(params):
|
||||
# can be a json, a python file or a dict with the parameters
|
||||
if type(params) == str:
|
||||
ext = os.path.splitext(params)
|
||||
if ext[1].lower() == '.json':
|
||||
return PresetLoaderJson(params)
|
||||
else:
|
||||
raise Exception("PresetLoader: wrong parameters file type: {}".format(ext[1]))
|
||||
elif type(params) is dict:
|
||||
return PresetLoaderDict(params)
|
||||
else:
|
||||
raise Exception("wrong parameters input, is neither a string nor a json file name: {}::{}".format(params, type(params)))
|
||||
|
||||
def __init__(self):
|
||||
if 'Knows' not in self.params:
|
||||
if 'knows' in self.params:
|
||||
self.params['Knows'] = self.params['knows']
|
||||
else:
|
||||
self.params['Knows'] = {}
|
||||
if 'Settings' not in self.params:
|
||||
if 'settings' in self.params:
|
||||
self.params['Settings'] = self.params['settings']
|
||||
else:
|
||||
self.params['Settings'] = {}
|
||||
if 'Controller' not in self.params:
|
||||
if 'controller' in self.params:
|
||||
self.params['Controller'] = self.params['controller']
|
||||
else:
|
||||
self.params['Controller'] = {}
|
||||
self.params['score'] = self.computeScore()
|
||||
|
||||
def load(self, player):
|
||||
# update the parameters in the parameters classes: Knows, Settings
|
||||
Knows.knowsDict[player] = Knows()
|
||||
Settings.SettingsDict[player] = Settings()
|
||||
Controller.ControllerDict[player] = Controller()
|
||||
|
||||
# Knows
|
||||
for param in self.params['Knows']:
|
||||
if isKnows(param) and hasattr(Knows, param):
|
||||
setattr(Knows.knowsDict[player], param, SMBool( self.params['Knows'][param][0],
|
||||
self.params['Knows'][param][1],
|
||||
['{}'.format(param)]))
|
||||
# Settings
|
||||
## hard rooms
|
||||
for hardRoom in ['X-Ray', 'Gauntlet']:
|
||||
if hardRoom in self.params['Settings']:
|
||||
Settings.SettingsDict[player].hardRooms[hardRoom] = Settings.hardRoomsPresets[hardRoom][self.params['Settings'][hardRoom]]
|
||||
|
||||
## bosses
|
||||
for boss in ['Kraid', 'Phantoon', 'Draygon', 'Ridley', 'MotherBrain']:
|
||||
if boss in self.params['Settings']:
|
||||
Settings.SettingsDict[player].bossesDifficulty[boss] = Settings.bossesDifficultyPresets[boss][self.params['Settings'][boss]]
|
||||
|
||||
## hellruns
|
||||
for hellRun in ['Ice', 'MainUpperNorfair', 'LowerNorfair']:
|
||||
if hellRun in self.params['Settings']:
|
||||
Settings.SettingsDict[player].hellRuns[hellRun] = Settings.hellRunPresets[hellRun][self.params['Settings'][hellRun]]
|
||||
|
||||
# Controller
|
||||
for button in self.params['Controller']:
|
||||
if isButton(button):
|
||||
setattr(Controller.ControllerDict[player], button, self.params['Controller'][button])
|
||||
|
||||
def dump(self, fileName):
|
||||
with open(fileName, 'w') as jsonFile:
|
||||
json.dump(self.params, jsonFile)
|
||||
|
||||
def printToScreen(self):
|
||||
print("self.params: {}".format(self.params))
|
||||
|
||||
print("loaded knows: ")
|
||||
for knows in Knows.__dict__:
|
||||
if isKnows(knows):
|
||||
print("{}: {}".format(knows, Knows.__dict__[knows]))
|
||||
print("loaded settings:")
|
||||
for setting in Settings.__dict__:
|
||||
if isSettings(setting):
|
||||
print("{}: {}".format(setting, Settings.__dict__[setting]))
|
||||
print("loaded controller:")
|
||||
for button in Controller.__dict__:
|
||||
if isButton(button):
|
||||
print("{}: {}".format(button, Controller.__dict__[button]))
|
||||
print("loaded score: {}".format(self.params['score']))
|
||||
|
||||
def computeScore(self):
|
||||
# the more techniques you know and the smaller the difficulty of the techniques, the higher the score
|
||||
diff2score = {
|
||||
easy: 6,
|
||||
medium: 5,
|
||||
hard: 4,
|
||||
harder: 3,
|
||||
hardcore: 2,
|
||||
mania: 1
|
||||
}
|
||||
|
||||
boss2score = {
|
||||
"He's annoying": 1,
|
||||
'A lot of trouble': 1,
|
||||
"I'm scared!": 1,
|
||||
"It can get ugly": 1,
|
||||
'Default': 2,
|
||||
'Quick Kill': 3,
|
||||
'Used to it': 3,
|
||||
'Is this really the last boss?': 3,
|
||||
'No problemo': 4,
|
||||
'Piece of cake': 4,
|
||||
'Nice cutscene bro': 4
|
||||
}
|
||||
|
||||
hellrun2score = {
|
||||
'No thanks': 0,
|
||||
'Solution': 0,
|
||||
'Gimme energy': 2,
|
||||
'Default': 4,
|
||||
'Bring the heat': 6,
|
||||
'I run RBO': 8
|
||||
}
|
||||
|
||||
hellrunLN2score = {
|
||||
'Default': 0,
|
||||
'Solution': 0,
|
||||
'Bring the heat': 6,
|
||||
'I run RBO': 12
|
||||
}
|
||||
|
||||
xray2score = {
|
||||
'Aarghh': 0,
|
||||
'Solution': 0,
|
||||
"I don't like spikes": 1,
|
||||
'Default': 2,
|
||||
"I don't mind spikes": 3,
|
||||
'D-Boost master': 4
|
||||
}
|
||||
|
||||
gauntlet2score = {
|
||||
'Aarghh': 0,
|
||||
"I don't like acid": 1,
|
||||
'Default': 2
|
||||
}
|
||||
|
||||
score = 0
|
||||
|
||||
# knows
|
||||
for know in Knows.__dict__:
|
||||
if isKnows(know):
|
||||
if know in self.params['Knows']:
|
||||
if self.params['Knows'][know][0] == True:
|
||||
score += diff2score[self.params['Knows'][know][1]]
|
||||
else:
|
||||
# if old preset with not all the knows, use default values for the know
|
||||
if Knows.__dict__[know].bool == True:
|
||||
score += diff2score[Knows.__dict__[know].difficulty]
|
||||
|
||||
# hard rooms
|
||||
hardRoom = 'X-Ray'
|
||||
if hardRoom in self.params['Settings']:
|
||||
score += xray2score[self.params['Settings'][hardRoom]]
|
||||
|
||||
hardRoom = 'Gauntlet'
|
||||
if hardRoom in self.params['Settings']:
|
||||
score += gauntlet2score[self.params['Settings'][hardRoom]]
|
||||
|
||||
# bosses
|
||||
for boss in ['Kraid', 'Phantoon', 'Draygon', 'Ridley', 'MotherBrain']:
|
||||
if boss in self.params['Settings']:
|
||||
score += boss2score[self.params['Settings'][boss]]
|
||||
|
||||
# hellruns
|
||||
for hellRun in ['Ice', 'MainUpperNorfair']:
|
||||
if hellRun in self.params['Settings']:
|
||||
score += hellrun2score[self.params['Settings'][hellRun]]
|
||||
|
||||
hellRun = 'LowerNorfair'
|
||||
if hellRun in self.params['Settings']:
|
||||
score += hellrunLN2score[self.params['Settings'][hellRun]]
|
||||
|
||||
return score
|
||||
|
||||
class PresetLoaderJson(PresetLoader):
|
||||
# when called from the test suite
|
||||
def __init__(self, jsonFileName):
|
||||
with open(jsonFileName) as jsonFile:
|
||||
self.params = json.load(jsonFile)
|
||||
super(PresetLoaderJson, self).__init__()
|
||||
|
||||
class PresetLoaderDict(PresetLoader):
|
||||
# when called from the website
|
||||
def __init__(self, params):
|
||||
self.params = params
|
||||
super(PresetLoaderDict, self).__init__()
|
||||
|
||||
def getDefaultMultiValues():
|
||||
from graph.graph_utils import GraphUtils
|
||||
defaultMultiValues = {
|
||||
'startLocation': GraphUtils.getStartAccessPointNames(),
|
||||
'majorsSplit': ['Full', 'FullWithHUD', 'Major', 'Chozo', 'Scavenger'],
|
||||
'progressionSpeed': ['slowest', 'slow', 'medium', 'fast', 'fastest', 'basic', 'VARIAble', 'speedrun'],
|
||||
'progressionDifficulty': ['easier', 'normal', 'harder'],
|
||||
'morphPlacement': ['early', 'normal'], #['early', 'late', 'normal'],
|
||||
'energyQty': ['ultra sparse', 'sparse', 'medium', 'vanilla'],
|
||||
'gravityBehaviour': ['Vanilla', 'Balanced', 'Progressive']
|
||||
}
|
||||
return defaultMultiValues
|
||||
|
||||
def getPresetValues():
|
||||
return [
|
||||
"newbie",
|
||||
"casual",
|
||||
"regular",
|
||||
"veteran",
|
||||
"expert",
|
||||
"master",
|
||||
"samus",
|
||||
"Season_Races",
|
||||
"SMRAT2021",
|
||||
"solution",
|
||||
"custom",
|
||||
"varia_custom"
|
||||
]
|
||||
|
||||
# from web to cli
|
||||
def convertParam(randoParams, param, inverse=False):
|
||||
value = randoParams.get(param, "off" if inverse == False else "on")
|
||||
if value == "on":
|
||||
return True if inverse == False else False
|
||||
elif value == "off":
|
||||
return False if inverse == False else True
|
||||
elif value == "random":
|
||||
return "random"
|
||||
raise Exception("invalid value for parameter {}".format(param))
|
||||
|
||||
def loadRandoPreset(world, player, args):
|
||||
defaultMultiValues = getDefaultMultiValues()
|
||||
diffs = ["easy", "medium", "hard", "harder", "hardcore", "mania", "infinity"]
|
||||
presetValues = getPresetValues()
|
||||
|
||||
args.animals = world.animals[player].value
|
||||
args.noVariaTweaks = not world.varia_tweaks[player].value
|
||||
args.maxDifficulty = diffs[world.max_difficulty[player].value]
|
||||
args.suitsRestriction = world.suits_restriction[player].value
|
||||
#args.hideItems = world.hide_items[player].value
|
||||
args.strictMinors = world.strict_minors[player].value
|
||||
args.noLayout = not world.layout_patches[player].value
|
||||
args.gravityBehaviour = defaultMultiValues["gravityBehaviour"][world.gravity_behaviour[player].value]
|
||||
args.nerfedCharge = world.nerfed_charge[player].value
|
||||
args.area = world.area_randomization[player].value != 0
|
||||
if args.area:
|
||||
args.areaLayoutBase = not world.area_layout[player].value
|
||||
args.lightArea = world.area_randomization[player].value == 1
|
||||
#args.escapeRando
|
||||
#args.noRemoveEscapeEnemies
|
||||
args.doorsColorsRando = world.doors_colors_rando[player].value
|
||||
args.allowGreyDoors = world.allow_grey_doors[player].value
|
||||
args.bosses = world.boss_randomization[player].value
|
||||
if world.fun_combat[player].value:
|
||||
args.superFun.append("Combat")
|
||||
if world.fun_movement[player].value:
|
||||
args.superFun.append("Movement")
|
||||
if world.fun_suits[player].value:
|
||||
args.superFun.append("Suits")
|
||||
|
||||
ipsPatches = {"spin_jump_restart":"spinjumprestart", "rando_speed":"rando_speed", "elevators_doors_speed":"elevators_doors_speed", "refill_before_save":"refill_before_save"}
|
||||
for settingName, patchName in ipsPatches.items():
|
||||
if hasattr(world, settingName) and getattr(world, settingName)[player].value:
|
||||
args.patches.append(patchName + '.ips')
|
||||
|
||||
patches = {"no_music":"No_Music", "infinite_space_jump":"Infinite_Space_Jump"}
|
||||
for settingName, patchName in patches.items():
|
||||
if hasattr(world, settingName) and getattr(world, settingName)[player].value:
|
||||
args.patches.append(patchName)
|
||||
|
||||
args.hud = world.hud[player].value
|
||||
args.morphPlacement = defaultMultiValues["morphPlacement"][world.morph_placement[player].value]
|
||||
#args.majorsSplit
|
||||
#args.scavNumLocs
|
||||
#args.scavRandomized
|
||||
#args.scavEscape
|
||||
args.startLocation = defaultMultiValues["startLocation"][world.start_location[player].value]
|
||||
#args.progressionDifficulty
|
||||
#args.progressionSpeed
|
||||
args.missileQty = world.missile_qty[player].value / float(10)
|
||||
args.superQty = world.super_qty[player].value / float(10)
|
||||
args.powerBombQty = world.power_bomb_qty[player].value / float(10)
|
||||
args.minorQty = world.minor_qty[player].value
|
||||
args.energyQty = defaultMultiValues["energyQty"][world.energy_qty[player].value]
|
||||
#args.minimizerN
|
||||
#args.minimizerTourian
|
||||
|
||||
return presetValues[world.preset[player].value]
|
||||
|
||||
def getRandomizerDefaultParameters():
|
||||
defaultParams = {}
|
||||
defaultMultiValues = getDefaultMultiValues()
|
||||
|
||||
defaultParams['complexity'] = "simple"
|
||||
defaultParams['preset'] = 'regular'
|
||||
defaultParams['randoPreset'] = ""
|
||||
defaultParams['raceMode'] = "off"
|
||||
defaultParams['majorsSplit'] = "Full"
|
||||
defaultParams['majorsSplitMultiSelect'] = defaultMultiValues['majorsSplit']
|
||||
defaultParams['scavNumLocs'] = "10"
|
||||
defaultParams['scavRandomized'] = "off"
|
||||
defaultParams['scavEscape'] = "off"
|
||||
defaultParams['startLocation'] = "Landing Site"
|
||||
defaultParams['startLocationMultiSelect'] = defaultMultiValues['startLocation']
|
||||
defaultParams['maxDifficulty'] = 'hardcore'
|
||||
defaultParams['progressionSpeed'] = "medium"
|
||||
defaultParams['progressionSpeedMultiSelect'] = defaultMultiValues['progressionSpeed']
|
||||
defaultParams['progressionDifficulty'] = 'normal'
|
||||
defaultParams['progressionDifficultyMultiSelect'] = defaultMultiValues['progressionDifficulty']
|
||||
defaultParams['morphPlacement'] = "early"
|
||||
defaultParams['morphPlacementMultiSelect'] = defaultMultiValues['morphPlacement']
|
||||
defaultParams['suitsRestriction'] = "on"
|
||||
defaultParams['hideItems'] = "off"
|
||||
defaultParams['strictMinors'] = "off"
|
||||
defaultParams['missileQty'] = "3"
|
||||
defaultParams['superQty'] = "2"
|
||||
defaultParams['powerBombQty'] = "1"
|
||||
defaultParams['minorQty'] = "100"
|
||||
defaultParams['energyQty'] = "vanilla"
|
||||
defaultParams['energyQtyMultiSelect'] = defaultMultiValues['energyQty']
|
||||
defaultParams['areaRandomization'] = "off"
|
||||
defaultParams['areaLayout'] = "off"
|
||||
defaultParams['lightAreaRandomization'] = "off"
|
||||
defaultParams['doorsColorsRando'] = "off"
|
||||
defaultParams['allowGreyDoors'] = "off"
|
||||
defaultParams['escapeRando'] = "off"
|
||||
defaultParams['removeEscapeEnemies'] = "off"
|
||||
defaultParams['bossRandomization'] = "off"
|
||||
defaultParams['minimizer'] = "off"
|
||||
defaultParams['minimizerQty'] = "45"
|
||||
defaultParams['minimizerTourian'] = "off"
|
||||
defaultParams['funCombat'] = "off"
|
||||
defaultParams['funMovement'] = "off"
|
||||
defaultParams['funSuits'] = "off"
|
||||
defaultParams['layoutPatches'] = "on"
|
||||
defaultParams['variaTweaks'] = "on"
|
||||
defaultParams['gravityBehaviour'] = "Balanced"
|
||||
defaultParams['gravityBehaviourMultiSelect'] = defaultMultiValues['gravityBehaviour']
|
||||
defaultParams['nerfedCharge'] = "off"
|
||||
defaultParams['itemsounds'] = "on"
|
||||
defaultParams['elevators_doors_speed'] = "on"
|
||||
defaultParams['spinjumprestart'] = "off"
|
||||
defaultParams['rando_speed'] = "off"
|
||||
defaultParams['Infinite_Space_Jump'] = "off"
|
||||
defaultParams['refill_before_save'] = "off"
|
||||
defaultParams['hud'] = "off"
|
||||
defaultParams['animals'] = "off"
|
||||
defaultParams['No_Music'] = "off"
|
||||
defaultParams['random_music'] = "off"
|
||||
|
||||
return defaultParams
|
||||
|
||||
def fixEnergy(items):
|
||||
# display number of energy used
|
||||
energies = [i for i in items if i.find('ETank') != -1]
|
||||
if len(energies) > 0:
|
||||
(maxETank, maxReserve, maxEnergy) = (0, 0, 0)
|
||||
for energy in energies:
|
||||
nETank = int(energy[0:energy.find('-ETank')])
|
||||
if energy.find('-Reserve') != -1:
|
||||
nReserve = int(energy[energy.find(' - ')+len(' - '):energy.find('-Reserve')])
|
||||
else:
|
||||
nReserve = 0
|
||||
nEnergy = nETank + nReserve
|
||||
if nEnergy > maxEnergy:
|
||||
maxEnergy = nEnergy
|
||||
maxETank = nETank
|
||||
maxReserve = nReserve
|
||||
items.remove(energy)
|
||||
items.append('{}-ETank'.format(maxETank))
|
||||
if maxReserve > 0:
|
||||
items.append('{}-Reserve'.format(maxReserve))
|
||||
return items
|
||||
27
worlds/sm/variaRandomizer/utils/vcr.py
Normal file
27
worlds/sm/variaRandomizer/utils/vcr.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import json, os.path
|
||||
|
||||
# record solver/rando to play in the VCR tracker
|
||||
class VCR(object):
|
||||
def __init__(self, name, type):
|
||||
self.baseName = os.path.basename(os.path.splitext(name)[0])
|
||||
self.outFileName = "{}.{}.vcr".format(self.baseName, type)
|
||||
self.empty()
|
||||
|
||||
def empty(self):
|
||||
self.tape = []
|
||||
|
||||
def addLocation(self, locName, itemName):
|
||||
self.tape.append({'type': 'location', 'loc': locName, 'item': itemName})
|
||||
|
||||
def addRollback(self, count):
|
||||
self.tape.append({'type': 'rollback', 'count': count})
|
||||
|
||||
def dump(self):
|
||||
with open(self.outFileName, 'w') as jsonFile:
|
||||
json.dump(self.tape, jsonFile)
|
||||
|
||||
# in scavenger we have the rando solver then the scav solver, generate vcr for both
|
||||
def reinit(self, type):
|
||||
self.dump()
|
||||
self.outFileName = "{}.{}.vcr".format(self.baseName, type)
|
||||
self.empty()
|
||||
3
worlds/sm/variaRandomizer/utils/version.py
Normal file
3
worlds/sm/variaRandomizer/utils/version.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# version displayed on the title screen, must be a max 32 chars [a-z0-9.-] string
|
||||
# either 'beta' or 'r.yyyy.mm.dd'
|
||||
displayedVersion = 'beta'
|
||||
Reference in New Issue
Block a user