From 21c56a91d2f5b6199b0ca5614d3aeee9bca79d8c Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Mon, 30 Dec 2024 23:28:40 +0000 Subject: [PATCH 1/2] Super Metroid: Replace random module with world random in variaRandomizer Fixes different generation on webhost compared to local generation, assuming no yaml options are using random options because all random option resolution is currently nondeterministic on webhost. The new arguments for passing self.random through to the various variaRandomizer classes and functions have been added to the end of the arguments for each relevant function, except `utils.randGaussBounds` where the `random` argument is more like a `self` argument in a method. The multiworld seed is now passed through to variaRandomizer instead of it generating its own seed. --- worlds/sm/__init__.py | 2 +- .../sm/variaRandomizer/graph/graph_utils.py | 26 +++++--- worlds/sm/variaRandomizer/rando/Choice.py | 22 +++---- worlds/sm/variaRandomizer/rando/Filler.py | 12 ++-- .../sm/variaRandomizer/rando/GraphBuilder.py | 17 ++--- worlds/sm/variaRandomizer/rando/Items.py | 64 ++++++++++--------- worlds/sm/variaRandomizer/rando/RandoExec.py | 17 ++--- .../sm/variaRandomizer/rando/RandoServices.py | 18 +++--- .../sm/variaRandomizer/rando/RandoSettings.py | 15 ++--- worlds/sm/variaRandomizer/rando/RandoSetup.py | 13 ++-- .../sm/variaRandomizer/rando/Restrictions.py | 4 +- worlds/sm/variaRandomizer/randomizer.py | 41 ++++++------ worlds/sm/variaRandomizer/rom/rompatcher.py | 11 ++-- .../sm/variaRandomizer/utils/doorsmanager.py | 7 +- worlds/sm/variaRandomizer/utils/objectives.py | 13 ++-- worlds/sm/variaRandomizer/utils/utils.py | 6 +- 16 files changed, 150 insertions(+), 138 deletions(-) diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 160b7e4ec78b..c71712b2b8db 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -130,7 +130,7 @@ def generate_early(self): Logic.factory('vanilla') dummy_rom_file = Utils.user_path(SMSettings.RomFile.copy_to) # actual rom set in generate_output - self.variaRando = VariaRandomizer(self.options, dummy_rom_file, self.player) + self.variaRando = VariaRandomizer(self.options, dummy_rom_file, self.player, self.multiworld.seed, self.random) self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty) # keeps Nothing items local so no player will ever pickup Nothing diff --git a/worlds/sm/variaRandomizer/graph/graph_utils.py b/worlds/sm/variaRandomizer/graph/graph_utils.py index 362533434634..b2b889fd2ba3 100644 --- a/worlds/sm/variaRandomizer/graph/graph_utils.py +++ b/worlds/sm/variaRandomizer/graph/graph_utils.py @@ -1,5 +1,4 @@ import copy -import random from ..logic.logic import Logic from ..utils.parameters import Knows from ..graph.location import locationsDict @@ -136,7 +135,8 @@ def getPossibleStartAPs(areaMode, maxDiff, morphPlacement, player): refused[apName] = cause return ret, refused - def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs): + @staticmethod + def updateLocClassesStart(startGraphArea, split, possibleMajLocs, preserveMajLocs, nLocs, random): locs = locationsDict preserveMajLocs = [locs[locName] for locName in preserveMajLocs if locs[locName].isClass(split)] possLocs = [locs[locName] for locName in possibleMajLocs][:nLocs] @@ -160,7 +160,8 @@ def getGraphPatches(startApName): ap = getAccessPoint(startApName) return ap.Start['patches'] if 'patches' in ap.Start else [] - def createBossesTransitions(): + @staticmethod + def createBossesTransitions(random): transitions = vanillaBossesTransitions def isVanilla(): for t in vanillaBossesTransitions: @@ -180,13 +181,15 @@ def isVanilla(): transitions.append((src,dst)) return transitions - def createAreaTransitions(lightAreaRando=False): + @staticmethod + def createAreaTransitions(lightAreaRando=False, *, random): if lightAreaRando: - return GraphUtils.createLightAreaTransitions() + return GraphUtils.createLightAreaTransitions(random=random) else: - return GraphUtils.createRegularAreaTransitions() + return GraphUtils.createRegularAreaTransitions(random=random) - def createRegularAreaTransitions(apList=None, apPred=None): + @staticmethod + def createRegularAreaTransitions(apList=None, apPred=None, *, random): if apList is None: apList = Logic.accessPoints if apPred is None: @@ -239,7 +242,8 @@ def loopUnusedTransitions(transitions, apList=None): transitions.append((ap.Name, ap.Name)) # crateria can be forced in corner cases - def createMinimizerTransitions(startApName, locLimit, forcedAreas=None): + @staticmethod + def createMinimizerTransitions(startApName, locLimit, forcedAreas=None, *, random): if forcedAreas is None: forcedAreas = [] if startApName == 'Ceres': @@ -316,7 +320,8 @@ def openTransitions(): GraphUtils.log.debug("FINAL MINIMIZER areas: "+str(areas)) return transitions - def createLightAreaTransitions(): + @staticmethod + def createLightAreaTransitions(random): # group APs by area aps = {} totalCount = 0 @@ -407,7 +412,8 @@ def getRooms(): return rooms - def escapeAnimalsTransitions(graph, possibleTargets, firstEscape): + @staticmethod + def escapeAnimalsTransitions(graph, possibleTargets, firstEscape, random): n = len(possibleTargets) assert (n < 4 and firstEscape is not None) or (n <= 4 and firstEscape is None), "Invalid possibleTargets list: " + str(possibleTargets) GraphUtils.log.debug("escapeAnimalsTransitions. possibleTargets="+str(possibleTargets)+", firstEscape="+str(firstEscape)) diff --git a/worlds/sm/variaRandomizer/rando/Choice.py b/worlds/sm/variaRandomizer/rando/Choice.py index b4f4166f7654..72af3d809a03 100644 --- a/worlds/sm/variaRandomizer/rando/Choice.py +++ b/worlds/sm/variaRandomizer/rando/Choice.py @@ -1,4 +1,3 @@ -import random from ..utils import log from ..utils.utils import getRangeDict, chooseFromRange from ..rando.ItemLocContainer import ItemLocation @@ -23,8 +22,9 @@ def getLocList(self, itemLocDict, item): # simple random choice, that chooses an item first, then a locatio to put it in class ItemThenLocChoice(Choice): - def __init__(self, restrictions): + def __init__(self, restrictions, random): super(ItemThenLocChoice, self).__init__(restrictions) + self.random = random def chooseItemLoc(self, itemLocDict, isProg): itemList = self.getItemList(itemLocDict) @@ -49,7 +49,7 @@ def chooseItemProg(self, itemList): return self.chooseItemRandom(itemList) def chooseItemRandom(self, itemList): - return random.choice(itemList) + return self.random.choice(itemList) def chooseLocation(self, locList, item, isProg): if len(locList) == 0: @@ -63,12 +63,12 @@ def chooseLocationProg(self, locList, item): return self.chooseLocationRandom(locList) def chooseLocationRandom(self, locList): - return random.choice(locList) + return self.random.choice(locList) # Choice specialization for prog speed based filler class ItemThenLocChoiceProgSpeed(ItemThenLocChoice): - def __init__(self, restrictions, progSpeedParams, distanceProp, services): - super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions) + def __init__(self, restrictions, progSpeedParams, distanceProp, services, random): + super(ItemThenLocChoiceProgSpeed, self).__init__(restrictions, random) self.progSpeedParams = progSpeedParams self.distanceProp = distanceProp self.services = services @@ -104,7 +104,7 @@ def chooseItemLoc(self, itemLocDict, isProg, progressionItemLocs, ap, container) if self.restrictions.isLateMorph() and canRollback and len(itemLocDict) == 1: item, locList = list(itemLocDict.items())[0] if item.Type == 'Morph': - morphLocs = self.restrictions.lateMorphCheck(container, locList) + morphLocs = self.restrictions.lateMorphCheck(container, locList, self.random) if morphLocs is not None: itemLocDict[item] = morphLocs else: @@ -115,7 +115,7 @@ def chooseItemLoc(self, itemLocDict, isProg, progressionItemLocs, ap, container) assert len(locs) == 1 and locs[0].Name == item.Name return ItemLocation(item, locs[0]) # late doors check for random door colors - if self.restrictions.isLateDoors() and random.random() < self.lateDoorsProb: + if self.restrictions.isLateDoors() and self.random.random() < self.lateDoorsProb: self.processLateDoors(itemLocDict, ap, container) self.progressionItemLocs = progressionItemLocs self.ap = ap @@ -145,14 +145,14 @@ def chooseItemProg(self, itemList): def chooseLocationProg(self, locs, item): locs = self.getLocsSpreadProgression(locs) - random.shuffle(locs) + self.random.shuffle(locs) ret = self.getChooseFunc(self.chooseLocRanges, self.chooseLocFuncs)(locs) self.log.debug('chooseLocationProg. ret='+ret.Name) return ret # get choose function from a weighted dict def getChooseFunc(self, rangeDict, funcDict): - v = chooseFromRange(rangeDict) + v = chooseFromRange(rangeDict, self.random) return funcDict[v] @@ -209,6 +209,6 @@ def getLocsSpreadProgression(self, availableLocations): for i in range(len(availableLocations)): loc = availableLocations[i] d = distances[i] - if d == maxDist or random.random() >= self.spreadProb: + if d == maxDist or self.random.random() >= self.spreadProb: locs.append(loc) return locs diff --git a/worlds/sm/variaRandomizer/rando/Filler.py b/worlds/sm/variaRandomizer/rando/Filler.py index 00caa7e630c5..3fab9d39d478 100644 --- a/worlds/sm/variaRandomizer/rando/Filler.py +++ b/worlds/sm/variaRandomizer/rando/Filler.py @@ -1,5 +1,5 @@ -import copy, time, random +import copy, time from ..utils import log from ..logic.cache import RequestCache from ..rando.RandoServices import RandoServices @@ -15,11 +15,11 @@ # item pool is not empty). # entry point is generateItems class Filler(object): - def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity): + def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random): self.startAP = startAP self.cache = RequestCache() self.graph = graph - self.services = RandoServices(graph, restrictions, self.cache) + self.services = RandoServices(graph, restrictions, self.cache, random=random) self.restrictions = restrictions self.settings = restrictions.settings self.endDate = endDate @@ -108,9 +108,9 @@ def step(self): # very simple front fill algorithm with no rollback and no "softlock checks" (== dessy algorithm) class FrontFiller(Filler): - def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity): - super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate) - self.choice = ItemThenLocChoice(restrictions) + def __init__(self, startAP, graph, restrictions, emptyContainer, endDate=infinity, *, random): + super(FrontFiller, self).__init__(startAP, graph, restrictions, emptyContainer, endDate, random=random) + self.choice = ItemThenLocChoice(restrictions, random) self.stdStart = GraphUtils.isStandardStart(self.startAP) def isEarlyGame(self): diff --git a/worlds/sm/variaRandomizer/rando/GraphBuilder.py b/worlds/sm/variaRandomizer/rando/GraphBuilder.py index 88b539e7f04a..7fb8802d540d 100644 --- a/worlds/sm/variaRandomizer/rando/GraphBuilder.py +++ b/worlds/sm/variaRandomizer/rando/GraphBuilder.py @@ -1,5 +1,5 @@ -import random, copy +import copy from ..utils import log from ..graph.graph_utils import GraphUtils, vanillaTransitions, vanillaBossesTransitions, escapeSource, escapeTargets, graphAreas, getAccessPoint from ..logic.logic import Logic @@ -11,13 +11,14 @@ # creates graph and handles randomized escape class GraphBuilder(object): - def __init__(self, graphSettings): + def __init__(self, graphSettings, random): self.graphSettings = graphSettings self.areaRando = graphSettings.areaRando self.bossRando = graphSettings.bossRando self.escapeRando = graphSettings.escapeRando self.minimizerN = graphSettings.minimizerN self.log = log.get('GraphBuilder') + self.random = random # builds everything but escape transitions def createGraph(self, maxDiff): @@ -48,18 +49,18 @@ def createGraph(self, maxDiff): objForced = forcedAreas.intersection(escAreas) escAreasList = sorted(list(escAreas)) while len(objForced) < n and len(escAreasList) > 0: - objForced.add(escAreasList.pop(random.randint(0, len(escAreasList)-1))) + objForced.add(escAreasList.pop(self.random.randint(0, len(escAreasList)-1))) forcedAreas = forcedAreas.union(objForced) - transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN, sorted(list(forcedAreas))) + transitions = GraphUtils.createMinimizerTransitions(self.graphSettings.startAP, self.minimizerN, sorted(list(forcedAreas)), random=self.random) else: if not self.bossRando: transitions += vanillaBossesTransitions else: - transitions += GraphUtils.createBossesTransitions() + transitions += GraphUtils.createBossesTransitions(self.random) if not self.areaRando: transitions += vanillaTransitions else: - transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando) + transitions += GraphUtils.createAreaTransitions(self.graphSettings.lightAreaRando, random=self.random) ret = AccessGraph(Logic.accessPoints, transitions, self.graphSettings.dotFile) Objectives.objDict[self.graphSettings.player].setGraph(ret, maxDiff) return ret @@ -100,7 +101,7 @@ def escapeGraph(self, container, graph, maxDiff, escapeTrigger): self.escapeTimer(graph, paths, self.areaRando or escapeTrigger is not None) self.log.debug("escapeGraph: ({}, {}) timer: {}".format(escapeSource, dst, graph.EscapeAttributes['Timer'])) # animals - GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst) + GraphUtils.escapeAnimalsTransitions(graph, possibleTargets, dst, self.random) return True def _getTargets(self, sm, graph, maxDiff): @@ -110,7 +111,7 @@ def _getTargets(self, sm, graph, maxDiff): if len(possibleTargets) == 0: self.log.debug("Can't randomize escape, fallback to vanilla") possibleTargets.append('Climb Bottom Left') - random.shuffle(possibleTargets) + self.random.shuffle(possibleTargets) return possibleTargets def getPossibleEscapeTargets(self, emptyContainer, graph, maxDiff): diff --git a/worlds/sm/variaRandomizer/rando/Items.py b/worlds/sm/variaRandomizer/rando/Items.py index ec58b4782a61..612bc36b6440 100644 --- a/worlds/sm/variaRandomizer/rando/Items.py +++ b/worlds/sm/variaRandomizer/rando/Items.py @@ -1,6 +1,6 @@ from ..utils.utils import randGaussBounds, getRangeDict, chooseFromRange from ..utils import log -import logging, copy, random +import logging, copy class Item: __slots__ = ( 'Category', 'Class', 'Name', 'Code', 'Type', 'BeamBits', 'ItemBits', 'Id' ) @@ -335,7 +335,7 @@ def getItemTypeCode(item, itemVisibility): itemCode = item.Code + modifier return itemCode - def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff): + def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff, random): self.qty = qty self.sm = sm self.majorsSplit = majorsSplit @@ -344,6 +344,7 @@ def __init__(self, majorsSplit, qty, sm, nLocs, bossesItems, maxDiff): self.maxDiff = maxDiff self.majorClass = 'Chozo' if majorsSplit == 'Chozo' else 'Major' self.itemPool = [] + self.random = random def newItemPool(self, addBosses=True): self.itemPool = [] @@ -386,7 +387,7 @@ def getItem(itemType, itemClass=None): return ItemManager.Items[itemType].withClass(itemClass) def createItemPool(self, exclude=None): - itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff) + itemPoolGenerator = ItemPoolGenerator.factory(self.majorsSplit, self, self.qty, self.sm, exclude, self.nLocs, self.maxDiff, self.random) self.itemPool = itemPoolGenerator.getItemPool() @staticmethod @@ -402,20 +403,20 @@ class ItemPoolGenerator(object): nbBosses = 9 @staticmethod - def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff): + def factory(majorsSplit, itemManager, qty, sm, exclude, nLocs, maxDiff, random): if majorsSplit == 'Chozo': - return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff) + return ItemPoolGeneratorChozo(itemManager, qty, sm, maxDiff, random) elif majorsSplit == 'Plando': - return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff) + return ItemPoolGeneratorPlando(itemManager, qty, sm, exclude, nLocs, maxDiff, random) elif nLocs == ItemPoolGenerator.maxLocs: if majorsSplit == "Scavenger": - return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff) + return ItemPoolGeneratorScavenger(itemManager, qty, sm, maxDiff, random) else: - return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff) + return ItemPoolGeneratorMajors(itemManager, qty, sm, maxDiff, random) else: - return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff) + return ItemPoolGeneratorMinimizer(itemManager, qty, sm, nLocs, maxDiff, random) - def __init__(self, itemManager, qty, sm, maxDiff): + def __init__(self, itemManager, qty, sm, maxDiff, random): self.itemManager = itemManager self.qty = qty self.sm = sm @@ -423,12 +424,13 @@ def __init__(self, itemManager, qty, sm, maxDiff): self.maxEnergy = 18 # 14E, 4R self.maxDiff = maxDiff self.log = log.get('ItemPool') + self.random = random def isUltraSparseNoTanks(self): # if low stuff botwoon is not known there is a hard energy req of one tank, even # with both suits lowStuffBotwoon = self.sm.knowsLowStuffBotwoon() - return random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff) + return self.random.random() < 0.5 and (lowStuffBotwoon.bool == True and lowStuffBotwoon.difficulty <= self.maxDiff) def calcMaxMinors(self): pool = self.itemManager.getItemPool() @@ -464,7 +466,7 @@ def addAmmo(self): rangeDict = getRangeDict(ammoQty) self.log.debug("rangeDict: {}".format(rangeDict)) while len(self.itemManager.getItemPool()) < maxItems: - item = chooseFromRange(rangeDict) + item = chooseFromRange(rangeDict, self.random) self.itemManager.addMinor(item) else: minorsTypes = ['Missile', 'Super', 'PowerBomb'] @@ -522,7 +524,7 @@ def addEnergy(self): # no etank nor reserve self.itemManager.removeItem('ETank') self.itemManager.addItem('NoEnergy', 'Chozo') - elif random.random() < 0.5: + elif self.random.random() < 0.5: # replace only etank with reserve self.itemManager.removeItem('ETank') self.itemManager.addItem('Reserve', 'Chozo') @@ -535,9 +537,9 @@ def addEnergy(self): # 4-6 # already 3E and 1R alreadyInPool = 4 - rest = randGaussBounds(2, 5) + rest = randGaussBounds(self.random, 2, 5) if rest >= 1: - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve', 'Minor') else: self.itemManager.addItem('ETank', 'Minor') @@ -550,13 +552,13 @@ def addEnergy(self): # 8-12 # add up to 3 Reserves or ETanks (cannot add more than 3 reserves) for i in range(3): - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve', 'Minor') else: self.itemManager.addItem('ETank', 'Minor') # 7 already in the pool (3 E, 1 R, + the previous 3) alreadyInPool = 7 - rest = 1 + randGaussBounds(4, 3.7) + rest = 1 + randGaussBounds(self.random, 4, 3.7) for i in range(rest): self.itemManager.addItem('ETank', 'Minor') # fill the rest with NoEnergy @@ -581,10 +583,10 @@ def getItemPool(self): return self.itemManager.getItemPool() class ItemPoolGeneratorMajors(ItemPoolGenerator): - def __init__(self, itemManager, qty, sm, maxDiff): - super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff) - self.sparseRest = 1 + randGaussBounds(2, 5) - self.mediumRest = 3 + randGaussBounds(4, 3.7) + def __init__(self, itemManager, qty, sm, maxDiff, random): + super(ItemPoolGeneratorMajors, self).__init__(itemManager, qty, sm, maxDiff, random) + self.sparseRest = 1 + randGaussBounds(self.random,2, 5) + self.mediumRest = 3 + randGaussBounds(self.random, 4, 3.7) self.ultraSparseNoTanks = self.isUltraSparseNoTanks() def addNoEnergy(self): @@ -609,7 +611,7 @@ def getE(toAdd): # no energy at all self.addNoEnergy() else: - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('ETank') else: self.itemManager.addItem('Reserve') @@ -620,7 +622,7 @@ def getE(toAdd): elif energyQty == 'sparse': # 4-6 - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve') else: self.itemManager.addItem('ETank') @@ -639,7 +641,7 @@ def getE(toAdd): alreadyInPool = 2 n = getE(3) for i in range(n): - if random.random() < 0.5: + if self.random.random() < 0.5: self.itemManager.addItem('Reserve') else: self.itemManager.addItem('ETank') @@ -676,15 +678,15 @@ def getItemPool(self): return self.itemManager.getItemPool() class ItemPoolGeneratorScavenger(ItemPoolGeneratorMajors): - def __init__(self, itemManager, qty, sm, maxDiff): - super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff) + def __init__(self, itemManager, qty, sm, maxDiff, random): + super(ItemPoolGeneratorScavenger, self).__init__(itemManager, qty, sm, maxDiff, random) def addNoEnergy(self): self.itemManager.addItem('Nothing') class ItemPoolGeneratorMinimizer(ItemPoolGeneratorMajors): - def __init__(self, itemManager, qty, sm, nLocs, maxDiff): - super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff) + def __init__(self, itemManager, qty, sm, nLocs, maxDiff, random): + super(ItemPoolGeneratorMinimizer, self).__init__(itemManager, qty, sm, maxDiff, random) self.maxItems = nLocs self.calcMaxAmmo() nMajors = len([itemName for itemName,item in ItemManager.Items.items() if item.Class == 'Major' and item.Category != 'Energy']) @@ -716,8 +718,8 @@ def __init__(self, itemManager, qty, sm, nLocs, maxDiff): self.log.debug("maxEnergy: "+str(self.maxEnergy)) class ItemPoolGeneratorPlando(ItemPoolGenerator): - def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff): - super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff) + def __init__(self, itemManager, qty, sm, exclude, nLocs, maxDiff, random): + super(ItemPoolGeneratorPlando, self).__init__(itemManager, qty, sm, maxDiff, random) # in exclude dict: # in alreadyPlacedItems: # dict of 'itemType: count' of items already added in the plando. @@ -805,7 +807,7 @@ def getItemPool(self): if ammoQty: rangeDict = getRangeDict(ammoQty) while len(self.itemManager.getItemPool()) < maxItems and remain > 0: - item = chooseFromRange(rangeDict) + item = chooseFromRange(rangeDict, self.random) self.itemManager.addMinor(item) remain -= 1 diff --git a/worlds/sm/variaRandomizer/rando/RandoExec.py b/worlds/sm/variaRandomizer/rando/RandoExec.py index f799252f979e..c50e0691e967 100644 --- a/worlds/sm/variaRandomizer/rando/RandoExec.py +++ b/worlds/sm/variaRandomizer/rando/RandoExec.py @@ -1,4 +1,4 @@ -import sys, random, time +import sys, time from ..utils import log from ..logic.logic import Logic @@ -14,7 +14,7 @@ # entry point for rando execution ("randomize" method) class RandoExec(object): - def __init__(self, seedName, vcr, randoSettings, graphSettings, player): + def __init__(self, seedName, vcr, randoSettings, graphSettings, player, random): self.errorMsg = "" self.seedName = seedName self.vcr = vcr @@ -22,6 +22,7 @@ def __init__(self, seedName, vcr, randoSettings, graphSettings, player): self.graphSettings = graphSettings self.log = log.get('RandoExec') self.player = player + self.random = random # processes settings to : # - create Restrictions and GraphBuilder objects @@ -31,7 +32,7 @@ def randomize(self): vcr = VCR(self.seedName, 'rando') if self.vcr == True else None self.errorMsg = "" split = self.randoSettings.restrictions['MajorMinor'] - self.graphBuilder = GraphBuilder(self.graphSettings) + self.graphBuilder = GraphBuilder(self.graphSettings, self.random) container = None i = 0 attempts = 500 if self.graphSettings.areaRando or self.graphSettings.doorsColorsRando or split == 'Scavenger' else 1 @@ -43,10 +44,10 @@ def randomize(self): while container is None and i < attempts and now <= endDate: self.restrictions = Restrictions(self.randoSettings) if self.graphSettings.doorsColorsRando == True: - DoorsManager.randomize(self.graphSettings.allowGreyDoors, self.player) + DoorsManager.randomize(self.graphSettings.allowGreyDoors, self.player, self.random) self.areaGraph = self.graphBuilder.createGraph(self.randoSettings.maxDiff) - services = RandoServices(self.areaGraph, self.restrictions) - setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player) + services = RandoServices(self.areaGraph, self.restrictions, random=self.random) + setup = RandoSetup(self.graphSettings, Logic.locations[:], services, self.player, self.random) self.setup = setup container = setup.createItemLocContainer(endDate, vcr) if container is None: @@ -78,7 +79,7 @@ def updateLocationsClass(self, split): n = nMaj elif split == 'Chozo': n = nChozo - GraphUtils.updateLocClassesStart(startAP.GraphArea, split, possibleMajLocs, preserveMajLocs, n) + GraphUtils.updateLocClassesStart(startAP.GraphArea, split, possibleMajLocs, preserveMajLocs, n, self.random) def postProcessItemLocs(self, itemLocs, hide): # hide some items like in dessy's @@ -89,7 +90,7 @@ def postProcessItemLocs(self, itemLocs, hide): if (item.Category != "Nothing" and loc.CanHidden == True and loc.Visibility == 'Visible'): - if bool(random.getrandbits(1)) == True: + if bool(self.random.getrandbits(1)) == True: loc.Visibility = 'Hidden' # put nothing in unfilled locations filledLocNames = [il.Location.Name for il in itemLocs] diff --git a/worlds/sm/variaRandomizer/rando/RandoServices.py b/worlds/sm/variaRandomizer/rando/RandoServices.py index a3ad1f39b565..e060c356aa54 100644 --- a/worlds/sm/variaRandomizer/rando/RandoServices.py +++ b/worlds/sm/variaRandomizer/rando/RandoServices.py @@ -1,5 +1,4 @@ - -import copy, random, sys, logging, os +import copy, sys, logging, os from enum import Enum, unique from ..utils import log from ..utils.parameters import infinity @@ -19,12 +18,13 @@ class ComebackCheckType(Enum): # collection of stateless services to be used mainly by fillers class RandoServices(object): - def __init__(self, graph, restrictions, cache=None): + def __init__(self, graph, restrictions, cache=None, *, random): self.restrictions = restrictions self.settings = restrictions.settings self.areaGraph = graph self.cache = cache self.log = log.get('RandoServices') + self.random = random @staticmethod def printProgress(s): @@ -217,7 +217,7 @@ def processEarlyMorph(self, ap, container, comebackCheck, itemLocDict, curLocs): # choose a morph item location in that context morphItemLoc = ItemLocation( morph, - random.choice(morphLocs) + self.random.choice(morphLocs) ) # acquire morph in new context and see if we can still open new locs newAP = self.collect(ap, containerCpy, morphItemLoc) @@ -232,7 +232,7 @@ def processLateMorph(self, container, itemLocDict): if morphLocItem is None or len(itemLocDict) == 1: # no morph, or it is the only possibility: nothing to do return - morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem]) + morphLocs = self.restrictions.lateMorphCheck(container, itemLocDict[morphLocItem], self.random) if morphLocs is not None: itemLocDict[morphLocItem] = morphLocs else: @@ -380,10 +380,10 @@ def findStartupProgItemPair(self, ap, container): (itemLocDict, isProg) = self.getPossiblePlacements(ap, container, ComebackCheckType.NoCheck) assert not isProg items = list(itemLocDict.keys()) - random.shuffle(items) + self.random.shuffle(items) for item in items: cont = copy.copy(container) - loc = random.choice(itemLocDict[item]) + loc = self.random.choice(itemLocDict[item]) itemLoc1 = ItemLocation(item, loc) self.log.debug("itemLoc1 attempt: "+getItemLocStr(itemLoc1)) newAP = self.collect(ap, cont, itemLoc1) @@ -391,8 +391,8 @@ def findStartupProgItemPair(self, ap, container): self.cache.reset() (ild, isProg) = self.getPossiblePlacements(newAP, cont, ComebackCheckType.NoCheck) if isProg: - item2 = random.choice(list(ild.keys())) - itemLoc2 = ItemLocation(item2, random.choice(ild[item2])) + item2 = self.random.choice(list(ild.keys())) + itemLoc2 = ItemLocation(item2, self.random.choice(ild[item2])) self.log.debug("itemLoc2: "+getItemLocStr(itemLoc2)) return (itemLoc1, itemLoc2) return None diff --git a/worlds/sm/variaRandomizer/rando/RandoSettings.py b/worlds/sm/variaRandomizer/rando/RandoSettings.py index 418688f1e5a9..b0e9bb4c0d81 100644 --- a/worlds/sm/variaRandomizer/rando/RandoSettings.py +++ b/worlds/sm/variaRandomizer/rando/RandoSettings.py @@ -1,5 +1,4 @@ - -import sys, random +import sys from collections import defaultdict from ..rando.Items import ItemManager from ..utils.utils import getRangeDict, chooseFromRange @@ -32,11 +31,11 @@ def updateSuperFun(self, superFun): def isPlandoRando(self): return self.PlandoOptions is not None - def getItemManager(self, smbm, nLocs, bossesItems): + def getItemManager(self, smbm, nLocs, bossesItems, random): if not self.isPlandoRando(): - return ItemManager(self.restrictions['MajorMinor'], self.qty, smbm, nLocs, bossesItems, self.maxDiff) + return ItemManager(self.restrictions['MajorMinor'], self.qty, smbm, nLocs, bossesItems, self.maxDiff, random) else: - return ItemManager('Plando', self.qty, smbm, nLocs, bossesItems, self.maxDiff) + return ItemManager('Plando', self.qty, smbm, nLocs, bossesItems, self.maxDiff, random) def getExcludeItems(self, locations): if not self.isPlandoRando(): @@ -94,7 +93,7 @@ def __init__(self, restrictions, nLocs): self.restrictions = restrictions self.nLocs = nLocs - def getVariableSpeed(self): + def getVariableSpeed(self, random): ranges = getRangeDict({ 'slowest':7, 'slow':20, @@ -102,7 +101,7 @@ def getVariableSpeed(self): 'fast':27, 'fastest':11 }) - return chooseFromRange(ranges) + return chooseFromRange(ranges, random) def getMinorHelpProb(self, progSpeed): if self.restrictions.split != 'Major': @@ -134,7 +133,7 @@ def getLateDoorsProb(self, progSpeed): def isSlow(self, progSpeed): return progSpeed == "slow" or (progSpeed == "slowest" and self.restrictions.split == "Chozo") - def getItemLimit(self, progSpeed): + def getItemLimit(self, progSpeed, random): itemLimit = self.nLocs if self.isSlow(progSpeed): itemLimit = int(self.nLocs*0.209) # 21 for 105 diff --git a/worlds/sm/variaRandomizer/rando/RandoSetup.py b/worlds/sm/variaRandomizer/rando/RandoSetup.py index 943e3fe5f3a2..eb64c0d3b458 100644 --- a/worlds/sm/variaRandomizer/rando/RandoSetup.py +++ b/worlds/sm/variaRandomizer/rando/RandoSetup.py @@ -1,4 +1,4 @@ -import copy, random +import copy from ..utils import log from ..utils.utils import randGaussBounds @@ -16,8 +16,9 @@ # checks init conditions for the randomizer: processes super fun settings, graph, start location, special restrictions # the entry point is createItemLocContainer class RandoSetup(object): - def __init__(self, graphSettings, locations, services, player): + def __init__(self, graphSettings, locations, services, player, random): self.sm = SMBoolManager(player, services.settings.maxDiff) + self.random = random self.settings = services.settings self.graphSettings = graphSettings self.startAP = graphSettings.startAP @@ -31,7 +32,7 @@ def __init__(self, graphSettings, locations, services, player): # print("nLocs Setup: "+str(len(self.locations))) # in minimizer we can have some missing boss locs bossesItems = [loc.BossItemType for loc in self.locations if loc.isBoss()] - self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems) + self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems, random) self.forbiddenItems = [] self.restrictedLocs = [] self.lastRestricted = [] @@ -165,7 +166,7 @@ def checkStart(self): return True self.log.debug("********* PRE RANDO START") container = copy.copy(self.container) - filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container) + filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container, random=self.random) condition = filler.createStepCountCondition(4) (isStuck, itemLocations, progItems) = filler.generateItems(condition) self.log.debug("********* PRE RANDO END") @@ -345,9 +346,9 @@ def addRestricted(self): def getForbiddenItemsFromList(self, itemList): self.log.debug('getForbiddenItemsFromList: ' + str(itemList)) remove = [] - n = randGaussBounds(len(itemList)) + n = randGaussBounds(self.random, len(itemList)) for i in range(n): - idx = random.randint(0, len(itemList) - 1) + idx = self.random.randint(0, len(itemList) - 1) item = itemList.pop(idx) if item is not None: remove.append(item) diff --git a/worlds/sm/variaRandomizer/rando/Restrictions.py b/worlds/sm/variaRandomizer/rando/Restrictions.py index fabdfea45516..4f9de53d7d6e 100644 --- a/worlds/sm/variaRandomizer/rando/Restrictions.py +++ b/worlds/sm/variaRandomizer/rando/Restrictions.py @@ -1,4 +1,4 @@ -import copy, random +import copy from ..utils import log from ..graph.graph_utils import getAccessPoint from ..rando.ItemLocContainer import getLocListStr @@ -112,7 +112,7 @@ def isItemLocMatching(self, item, loc): return item.Class == "Minor" # return True if we can keep morph as a possibility - def lateMorphCheck(self, container, possibleLocs): + def lateMorphCheck(self, container, possibleLocs, random): # the closer we get to the limit the higher the chances of allowing morph proba = random.randint(0, self.lateMorphLimit) if self.split == 'Full': diff --git a/worlds/sm/variaRandomizer/randomizer.py b/worlds/sm/variaRandomizer/randomizer.py index 8a7a2ea0e2a5..71f21f9e6125 100644 --- a/worlds/sm/variaRandomizer/randomizer.py +++ b/worlds/sm/variaRandomizer/randomizer.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 from Utils import output_path -import argparse, os.path, json, sys, shutil, random, copy, requests +import argparse, os.path, json, sys, shutil, copy, requests from .rando.RandoSettings import RandoSettings, GraphSettings from .rando.RandoExec import RandoExec @@ -39,7 +39,7 @@ def getLogic(): tourians = defaultMultiValues['tourian'] areaRandomizations = defaultMultiValues['areaRandomization'] -def randomMulti(args, param, defaultMultiValues): +def randomMulti(args, param, defaultMultiValues, random): value = args[param] isRandom = False @@ -250,10 +250,11 @@ class VariaRandomizer: parser.add_argument('--tourianList', help="list to choose from when random", dest='tourianList', nargs='?', default=None) - def __init__(self, options, rom, player): + def __init__(self, options, rom, player, seed, random): # 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 + self.random = random args = self.args args.rom = rom # args.startLocation = to_pascal_case_with_space(options.startLocation.current_key) @@ -323,11 +324,13 @@ def forceArg(arg, value, msg, altValue=None, webArg=None, webValue=None): 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 + # Archipelago provides a seed for the multiworld. + self.seed = seed + # # 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)) if args.raceMagic is not None: @@ -360,12 +363,12 @@ def forceArg(arg, value, msg, altValue=None, webArg=None, webValue=None): 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) - (_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians) - (areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations) + (_, progSpeed) = randomMulti(args.__dict__, "progressionSpeed", speeds, random) + (_, progDiff) = randomMulti(args.__dict__, "progressionDifficulty", progDiffs, random) + (majorsSplitRandom, args.majorsSplit) = randomMulti(args.__dict__, "majorsSplit", majorsSplits, random) + (_, self.gravityBehaviour) = randomMulti(args.__dict__, "gravityBehaviour", gravityBehaviours, random) + (_, args.tourian) = randomMulti(args.__dict__, "tourian", tourians, random) + (areaRandom, args.area) = randomMulti(args.__dict__, "area", areaRandomizations, random) areaRandomization = args.area in ['light', 'full'] lightArea = args.area == 'light' @@ -626,7 +629,7 @@ def forceArg(arg, value, msg, altValue=None, webArg=None, webValue=None): if args.objective: if (args.objectiveRandom): availableObjectives = [goal for goal in objectives if goal != "collect 100% items"] if "random" in args.objectiveList else args.objectiveList - self.objectivesManager.setRandom(args.nbObjective, availableObjectives) + self.objectivesManager.setRandom(args.nbObjective, availableObjectives, self.random) else: maxActiveGoals = Objectives.maxActiveGoals - addedObjectives if len(args.objective) > maxActiveGoals: @@ -660,7 +663,7 @@ def forceArg(arg, value, msg, altValue=None, webArg=None, webValue=None): # print("energyQty:{}".format(energyQty)) #try: - self.randoExec = RandoExec(seedName, args.vcr, randoSettings, graphSettings, self.player) + self.randoExec = RandoExec(seedName, args.vcr, randoSettings, graphSettings, self.player, self.random) 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. @@ -690,7 +693,7 @@ def PatchRom(self, outputFilename, customPrePatchApply = None, customPostPatchAp '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(self.random.choice(animalsPatches)) args.patches.append("Escape_Animals_Change_Event") else: optErrMsgs.append("Ignored animals surprise because of escape randomization") @@ -760,9 +763,9 @@ def PatchRom(self, outputFilename, customPrePatchApply = None, customPostPatchAp # patch local rom romFileName = args.rom shutil.copyfile(romFileName, outputFilename) - romPatcher = RomPatcher(settings=patcherSettings, romFileName=outputFilename, magic=args.raceMagic, player=self.player) + romPatcher = RomPatcher(settings=patcherSettings, romFileName=outputFilename, magic=args.raceMagic, player=self.player, random=self.random) else: - romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic) + romPatcher = RomPatcher(settings=patcherSettings, magic=args.raceMagic, random=self.random) if customPrePatchApply != None: customPrePatchApply(romPatcher) diff --git a/worlds/sm/variaRandomizer/rom/rompatcher.py b/worlds/sm/variaRandomizer/rom/rompatcher.py index 2dcf554a0065..8a623f679737 100644 --- a/worlds/sm/variaRandomizer/rom/rompatcher.py +++ b/worlds/sm/variaRandomizer/rom/rompatcher.py @@ -49,7 +49,7 @@ class RomPatcher: 'DoorsColors': ['beam_doors_plms.ips', 'beam_doors_gfx.ips', 'red_doors.ips'] } - def __init__(self, settings=None, romFileName=None, magic=None, player=0): + def __init__(self, settings=None, romFileName=None, magic=None, player=0, *, random): self.log = log.get('RomPatcher') self.settings = settings self.romFileName = romFileName @@ -76,6 +76,7 @@ def __init__(self, settings=None, romFileName=None, magic=None, player=0): 0x93ea: self.forceRoomCRE } self.player = player + self.random = random def patchRom(self): self.applyIPSPatches() @@ -495,9 +496,9 @@ def commitIPS(self): self.romFile.ipsPatch(self.ipsPatches) def writeSeed(self, seed): - random.seed(seed) - seedInfo = random.randint(0, 0xFFFF) - seedInfo2 = random.randint(0, 0xFFFF) + r = random.Random(seed) + seedInfo = r.randint(0, 0xFFFF) + seedInfo2 = r.randint(0, 0xFFFF) self.romFile.writeWord(seedInfo, snes_to_pc(0xdfff00)) self.romFile.writeWord(seedInfo2) @@ -1065,7 +1066,7 @@ def updateIndicatorPLM(door, doorType): def writeObjectives(self, itemLocs, tourian): objectives = Objectives.objDict[self.player] - objectives.writeGoals(self.romFile) + objectives.writeGoals(self.romFile, self.random) objectives.writeIntroObjectives(self.romFile, tourian) self.writeItemsMasks(itemLocs) # hack bomb_torizo.ips to wake BT in all cases if necessary, ie chozo bots objective is on, and nothing at bombs diff --git a/worlds/sm/variaRandomizer/utils/doorsmanager.py b/worlds/sm/variaRandomizer/utils/doorsmanager.py index 6a8ecda1cbfb..425958c54989 100644 --- a/worlds/sm/variaRandomizer/utils/doorsmanager.py +++ b/worlds/sm/variaRandomizer/utils/doorsmanager.py @@ -1,4 +1,3 @@ -import random from enum import IntEnum,IntFlag import copy from ..logic.smbool import SMBool @@ -123,7 +122,7 @@ def filterColorList(self, colorsList): else: return [color for color in colorsList if color not in self.forbiddenColors] - def randomize(self, allowGreyDoors): + def randomize(self, allowGreyDoors, random): if self.canRandomize(): if self.canGrey and allowGreyDoors: self.setColor(random.choice(self.filterColorList(colorsListGrey))) @@ -347,9 +346,9 @@ def setDoorsColor(player=0): currentDoors['CrabShaftRight'].forceBlue() @staticmethod - def randomize(allowGreyDoors, player): + def randomize(allowGreyDoors, player, random): for door in DoorsManager.doorsDict[player].values(): - door.randomize(allowGreyDoors) + door.randomize(allowGreyDoors, random) # 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'] diff --git a/worlds/sm/variaRandomizer/utils/objectives.py b/worlds/sm/variaRandomizer/utils/objectives.py index 67cdb9a1c132..8ca2ce65247f 100644 --- a/worlds/sm/variaRandomizer/utils/objectives.py +++ b/worlds/sm/variaRandomizer/utils/objectives.py @@ -1,5 +1,4 @@ import copy -import random from ..rom.addresses import Addresses from ..rom.rom import pc_to_snes from ..logic.helpers import Bosses @@ -28,7 +27,7 @@ class Synonyms(object): ] alreadyUsed = [] @staticmethod - def getVerb(): + def getVerb(random): verb = random.choice(Synonyms.killSynonyms) while verb in Synonyms.alreadyUsed: verb = random.choice(Synonyms.killSynonyms) @@ -88,10 +87,10 @@ def canClearGoal(self, smbm, ap=None): # not all objectives require an ap (like limit objectives) return self.clearFunc(smbm, ap) - def getText(self): + def getText(self, random): out = "{}. ".format(self.rank) if self.useSynonym: - out += self.text.format(Synonyms.getVerb()) + out += self.text.format(Synonyms.getVerb(random)) else: out += self.text assert len(out) <= 28, "Goal text '{}' is too long: {}, max 28".format(out, len(out)) @@ -676,7 +675,7 @@ def getAllGoals(removeNothing=False): return [goal.name for goal in _goals.values() if goal.available and (not removeNothing or goal.name != "nothing")] # call from rando - def setRandom(self, nbGoals, availableGoals): + def setRandom(self, nbGoals, availableGoals, random): while self.nbActiveGoals < nbGoals and availableGoals: goalName = random.choice(availableGoals) self.addGoal(goalName) @@ -702,7 +701,7 @@ def readGoals(self, romReader): LOG.debug("tourianRequired: {}".format(self.tourianRequired)) # call from rando - def writeGoals(self, romFile): + def writeGoals(self, romFile, random): # write check functions romFile.seek(Addresses.getOne('objectivesList')) for goal in self.activeGoals: @@ -736,7 +735,7 @@ def writeGoals(self, romFile): space = 3 if self.nbActiveGoals == 5 else 4 for i, goal in enumerate(self.activeGoals): addr = baseAddr + i * lineLength * space - text = goal.getText() + text = goal.getText(random) romFile.seek(addr) for c in text: if c not in char2tile: diff --git a/worlds/sm/variaRandomizer/utils/utils.py b/worlds/sm/variaRandomizer/utils/utils.py index f7d699b66549..c297a3ccc7ac 100644 --- a/worlds/sm/variaRandomizer/utils/utils.py +++ b/worlds/sm/variaRandomizer/utils/utils.py @@ -1,5 +1,5 @@ import io -import os, json, re, random +import os, json, re import pathlib import sys from typing import Any @@ -88,7 +88,7 @@ def normalizeRounding(n): # gauss random in [0, r] range # the higher the slope, the less probable extreme values are. -def randGaussBounds(r, slope=5): +def randGaussBounds(random, r, slope=5): r = float(r) n = normalizeRounding(random.gauss(r/2, r/slope)) if n < 0: @@ -111,7 +111,7 @@ def getRangeDict(weightDict): return rangeDict -def chooseFromRange(rangeDict): +def chooseFromRange(rangeDict, random): r = random.random() val = None for v in sorted(rangeDict, key=rangeDict.get): From 770b96d0d46eb64c03c170323b5e3c01f50fa4b4 Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Sun, 2 Feb 2025 02:00:00 +0000 Subject: [PATCH 2/2] Use self.random instead of self.multiworld.random --- worlds/sm/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 9907c99f9ff4..bc8dcd6114bc 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -314,11 +314,11 @@ def create_item(self, name: str) -> Item: raise KeyError(f"Item {name} for {self.player_name} is invalid.") def get_filler_item_name(self) -> str: - if self.multiworld.random.randint(0, 100) < self.options.minor_qty.value: + if self.random.randint(0, 100) < self.options.minor_qty.value: power_bombs = self.options.power_bomb_qty.value missiles = self.options.missile_qty.value super_missiles = self.options.super_qty.value - roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles) + roll = self.random.randint(1, power_bombs + missiles + super_missiles) if roll <= power_bombs: return "Power Bomb" elif roll <= power_bombs + missiles: @@ -340,8 +340,8 @@ def pre_fill(self): else: nonChozoLoc.append(loc) - self.multiworld.random.shuffle(nonChozoLoc) - self.multiworld.random.shuffle(chozoLoc) + self.random.shuffle(nonChozoLoc) + self.random.shuffle(chozoLoc) missingCount = len(self.NothingPool) - len(nonChozoLoc) locations = nonChozoLoc if (missingCount > 0):