import io import os, json, re, random import pathlib import sys from typing import Any import zipfile 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 # support for AP world isAPWorld = ".apworld" in sys.modules[__name__].__file__ def getZipFile(): filename = sys.modules[__name__].__file__ apworldExt = ".apworld" zipPath = pathlib.Path(filename[:filename.index(apworldExt) + len(apworldExt)]) return (zipfile.ZipFile(zipPath), zipPath.stem) def openFile(resource: str, mode: str = "r", encoding: None = None): if isAPWorld: (zipFile, stem) = getZipFile() with zipFile as zf: zipFilePath = resource[resource.index(stem + "/"):] if mode == 'rb': return zf.open(zipFilePath, 'r') else: return io.TextIOWrapper(zf.open(zipFilePath, mode), encoding) else: return open(resource, mode) def listDir(resource: str): if isAPWorld: (zipFile, stem) = getZipFile() with zipFile as zf: zipFilePath = resource[resource.index(stem + "/"):] path = zipfile.Path(zf, zipFilePath + "/") files = [f.at[len(zipFilePath)+1:] for f in path.iterdir()] return files else: return os.listdir(resource) def exists(resource: str): if isAPWorld: (zipFile, stem) = getZipFile() with zipFile as zf: if (stem in resource): zipFilePath = resource[resource.index(stem + "/"):] path = zipfile.Path(zf, zipFilePath) return path.exists() else: return False else: return os.path.exists(resource) def isStdPreset(preset): return preset in ['newbie', 'casual', 'regular', 'veteran', 'expert', 'master', 'samus', 'solution', 'Season_Races', 'SMRAT2021'] def getPresetDir(preset) -> str: if isStdPreset(preset): return 'worlds/sm/variaRandomizer/standard_presets' else: return '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 openFile(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