2021-11-12 08:00:11 -05:00
#!/usr/bin/python3
from Utils import output_path
import argparse , os . path , json , sys , shutil , random , copy , requests
from rando . RandoSettings import RandoSettings , GraphSettings
from rando . RandoExec import RandoExec
from graph . graph_utils import vanillaTransitions , vanillaBossesTransitions , GraphUtils , getAccessPoint
from utils . parameters import Knows , Controller , easy , medium , hard , harder , hardcore , mania , infinity , text2diff , diff2text , appDir
from rom . rom_patches import RomPatches
from rom . rompatcher import RomPatcher
from utils . utils import PresetLoader , loadRandoPreset , getDefaultMultiValues , getPresetDir
from utils . version import displayedVersion
from logic . smbool import SMBool
from utils . doorsmanager import DoorsManager
from logic . logic import Logic
import utils . log
# we need to know the logic before doing anything else
def getLogic ( ) :
# check if --logic is there
logic = ' vanilla '
for i , param in enumerate ( sys . argv ) :
if param == ' --logic ' and i + 1 < len ( sys . argv ) :
logic = sys . argv [ i + 1 ]
return logic
Logic . factory ( getLogic ( ) )
defaultMultiValues = getDefaultMultiValues ( )
speeds = defaultMultiValues [ ' progressionSpeed ' ]
energyQties = defaultMultiValues [ ' energyQty ' ]
progDiffs = defaultMultiValues [ ' progressionDifficulty ' ]
morphPlacements = defaultMultiValues [ ' morphPlacement ' ]
majorsSplits = defaultMultiValues [ ' majorsSplit ' ]
gravityBehaviours = defaultMultiValues [ ' gravityBehaviour ' ]
def randomMulti ( args , param , defaultMultiValues ) :
value = args [ param ]
isRandom = False
if value == " random " :
isRandom = True
if args [ param + " List " ] != None :
# use provided list
choices = args [ param + " List " ] . split ( ' , ' )
value = random . choice ( choices )
else :
# use default list
value = random . choice ( defaultMultiValues )
return ( isRandom , value )
def dumpErrorMsg ( outFileName , msg ) :
print ( " DIAG: " + msg )
if outFileName is not None :
with open ( outFileName , ' w ' ) as jsonFile :
json . dump ( { " errorMsg " : msg } , jsonFile )
def dumpErrorMsgs ( outFileName , msgs ) :
dumpErrorMsg ( outFileName , joinErrorMsgs ( msgs ) )
def joinErrorMsgs ( msgs ) :
return ' \n ' . join ( msgs )
def restricted_float ( x ) :
x = float ( x )
if x < 0.0 or x > 9.0 :
raise argparse . ArgumentTypeError ( " %r not in range [1.0, 9.0] " % ( x , ) )
return x
def to_pascal_case_with_space ( snake_str ) :
return snake_str . replace ( " _ " , " " ) . title ( )
class VariaRandomizer :
parser = argparse . ArgumentParser ( description = " Random Metroid Randomizer " )
parser . add_argument ( ' --patchOnly ' ,
help = " only apply patches, do not perform any randomization " , action = ' store_true ' ,
dest = ' patchOnly ' , default = False )
parser . add_argument ( ' --param ' , ' -p ' , help = " the input parameters " ,
default = None , dest = ' paramsFileName ' )
parser . add_argument ( ' --dir ' ,
help = " output directory for ROM and dot files " ,
dest = ' directory ' , nargs = ' ? ' , default = ' . ' )
parser . add_argument ( ' --dot ' ,
help = " generate dot file with area graph " ,
action = ' store_true ' , dest = ' dot ' , default = False )
parser . add_argument ( ' --area ' , help = " area mode " ,
dest = ' area ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --areaLayoutBase ' ,
help = " use simple layout patch for area mode " , action = ' store_true ' ,
dest = ' areaLayoutBase ' , default = False )
parser . add_argument ( ' --lightArea ' , help = " keep number of transitions between vanilla areas " , action = ' store_true ' ,
dest = ' lightArea ' , default = False )
parser . add_argument ( ' --escapeRando ' ,
help = " Randomize the escape sequence " ,
dest = ' escapeRando ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --noRemoveEscapeEnemies ' ,
help = " Do not remove enemies during escape sequence " , action = ' store_true ' ,
dest = ' noRemoveEscapeEnemies ' , default = False )
parser . add_argument ( ' --bosses ' , help = " randomize bosses " ,
dest = ' bosses ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --minimizer ' , help = " minimizer mode: area and boss mixed together. arg is number of non boss locations " ,
dest = ' minimizerN ' , nargs = ' ? ' , const = 35 , default = None ,
choices = [ str ( i ) for i in range ( 30 , 101 ) ] + [ " random " ] )
parser . add_argument ( ' --minimizerTourian ' ,
help = " Tourian speedup in minimizer mode " ,
dest = ' minimizerTourian ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --startLocation ' , help = " Name of the Access Point to start from " ,
dest = ' startLocation ' , nargs = ' ? ' , default = " Landing Site " ,
choices = [ ' random ' ] + GraphUtils . getStartAccessPointNames ( ) )
parser . add_argument ( ' --startLocationList ' , help = " list to choose from when random " ,
dest = ' startLocationList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --debug ' , ' -d ' , help = " activate debug logging " , dest = ' debug ' ,
action = ' store_true ' )
parser . add_argument ( ' --maxDifficulty ' , ' -t ' ,
help = " the maximum difficulty generated seed will be for given parameters " ,
dest = ' maxDifficulty ' , nargs = ' ? ' , default = None ,
choices = [ ' easy ' , ' medium ' , ' hard ' , ' harder ' , ' hardcore ' , ' mania ' , ' random ' ] )
parser . add_argument ( ' --minDifficulty ' ,
help = " the minimum difficulty generated seed will be for given parameters (speedrun prog speed required) " ,
dest = ' minDifficulty ' , nargs = ' ? ' , default = None ,
choices = [ ' easy ' , ' medium ' , ' hard ' , ' harder ' , ' hardcore ' , ' mania ' ] )
parser . add_argument ( ' --seed ' , ' -s ' , help = " randomization seed to use " , dest = ' seed ' ,
nargs = ' ? ' , default = 0 , type = int )
parser . add_argument ( ' --rom ' , ' -r ' ,
help = " the vanilla ROM " ,
dest = ' rom ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --output ' ,
help = " to choose the name of the generated json (for the webservice) " ,
dest = ' output ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --preset ' ,
help = " the name of the preset (for the webservice) " ,
dest = ' preset ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --patch ' , ' -c ' ,
help = " optional patches to add " ,
dest = ' patches ' , nargs = ' ? ' , default = [ ] , action = ' append ' ,
choices = [ ' itemsounds.ips ' , ' elevators_doors_speed.ips ' , ' random_music.ips ' ,
' spinjumprestart.ips ' , ' rando_speed.ips ' , ' No_Music ' , ' AimAnyButton.ips ' ,
' max_ammo_display.ips ' , ' supermetroid_msu1.ips ' , ' Infinite_Space_Jump ' ,
' refill_before_save.ips ' , ' remove_elevators_doors_speed.ips ' ,
' remove_itemsounds.ips ' , ' vanilla_music.ips ' ] )
parser . add_argument ( ' --missileQty ' , ' -m ' ,
help = " quantity of missiles " ,
dest = ' missileQty ' , nargs = ' ? ' , default = 3 ,
type = restricted_float )
parser . add_argument ( ' --superQty ' , ' -q ' ,
help = " quantity of super missiles " ,
dest = ' superQty ' , nargs = ' ? ' , default = 2 ,
type = restricted_float )
parser . add_argument ( ' --powerBombQty ' , ' -w ' ,
help = " quantity of power bombs " ,
dest = ' powerBombQty ' , nargs = ' ? ' , default = 1 ,
type = restricted_float )
parser . add_argument ( ' --minorQty ' , ' -n ' ,
help = " quantity of minors " ,
dest = ' minorQty ' , nargs = ' ? ' , default = 100 ,
choices = [ str ( i ) for i in range ( 0 , 101 ) ] )
parser . add_argument ( ' --energyQty ' , ' -g ' ,
help = " quantity of ETanks/Reserve Tanks " ,
dest = ' energyQty ' , nargs = ' ? ' , default = ' vanilla ' ,
choices = energyQties + [ ' random ' ] )
parser . add_argument ( ' --energyQtyList ' , help = " list to choose from when random " ,
dest = ' energyQtyList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --strictMinors ' ,
help = " minors quantities values will be strictly followed instead of being probabilities " ,
dest = ' strictMinors ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --majorsSplit ' ,
help = " how to split majors/minors: Full, FullWithHUD, Major, Chozo, Scavenger " ,
dest = ' majorsSplit ' , nargs = ' ? ' , choices = majorsSplits + [ ' random ' ] , default = ' Full ' )
parser . add_argument ( ' --majorsSplitList ' , help = " list to choose from when random " ,
dest = ' majorsSplitList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --scavNumLocs ' ,
help = " For Scavenger split, number of major locations in the mandatory route " ,
dest = ' scavNumLocs ' , nargs = ' ? ' , default = 10 ,
choices = [ " 0 " ] + [ str ( i ) for i in range ( 4 , 17 ) ] )
parser . add_argument ( ' --scavRandomized ' ,
help = " For Scavenger split, decide whether mandatory major locs will have non-vanilla items " ,
dest = ' scavRandomized ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --scavEscape ' ,
help = " For Scavenger split, decide whether escape sequence shall be triggered as soon as the hunt is over " ,
dest = ' scavEscape ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --suitsRestriction ' ,
help = " no suits in early game " ,
dest = ' suitsRestriction ' , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --morphPlacement ' ,
help = " morph placement " ,
dest = ' morphPlacement ' , nargs = ' ? ' , default = ' early ' ,
choices = morphPlacements + [ ' random ' ] )
parser . add_argument ( ' --morphPlacementList ' , help = " list to choose from when random " ,
dest = ' morphPlacementList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --hideItems ' , help = " Like in dessy ' s rando hide half of the items " ,
dest = " hideItems " , nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --progressionSpeed ' , ' -i ' ,
help = " progression speed, from " + str ( speeds ) + " . ' random ' picks a random speed from these. Pick a random speed from a subset using comma-separated values, like ' slow,medium,fast ' . " ,
dest = ' progressionSpeed ' , nargs = ' ? ' , default = ' medium ' , choices = speeds + [ ' random ' ] )
parser . add_argument ( ' --progressionSpeedList ' , help = " list to choose from when random " ,
dest = ' progressionSpeedList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --progressionDifficulty ' ,
help = " " ,
dest = ' progressionDifficulty ' , nargs = ' ? ' , default = ' normal ' ,
choices = progDiffs + [ ' random ' ] )
parser . add_argument ( ' --progressionDifficultyList ' , help = " list to choose from when random " ,
dest = ' progressionDifficultyList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --superFun ' ,
help = " randomly remove major items from the pool for maximum enjoyment " ,
dest = ' superFun ' , nargs = ' ? ' , default = [ ] , action = ' append ' ,
choices = [ ' Movement ' , ' Combat ' , ' Suits ' , ' MovementRandom ' , ' CombatRandom ' , ' SuitsRandom ' ] )
parser . add_argument ( ' --animals ' ,
help = " randomly change the save the animals room " ,
dest = ' animals ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --nolayout ' ,
help = " do not include total randomizer layout patches " ,
dest = ' noLayout ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --gravityBehaviour ' ,
help = " varia/gravity suits behaviour " ,
dest = ' gravityBehaviour ' , nargs = ' ? ' , default = ' Balanced ' , choices = gravityBehaviours + [ ' random ' ] )
parser . add_argument ( ' --gravityBehaviourList ' , help = " list to choose from when random " ,
dest = ' gravityBehaviourList ' , nargs = ' ? ' , default = None )
parser . add_argument ( ' --nerfedCharge ' ,
help = " apply nerfed charge patch " ,
dest = ' nerfedCharge ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --novariatweaks ' ,
help = " do not include VARIA randomizer tweaks " ,
dest = ' noVariaTweaks ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --controls ' ,
help = " specify controls, comma-separated, in that order: Shoot,Jump,Dash,ItemSelect,ItemCancel,AngleUp,AngleDown. Possible values: A,B,X,Y,L,R,Select,None " ,
dest = ' controls ' )
parser . add_argument ( ' --moonwalk ' ,
help = " Enables moonwalk by default " ,
dest = ' moonWalk ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --runtime ' ,
help = " Maximum runtime limit in seconds. If 0 or negative, no runtime limit. Default is 30. " ,
dest = ' runtimeLimit_s ' , nargs = ' ? ' , default = 30 , type = int )
parser . add_argument ( ' --race ' , help = " Race mode magic number, between 1 and 65535 " , dest = ' raceMagic ' ,
type = int )
parser . add_argument ( ' --vcr ' , help = " Generate VCR output file " , dest = ' vcr ' , action = ' store_true ' )
parser . add_argument ( ' --palette ' , help = " Randomize the palettes " , dest = ' palette ' , action = ' store_true ' )
parser . add_argument ( ' --individual_suit_shift ' , help = " palette param " , action = ' store_true ' ,
dest = ' individual_suit_shift ' , default = False )
parser . add_argument ( ' --individual_tileset_shift ' , help = " palette param " , action = ' store_true ' ,
dest = ' individual_tileset_shift ' , default = False )
parser . add_argument ( ' --no_match_ship_and_power ' , help = " palette param " , action = ' store_false ' ,
dest = ' match_ship_and_power ' , default = True )
parser . add_argument ( ' --seperate_enemy_palette_groups ' , help = " palette param " , action = ' store_true ' ,
dest = ' seperate_enemy_palette_groups ' , default = False )
parser . add_argument ( ' --no_match_room_shift_with_boss ' , help = " palette param " , action = ' store_false ' ,
dest = ' match_room_shift_with_boss ' , default = True )
parser . add_argument ( ' --no_shift_tileset_palette ' , help = " palette param " , action = ' store_false ' ,
dest = ' shift_tileset_palette ' , default = True )
parser . add_argument ( ' --no_shift_boss_palettes ' , help = " palette param " , action = ' store_false ' ,
dest = ' shift_boss_palettes ' , default = True )
parser . add_argument ( ' --no_shift_suit_palettes ' , help = " palette param " , action = ' store_false ' ,
dest = ' shift_suit_palettes ' , default = True )
parser . add_argument ( ' --no_shift_enemy_palettes ' , help = " palette param " , action = ' store_false ' ,
dest = ' shift_enemy_palettes ' , default = True )
parser . add_argument ( ' --no_shift_beam_palettes ' , help = " palette param " , action = ' store_false ' ,
dest = ' shift_beam_palettes ' , default = True )
parser . add_argument ( ' --no_shift_ship_palette ' , help = " palette param " , action = ' store_false ' ,
dest = ' shift_ship_palette ' , default = True )
parser . add_argument ( ' --min_degree ' , help = " min hue shift " , dest = ' min_degree ' , nargs = ' ? ' , default = - 180 , type = int )
parser . add_argument ( ' --max_degree ' , help = " max hue shift " , dest = ' max_degree ' , nargs = ' ? ' , default = 180 , type = int )
parser . add_argument ( ' --no_global_shift ' , help = " " , action = ' store_false ' , dest = ' global_shift ' , default = True )
parser . add_argument ( ' --invert ' , help = " invert color range " , dest = ' invert ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --no_blue_door_palette ' , help = " palette param " , action = ' store_true ' ,
dest = ' no_blue_door_palette ' , default = False )
parser . add_argument ( ' --ext_stats ' , help = " dump extended stats SQL " , nargs = ' ? ' , default = None , dest = ' extStatsFilename ' )
parser . add_argument ( ' --randoPreset ' , help = " rando preset file " , dest = " randoPreset " , nargs = ' ? ' , default = None )
parser . add_argument ( ' --fakeRandoPreset ' , help = " for prog speed stats " , dest = " fakeRandoPreset " , nargs = ' ? ' , default = None )
parser . add_argument ( ' --plandoRando ' , help = " json string with already placed items/locs " , dest = " plandoRando " ,
nargs = ' ? ' , default = None )
parser . add_argument ( ' --sprite ' , help = ' use a custom sprite for Samus ' , dest = ' sprite ' , default = None )
parser . add_argument ( ' --no_spin_attack ' , help = ' when using a custom sprite, use the same animation for screw attack with or without Space Jump ' , dest = ' noSpinAttack ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --customItemNames ' , help = ' add custom item names for some of them, related to the custom sprite ' ,
dest = ' customItemNames ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --ship ' , help = ' use a custom sprite for Samus ship ' , dest = ' ship ' , default = None )
parser . add_argument ( ' --seedIps ' , help = ' ips generated from previous seed ' , dest = ' seedIps ' , default = None )
parser . add_argument ( ' --jm, ' , help = " display data used by jm for its stats " , dest = ' jm ' , action = ' store_true ' , default = False )
parser . add_argument ( ' --doorsColorsRando ' , help = ' randomize color of colored doors ' , dest = ' doorsColorsRando ' ,
nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --allowGreyDoors ' , help = ' add grey color in doors colors pool ' , dest = ' allowGreyDoors ' ,
nargs = ' ? ' , const = True , default = False )
parser . add_argument ( ' --logic ' , help = ' logic to use ' , dest = ' logic ' , nargs = ' ? ' , default = " varia " , choices = [ " varia " , " rotation " ] )
parser . add_argument ( ' --hud ' , help = ' Enable VARIA hud ' , dest = ' hud ' ,
nargs = ' ? ' , const = True , default = False )
def __init__ ( self , world , rom , player ) :
# parse args
self . args = copy . deepcopy ( VariaRandomizer . parser . parse_args ( [ " --logic " , " varia " ] ) ) #dummy custom args to skip parsing _sys.argv while still get default values
self . player = player
args = self . args
args . rom = rom
# args.startLocation = to_pascal_case_with_space(world.startLocation[player].current_key)
if args . output is None and args . rom is None :
print ( " Need --output or --rom parameter " )
sys . exit ( - 1 )
elif args . output is not None and args . rom is not None :
print ( " Can ' t have both --output and --rom parameters " )
sys . exit ( - 1 )
if args . plandoRando != None and args . output == None :
print ( " plandoRando param requires output param " )
sys . exit ( - 1 )
utils . log . init ( args . debug )
logger = utils . log . get ( ' Rando ' )
Logic . factory ( args . logic )
# service to force an argument value and notify it
argDict = vars ( args )
self . forcedArgs = { }
self . optErrMsgs = [ ]
optErrMsgs = self . optErrMsgs
def forceArg ( arg , value , msg , altValue = None , webArg = None , webValue = None ) :
okValues = [ value ]
if altValue is not None :
okValues . append ( altValue )
if argDict [ arg ] not in okValues :
argDict [ arg ] = value
self . forcedArgs [ webArg if webArg != None else arg ] = webValue if webValue != None else value
# print(msg)
# optErrMsgs.append(msg)
preset = loadRandoPreset ( world , self . player , args )
# use the skill preset from the rando preset
if preset is not None and preset != ' custom ' and preset != ' varia_custom ' and args . paramsFileName is None :
2021-11-14 05:27:03 +01:00
args . paramsFileName = os . path . join ( appDir , getPresetDir ( preset ) , preset + " .json " )
2021-11-12 08:00:11 -05:00
# if diff preset given, load it
if args . paramsFileName is not None :
PresetLoader . factory ( args . paramsFileName ) . load ( self . player )
preset = os . path . splitext ( os . path . basename ( args . paramsFileName ) ) [ 0 ]
if args . preset is not None :
preset = args . preset
else :
if preset == ' custom ' :
PresetLoader . factory ( world . custom_preset [ player ] . value ) . load ( self . player )
elif preset == ' varia_custom ' :
url = ' https://randommetroidsolver.pythonanywhere.com/presetWebService '
preset_name = next ( iter ( world . varia_custom_preset [ player ] . value ) )
payload = ' {{ " preset " : " {} " }} ' . format ( preset_name )
headers = { ' content-type ' : ' application/json ' , ' Accept-Charset ' : ' UTF-8 ' }
response = requests . post ( url , data = payload , headers = headers )
if response . ok :
PresetLoader . factory ( json . loads ( response . text ) ) . load ( self . player )
else :
2021-12-08 09:27:58 +01:00
raise Exception ( " Got error {} {} {} from trying to fetch varia custom preset named {} " . format ( response . status_code , response . reason , response . text , preset_name ) )
2021-11-12 08:00:11 -05:00
else :
preset = ' default '
2021-11-14 20:51:10 +01:00
PresetLoader . factory ( os . path . join ( appDir , getPresetDir ( ' casual ' ) , ' casual.json ' ) ) . load ( self . player )
2021-11-12 08:00:11 -05:00
logger . debug ( " preset: {} " . format ( preset ) )
# if no seed given, choose one
if args . seed == 0 :
self . seed = random . randrange ( sys . maxsize )
else :
self . seed = args . seed
logger . debug ( " seed: {} " . format ( self . seed ) )
seed4rand = self . seed
if args . raceMagic is not None :
if args . raceMagic < = 0 or args . raceMagic > = 0x10000 :
print ( " Invalid magic " )
sys . exit ( - 1 )
seed4rand = self . seed ^ args . raceMagic
# random.seed(seed4rand)
# if no max diff, set it very high
if args . maxDifficulty :
if args . maxDifficulty == ' random ' :
diffs = [ ' easy ' , ' medium ' , ' hard ' , ' harder ' , ' hardcore ' , ' mania ' ]
self . maxDifficulty = text2diff [ random . choice ( diffs ) ]
else :
self . maxDifficulty = text2diff [ args . maxDifficulty ]
else :
self . maxDifficulty = infinity
# same as solver, increase max difficulty
threshold = self . maxDifficulty
epsilon = 0.001
if self . maxDifficulty < = easy :
threshold = medium - epsilon
elif self . maxDifficulty < = medium :
threshold = hard - epsilon
elif self . maxDifficulty < = hard :
threshold = harder - epsilon
elif self . maxDifficulty < = harder :
threshold = hardcore - epsilon
elif self . maxDifficulty < = hardcore :
threshold = mania - epsilon
maxDifficulty = threshold
logger . debug ( " maxDifficulty: {} " . format ( self . maxDifficulty ) )
# handle random parameters with dynamic pool of values
( _ , progSpeed ) = randomMulti ( args . __dict__ , " progressionSpeed " , speeds )
( _ , progDiff ) = randomMulti ( args . __dict__ , " progressionDifficulty " , progDiffs )
( majorsSplitRandom , args . majorsSplit ) = randomMulti ( args . __dict__ , " majorsSplit " , majorsSplits )
( _ , self . gravityBehaviour ) = randomMulti ( args . __dict__ , " gravityBehaviour " , gravityBehaviours )
if args . minDifficulty :
minDifficulty = text2diff [ args . minDifficulty ]
if progSpeed != " speedrun " :
optErrMsgs . append ( " Minimum difficulty setting ignored, as prog speed is not speedrun " )
else :
minDifficulty = 0
if args . area == True and args . bosses == True and args . minimizerN is not None :
forceArg ( ' majorsSplit ' , ' Full ' , " ' Majors Split ' forced to Full " , altValue = ' FullWithHUD ' )
if args . minimizerN == " random " :
self . minimizerN = random . randint ( 30 , 60 )
logger . debug ( " minimizerN: {} " . format ( self . minimizerN ) )
else :
self . minimizerN = int ( args . minimizerN )
else :
self . minimizerN = None
areaRandom = False
if args . area == ' random ' :
areaRandom = True
args . area = bool ( random . getrandbits ( 1 ) )
logger . debug ( " area: {} " . format ( args . area ) )
doorsColorsRandom = False
if args . doorsColorsRando == ' random ' :
doorsColorsRandom = True
args . doorsColorsRando = bool ( random . getrandbits ( 1 ) )
logger . debug ( " doorsColorsRando: {} " . format ( args . doorsColorsRando ) )
bossesRandom = False
if args . bosses == ' random ' :
bossesRandom = True
args . bosses = bool ( random . getrandbits ( 1 ) )
logger . debug ( " bosses: {} " . format ( args . bosses ) )
if args . escapeRando == ' random ' :
args . escapeRando = bool ( random . getrandbits ( 1 ) )
logger . debug ( " escapeRando: {} " . format ( args . escapeRando ) )
if args . suitsRestriction != False and self . minimizerN is not None :
forceArg ( ' suitsRestriction ' , False , " ' Suits restriction ' forced to off " , webValue = ' off ' )
if args . suitsRestriction == ' random ' :
if args . morphPlacement == ' late ' and args . area == True :
forceArg ( ' suitsRestriction ' , False , " ' Suits restriction ' forced to off " , webValue = ' off ' )
else :
args . suitsRestriction = bool ( random . getrandbits ( 1 ) )
logger . debug ( " suitsRestriction: {} " . format ( args . suitsRestriction ) )
if args . hideItems == ' random ' :
args . hideItems = bool ( random . getrandbits ( 1 ) )
if args . morphPlacement == ' random ' :
if args . morphPlacementList != None :
morphPlacements = args . morphPlacementList . split ( ' , ' )
args . morphPlacement = random . choice ( morphPlacements )
# Scavenger Hunt constraints
if args . majorsSplit == ' Scavenger ' :
forceArg ( ' progressionSpeed ' , ' speedrun ' , " ' Progression speed ' forced to speedrun " )
progSpeed = " speedrun "
forceArg ( ' hud ' , True , " ' VARIA HUD ' forced to on " , webValue = ' on ' )
if not GraphUtils . isStandardStart ( args . startLocation ) :
forceArg ( ' startLocation ' , " Landing Site " , " Start Location forced to Landing Site because of Scavenger mode " )
if args . morphPlacement == ' late ' :
forceArg ( ' morphPlacement ' , ' normal ' , " ' Morph Placement ' forced to normal instead of late " )
if args . scavEscape == True :
forceArg ( ' escapeRando ' , True , " ' Escape randomization ' forced to on " , webValue = ' on ' )
forceArg ( ' noRemoveEscapeEnemies ' , True , " Enemies enabled during escape sequence " , webArg = ' removeEscapeEnemies ' , webValue = ' off ' )
# random fill makes certain options unavailable
if ( progSpeed == ' speedrun ' or progSpeed == ' basic ' ) and args . majorsSplit != ' Scavenger ' :
forceArg ( ' progressionDifficulty ' , ' normal ' , " ' Progression difficulty ' forced to normal " )
progDiff = args . progressionDifficulty
logger . debug ( " progressionDifficulty: {} " . format ( progDiff ) )
if args . strictMinors == ' random ' :
args . strictMinors = bool ( random . getrandbits ( 1 ) )
# in plando rando we know that the start ap is ok
if not GraphUtils . isStandardStart ( args . startLocation ) and args . plandoRando is None :
if args . majorsSplit in [ ' Major ' , " Chozo " ] :
forceArg ( ' hud ' , True , " ' VARIA HUD ' forced to on " , webValue = ' on ' )
forceArg ( ' noVariaTweaks ' , False , " ' VARIA tweaks ' forced to on " , webValue = ' on ' )
forceArg ( ' noLayout ' , False , " ' Anti-softlock layout patches ' forced to on " , webValue = ' on ' )
forceArg ( ' suitsRestriction ' , False , " ' Suits restriction ' forced to off " , webValue = ' off ' )
forceArg ( ' areaLayoutBase ' , False , " ' Additional layout patches for easier navigation ' forced to on " , webValue = ' on ' )
possibleStartAPs , reasons = GraphUtils . getPossibleStartAPs ( args . area , self . maxDifficulty , args . morphPlacement , self . player )
if args . startLocation == ' random ' :
if args . startLocationList != None :
# to be able to give the list in jm we had to replace ' ' with '_', do the opposite operation
startLocationList = args . startLocationList . replace ( ' _ ' , ' ' )
startLocationList = startLocationList . split ( ' , ' )
# intersection between user whishes and reality
possibleStartAPs = sorted ( list ( set ( possibleStartAPs ) . intersection ( set ( startLocationList ) ) ) )
if len ( possibleStartAPs ) == 0 :
optErrMsgs + = [ " %s : %s " % ( apName , cause ) for apName , cause in reasons . items ( ) if apName in startLocationList ]
optErrMsgs . append ( ' Invalid start locations list with your settings. ' )
dumpErrorMsgs ( args . output , optErrMsgs )
sys . exit ( - 1 )
args . startLocation = random . choice ( possibleStartAPs )
elif args . startLocation not in possibleStartAPs :
optErrMsgs . append ( ' Invalid start location: {} . {} ' . format ( args . startLocation , reasons [ args . startLocation ] ) )
optErrMsgs . append ( ' Possible start locations with these settings: {} ' . format ( possibleStartAPs ) )
dumpErrorMsgs ( args . output , optErrMsgs )
sys . exit ( - 1 )
ap = getAccessPoint ( args . startLocation )
if ' forcedEarlyMorph ' in ap . Start and ap . Start [ ' forcedEarlyMorph ' ] == True :
forceArg ( ' morphPlacement ' , ' early ' , " ' Morph Placement ' forced to early for custom start location " )
else :
if progSpeed == ' speedrun ' :
if args . morphPlacement == ' late ' :
forceArg ( ' morphPlacement ' , ' normal ' , " ' Morph Placement ' forced to normal instead of late " )
elif ( not GraphUtils . isStandardStart ( args . startLocation ) ) and args . morphPlacement != ' normal ' :
forceArg ( ' morphPlacement ' , ' normal ' , " ' Morph Placement ' forced to normal for custom start location " )
if args . majorsSplit == ' Chozo ' and args . morphPlacement == " late " :
forceArg ( ' morphPlacement ' , ' normal ' , " ' Morph Placement ' forced to normal for Chozo " )
#if args.patchOnly == False:
# print("SEED: " + str(self.seed))
# fill restrictions dict
restrictions = { ' Suits ' : args . suitsRestriction , ' Morph ' : args . morphPlacement , " doors " : " normal " if not args . doorsColorsRando else " late " }
restrictions [ ' MajorMinor ' ] = ' Full ' if args . majorsSplit == ' FullWithHUD ' else args . majorsSplit
if restrictions [ " MajorMinor " ] == " Scavenger " :
scavNumLocs = int ( args . scavNumLocs )
if scavNumLocs == 0 :
scavNumLocs = random . randint ( 4 , 16 )
restrictions [ " ScavengerParams " ] = { ' numLocs ' : scavNumLocs , ' vanillaItems ' : not args . scavRandomized , ' escape ' : args . scavEscape }
seedCode = ' X '
if majorsSplitRandom == False :
if restrictions [ ' MajorMinor ' ] == ' Full ' :
seedCode = ' FX '
elif restrictions [ ' MajorMinor ' ] == ' Chozo ' :
seedCode = ' ZX '
elif restrictions [ ' MajorMinor ' ] == ' Major ' :
seedCode = ' MX '
elif restrictions [ ' MajorMinor ' ] == ' Scavenger ' :
seedCode = ' SX '
if args . bosses == True and bossesRandom == False :
seedCode = ' B ' + seedCode
if args . doorsColorsRando == True and doorsColorsRandom == False :
seedCode = ' D ' + seedCode
if args . area == True and areaRandom == False :
seedCode = ' A ' + seedCode
# output ROM name
#if args.patchOnly == False:
# self.fileName = 'VARIA_Randomizer_' + seedCode + str(self.seed) + '_' + preset
# if args.progressionSpeed != "random":
# self.fileName += "_" + args.progressionSpeed
#else:
# self.fileName = 'VARIA' # TODO : find better way to name the file (argument?)
self . fileName = output_path
seedName = self . fileName
if args . directory != ' . ' :
self . fileName = args . directory + ' / ' + self . fileName
if args . noLayout == True :
RomPatches . ActivePatches [ self . player ] = RomPatches . TotalBase . copy ( )
else :
RomPatches . ActivePatches [ self . player ] = RomPatches . Total . copy ( )
RomPatches . ActivePatches [ self . player ] . remove ( RomPatches . BlueBrinstarBlueDoor )
RomPatches . ActivePatches [ self . player ] + = GraphUtils . getGraphPatches ( args . startLocation )
if self . gravityBehaviour != " Balanced " :
RomPatches . ActivePatches [ self . player ] . remove ( RomPatches . NoGravityEnvProtection )
if self . gravityBehaviour == " Progressive " :
RomPatches . ActivePatches [ self . player ] . append ( RomPatches . ProgressiveSuits )
if args . nerfedCharge == True :
RomPatches . ActivePatches [ self . player ] . append ( RomPatches . NerfedCharge )
if args . noVariaTweaks == False :
RomPatches . ActivePatches [ self . player ] + = RomPatches . VariaTweaks
if self . minimizerN is not None :
RomPatches . ActivePatches [ self . player ] . append ( RomPatches . NoGadoras )
if args . minimizerTourian == True :
RomPatches . ActivePatches [ self . player ] + = RomPatches . MinimizerTourian
missileQty = float ( args . missileQty )
superQty = float ( args . superQty )
powerBombQty = float ( args . powerBombQty )
minorQty = int ( args . minorQty )
self . energyQty = args . energyQty
if missileQty < 1 :
missileQty = random . randint ( 1 , 9 )
if superQty < 1 :
superQty = random . randint ( 1 , 9 )
if powerBombQty < 1 :
powerBombQty = random . randint ( 1 , 9 )
if minorQty < 1 :
minorQty = random . randint ( 25 , 100 )
if self . energyQty == ' random ' :
if args . energyQtyList != None :
# with jm can't have a list with space in it
energyQtyList = args . energyQtyList . replace ( ' _ ' , ' ' )
energyQties = energyQtyList . split ( ' , ' )
self . energyQty = random . choice ( energyQties )
if self . energyQty == ' ultra sparse ' :
# add nerfed rainbow beam patch
RomPatches . ActivePatches [ self . player ] . append ( RomPatches . NerfedRainbowBeam )
qty = { ' energy ' : self . energyQty ,
' minors ' : minorQty ,
' ammo ' : { ' Missile ' : missileQty ,
' Super ' : superQty ,
' PowerBomb ' : powerBombQty } ,
' strictMinors ' : args . strictMinors }
logger . debug ( " quantities: {} " . format ( qty ) )
if len ( args . superFun ) > 0 :
superFun = [ ]
for fun in args . superFun :
if fun . find ( ' Random ' ) != - 1 :
if bool ( random . getrandbits ( 1 ) ) == True :
superFun . append ( fun [ 0 : fun . find ( ' Random ' ) ] )
else :
superFun . append ( fun )
args . superFun = superFun
logger . debug ( " superFun: {} " . format ( args . superFun ) )
ctrlButton = [ " A " , " B " , " X " , " Y " , " L " , " R " , " Select " ]
ctrl = Controller . ControllerDict [ self . player ]
self . ctrlDict = { getattr ( ctrl , button ) : button for button in ctrlButton }
args . moonWalk = ctrl . Moonwalk
plandoSettings = None
if args . plandoRando is not None :
forceArg ( ' progressionSpeed ' , ' speedrun ' , " ' Progression Speed ' forced to speedrun " )
progSpeed = ' speedrun '
forceArg ( ' majorsSplit ' , ' Full ' , " ' Majors Split ' forced to Full " )
forceArg ( ' morphPlacement ' , ' normal ' , " ' Morph Placement ' forced to normal " )
forceArg ( ' progressionDifficulty ' , ' normal ' , " ' Progression difficulty ' forced to normal " )
progDiff = ' normal '
args . plandoRando = json . loads ( args . plandoRando )
RomPatches . ActivePatches [ self . player ] = args . plandoRando [ " patches " ]
DoorsManager . unserialize ( args . plandoRando [ " doors " ] )
plandoSettings = { " locsItems " : args . plandoRando [ ' locsItems ' ] , " forbiddenItems " : args . plandoRando [ ' forbiddenItems ' ] }
randoSettings = RandoSettings ( self . maxDifficulty , progSpeed , progDiff , qty ,
restrictions , args . superFun , args . runtimeLimit_s ,
plandoSettings , minDifficulty )
# print some parameters for jm's stats
if args . jm == True :
print ( " startLocation: {} " . format ( args . startLocation ) )
print ( " progressionSpeed: {} " . format ( progSpeed ) )
print ( " majorsSplit: {} " . format ( args . majorsSplit ) )
print ( " morphPlacement: {} " . format ( args . morphPlacement ) )
dotFile = None
if args . area == True :
if args . dot == True :
dotFile = args . directory + ' / ' + seedName + ' .dot '
RomPatches . ActivePatches [ self . player ] + = RomPatches . AreaBaseSet
if args . areaLayoutBase == False :
RomPatches . ActivePatches [ self . player ] + = RomPatches . AreaComfortSet
if args . doorsColorsRando == True :
RomPatches . ActivePatches [ self . player ] . append ( RomPatches . RedDoorsMissileOnly )
graphSettings = GraphSettings ( args . startLocation , args . area , args . lightArea , args . bosses ,
args . escapeRando , self . minimizerN , dotFile , args . doorsColorsRando , args . allowGreyDoors ,
args . plandoRando [ " transitions " ] if args . plandoRando != None else None )
if args . plandoRando is None :
DoorsManager . setDoorsColor ( self . player )
self . escapeAttr = None
if args . patchOnly == False :
try :
self . randoExec = RandoExec ( seedName , args . vcr , randoSettings , graphSettings , self . player )
self . container = self . randoExec . randomize ( )
# if we couldn't find an area layout then the escape graph is not created either
# and getDoorConnections will crash if random escape is activated.
stuck = False
if not stuck or args . vcr == True :
self . doors = GraphUtils . getDoorConnections ( self . randoExec . areaGraph ,
args . area , args . bosses ,
args . escapeRando )
escapeAttr = self . randoExec . areaGraph . EscapeAttributes if args . escapeRando else None
if escapeAttr is not None :
escapeAttr [ ' patches ' ] = [ ]
if args . noRemoveEscapeEnemies == True :
escapeAttr [ ' patches ' ] . append ( " Escape_Rando_Enable_Enemies " )
if args . scavEscape == True :
escapeAttr [ ' patches ' ] . append ( ' Escape_Scavenger ' )
except Exception as e :
import traceback
traceback . print_exc ( file = sys . stdout )
dumpErrorMsg ( args . output , " Error: {} " . format ( e ) )
sys . exit ( - 1 )
else :
stuck = False
itemLocs = [ ]
progItemLocs = None
if stuck == True :
dumpErrorMsg ( args . output , self . randoExec . errorMsg )
print ( " Can ' t generate " + self . fileName + " with the given parameters: {} " . format ( self . randoExec . errorMsg ) )
# in vcr mode we still want the seed to be generated to analyze it
if args . vcr == False :
sys . exit ( - 1 )
#if args.patchOnly == False:
# randoExec.postProcessItemLocs(itemLocs, args.hideItems)
def PatchRom ( self , outputFilename , customPatchApply = None ) :
args = self . args
optErrMsgs = self . optErrMsgs
# choose on animal patch
if args . animals == True :
animalsPatches = [ ' animal_enemies.ips ' , ' animals.ips ' , ' draygonimals.ips ' , ' escapimals.ips ' ,
' gameend.ips ' , ' grey_door_animals.ips ' , ' low_timer.ips ' , ' metalimals.ips ' ,
' phantoonimals.ips ' , ' ridleyimals.ips ' ]
if args . escapeRando == False :
args . patches . append ( random . choice ( animalsPatches ) )
args . patches . append ( " Escape_Animals_Change_Event " )
else :
optErrMsgs . append ( " Ignored animals surprise because of escape randomization " )
# # transform itemLocs in our usual dict(location, item), exclude minors, we'll get them with the solver
# locsItems = {}
# for itemLoc in itemLocs:
# locName = itemLoc.Location.Name
# itemType = itemLoc.Item.Type
# if itemType in ['Missile', 'Super', 'PowerBomb']:
# continue
# locsItems[locName] = itemType
# if args.debug == True:
# for loc in sorted(locsItems.keys()):
# print('{:>50}: {:>16} '.format(loc, locsItems[loc]))
# if args.plandoRando != None:
# with open(args.output, 'w') as jsonFile:
# json.dump({"itemLocs": [il.json() for il in itemLocs], "errorMsg": randoExec.errorMsg}, jsonFile)
# sys.exit(0)
# # generate extended stats
# if args.extStatsFilename != None:
# with open(args.extStatsFilename, 'a') as extStatsFile:
# skillPreset = os.path.splitext(os.path.basename(args.paramsFileName))[0]
# if args.fakeRandoPreset is not None:
# randoPreset = args.fakeRandoPreset
# else:
# randoPreset = os.path.splitext(os.path.basename(args.randoPreset))[0]
# db.DB.dumpExtStatsItems(skillPreset, randoPreset, locsItems, extStatsFile)
try :
# args.rom is not None: generate local rom named filename.sfc with args.rom as source
# args.output is not None: generate local json named args.output
if args . rom is not None :
# patch local rom
romFileName = args . rom
shutil . copyfile ( romFileName , outputFilename )
romPatcher = RomPatcher ( outputFilename , args . raceMagic , False , self . player )
else :
romPatcher = RomPatcher ( magic = args . raceMagic )
if args . hud == True or args . majorsSplit == " FullWithHUD " :
args . patches . append ( " varia_hud.ips " )
if args . patchOnly == False :
romPatcher . applyIPSPatches ( args . startLocation , args . patches ,
args . noLayout , self . gravityBehaviour ,
args . area , args . bosses , args . areaLayoutBase ,
args . noVariaTweaks , args . nerfedCharge , self . energyQty == ' ultra sparse ' ,
self . escapeAttr , self . minimizerN , args . minimizerTourian ,
args . doorsColorsRando )
else :
# from customizer permalink, apply previously generated seed ips first
if args . seedIps != None :
romPatcher . applyIPSPatch ( args . seedIps )
romPatcher . addIPSPatches ( args . patches )
# don't color randomize custom ships
args . shift_ship_palette = False
if customPatchApply != None :
customPatchApply ( romPatcher )
# we have to write ips to ROM before doing our direct modifications which will rewrite some parts (like in credits),
# but in web mode we only want to generate a global ips at the end
# if args.rom != None:
# romPatcher.commitIPS()
if args . patchOnly == False :
# romPatcher.writeItemsLocs(itemLocs)
# romPatcher.writeSplitLocs(args.majorsSplit, itemLocs, progItemLocs)
romPatcher . writeItemsNumber ( )
romPatcher . writeSeed ( self . seed ) # lol if race mode
# romPatcher.writeSpoiler(itemLocs, progItemLocs)
# romPatcher.writeRandoSettings(self.randoExec.randoSettings, itemLocs)
romPatcher . writeDoorConnections ( self . doors )
romPatcher . writeVersion ( displayedVersion )
if self . ctrlDict is not None :
romPatcher . writeControls ( self . ctrlDict )
if args . moonWalk == True :
romPatcher . enableMoonWalk ( )
if args . patchOnly == False :
romPatcher . writeMagic ( )
romPatcher . writeMajorsSplit ( args . majorsSplit )
# if args.palette == True:
# paletteSettings = {
# "global_shift": None,
# "individual_suit_shift": None,
# "individual_tileset_shift": None,
# "match_ship_and_power": None,
# "seperate_enemy_palette_groups": None,
# "match_room_shift_with_boss": None,
# "shift_tileset_palette": None,
# "shift_boss_palettes": None,
# "shift_suit_palettes": None,
# "shift_enemy_palettes": None,
# "shift_beam_palettes": None,
# "shift_ship_palette": None,
# "min_degree": None,
# "max_degree": None,
# "invert": None,
# "no_blue_door_palette": None
# }
# for param in paletteSettings:
# paletteSettings[param] = getattr(args, param)
# PaletteRando(romPatcher, paletteSettings, args.sprite).randomize()
# web mode, generate only one ips at the end
if args . rom == None :
romPatcher . commitIPS ( )
romPatcher . end ( )
if args . patchOnly == False :
if len ( optErrMsgs ) > 0 :
# optErrMsgs.append(randoExec.errorMsg)
msg = joinErrorMsgs ( optErrMsgs )
else :
# msg = randoExec.errorMsg
msg = ' '
else :
msg = ' '
if args . rom is None : # web mode
data = romPatcher . romFile . data
self . fileName = ' {} .sfc ' . format ( self . fileName )
data [ " fileName " ] = self . fileName
# error msg in json to be displayed by the web site
data [ " errorMsg " ] = msg
# replaced parameters to update stats in database
if len ( self . forcedArgs ) > 0 :
data [ " forcedArgs " ] = self . forcedArgs
with open ( outputFilename , ' w ' ) as jsonFile :
json . dump ( data , jsonFile )
else : # CLI mode
if msg != " " :
print ( msg )
except Exception as e :
import traceback
traceback . print_exc ( file = sys . stdout )
msg = " Error patching {} : ( {} : {} ) " . format ( outputFilename , type ( e ) . __name__ , e )
dumpErrorMsg ( args . output , msg )
sys . exit ( - 1 )
# if stuck == True:
# print("Rom generated for debug purpose: {}".format(self.fileName))
# else:
# print("Rom generated: {}".format(self.fileName))