| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  | import copy | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-08 16:52:34 -04:00
										 |  |  | from ..utils import log | 
					
						
							|  |  |  | from ..utils.utils import randGaussBounds | 
					
						
							|  |  |  | from ..logic.smbool import SMBool, smboolFalse | 
					
						
							|  |  |  | from ..logic.smboolmanager import SMBoolManager | 
					
						
							|  |  |  | from ..logic.helpers import Bosses | 
					
						
							|  |  |  | from ..graph.graph_utils import getAccessPoint, GraphUtils | 
					
						
							|  |  |  | from ..rando.Filler import FrontFiller | 
					
						
							|  |  |  | from ..rando.ItemLocContainer import ItemLocContainer, getLocListStr, ItemLocation, getItemListStr | 
					
						
							|  |  |  | from ..rando.Restrictions import Restrictions | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  | from ..utils.objectives import Objectives | 
					
						
							| 
									
										
										
										
											2023-04-08 16:52:34 -04:00
										 |  |  | from ..utils.parameters import infinity | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  | from ..rom.rom_patches import RomPatches | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | # checks init conditions for the randomizer: processes super fun settings, graph, start location, special restrictions | 
					
						
							|  |  |  | # the entry point is createItemLocContainer | 
					
						
							|  |  |  | class RandoSetup(object): | 
					
						
							| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  |     def __init__(self, graphSettings, locations, services, player, random): | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.sm = SMBoolManager(player, services.settings.maxDiff) | 
					
						
							| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  |         self.random = random | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.settings = services.settings | 
					
						
							|  |  |  |         self.graphSettings = graphSettings | 
					
						
							|  |  |  |         self.startAP = graphSettings.startAP | 
					
						
							|  |  |  |         self.superFun = self.settings.getSuperFun() | 
					
						
							|  |  |  |         self.container = None | 
					
						
							|  |  |  |         self.services = services | 
					
						
							|  |  |  |         self.restrictions = services.restrictions | 
					
						
							|  |  |  |         self.areaGraph = services.areaGraph | 
					
						
							|  |  |  |         self.allLocations = locations | 
					
						
							|  |  |  |         self.locations = self.areaGraph.getAccessibleLocations(locations, self.startAP) | 
					
						
							|  |  |  | #        print("nLocs Setup: "+str(len(self.locations))) | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |         # in minimizer we can have some missing boss locs | 
					
						
							|  |  |  |         bossesItems = [loc.BossItemType for loc in self.locations if loc.isBoss()] | 
					
						
							| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  |         self.itemManager = self.settings.getItemManager(self.sm, len(self.locations), bossesItems, random) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.forbiddenItems = [] | 
					
						
							|  |  |  |         self.restrictedLocs = [] | 
					
						
							|  |  |  |         self.lastRestricted = [] | 
					
						
							|  |  |  |         self.bossesLocs = sorted(['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']) | 
					
						
							|  |  |  |         self.suits = ['Varia', 'Gravity'] | 
					
						
							|  |  |  |         # organized by priority | 
					
						
							|  |  |  |         self.movementItems = ['SpaceJump', 'HiJump', 'SpeedBooster', 'Bomb', 'Grapple', 'SpringBall'] | 
					
						
							|  |  |  |         # organized by priority | 
					
						
							|  |  |  |         self.combatItems = ['ScrewAttack', 'Plasma', 'Wave', 'Spazer'] | 
					
						
							|  |  |  |         # OMG | 
					
						
							|  |  |  |         self.bossChecks = { | 
					
						
							|  |  |  |             'Kraid' : self.sm.enoughStuffsKraid, | 
					
						
							|  |  |  |             'Phantoon' : self.sm.enoughStuffsPhantoon, | 
					
						
							|  |  |  |             'Draygon' : self.sm.enoughStuffsDraygon, | 
					
						
							|  |  |  |             'Ridley' : self.sm.enoughStuffsRidley, | 
					
						
							|  |  |  |             'Mother Brain': self.sm.enoughStuffsMotherbrain | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         self.okay = lambda: SMBool(True, 0) | 
					
						
							|  |  |  |         exclude = self.settings.getExcludeItems(self.locations) | 
					
						
							|  |  |  |         # we have to use item manager only once, otherwise pool will change | 
					
						
							|  |  |  |         self.itemManager.createItemPool(exclude) | 
					
						
							|  |  |  |         self.basePool = self.itemManager.getItemPool()[:] | 
					
						
							| 
									
										
										
										
											2023-03-25 14:30:38 -04:00
										 |  |  |         self.log = log.get('RandoSetup') | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         if len(locations) != len(self.locations): | 
					
						
							|  |  |  |             self.log.debug("inaccessible locations :"+getLocListStr([loc for loc in locations if loc not in self.locations])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # processes everything and returns an ItemLocContainer, or None if failed (invalid init conditions/settings) | 
					
						
							|  |  |  |     def createItemLocContainer(self, endDate, vcr=None): | 
					
						
							|  |  |  |         self.getForbidden() | 
					
						
							|  |  |  |         self.log.debug("LAST CHECKPOOL") | 
					
						
							|  |  |  |         if not self.checkPool(): | 
					
						
							|  |  |  |             self.log.debug("createItemLocContainer: last checkPool fail") | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         # reset restricted in locs from previous attempt | 
					
						
							|  |  |  |         for loc in self.locations: | 
					
						
							|  |  |  |             loc.restricted = False | 
					
						
							|  |  |  |         for loc in self.restrictedLocs: | 
					
						
							|  |  |  |             self.log.debug("createItemLocContainer: loc is restricted: {}".format(loc.Name)) | 
					
						
							|  |  |  |             loc.restricted = True | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |          | 
					
						
							|  |  |  |         # checkDoorBeams calls checkPool, so save error messages | 
					
						
							|  |  |  |         errorMsgsBck = self.errorMsgs[:] | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.checkDoorBeams() | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |         self.errorMsgs = errorMsgsBck | 
					
						
							|  |  |  |          | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.container = ItemLocContainer(self.sm, self.getItemPool(), self.locations) | 
					
						
							|  |  |  |         if self.restrictions.isLateMorph(): | 
					
						
							|  |  |  |             self.restrictions.lateMorphInit(self.startAP, self.container, self.services) | 
					
						
							|  |  |  |             isStdStart = GraphUtils.isStandardStart(self.startAP) | 
					
						
							|  |  |  |             # ensure we have an area layout that can put morph outside start area | 
					
						
							|  |  |  |             # TODO::allow for custom start which doesn't require morph early | 
					
						
							|  |  |  |             if self.graphSettings.areaRando and isStdStart and not self.restrictions.suitsRestrictions and self.restrictions.lateMorphForbiddenArea is None: | 
					
						
							|  |  |  |                 self.container = None | 
					
						
							|  |  |  |                 self.log.debug("createItemLocContainer: checkLateMorph fail") | 
					
						
							|  |  |  |                 return None | 
					
						
							|  |  |  |         # checkStart needs the container | 
					
						
							|  |  |  |         if not self.checkStart(): | 
					
						
							|  |  |  |             self.container = None | 
					
						
							|  |  |  |             self.log.debug("createItemLocContainer: checkStart fail") | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         self.settings.updateSuperFun(self.superFun) | 
					
						
							|  |  |  |         return self.container | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getRestrictionsDict(self): | 
					
						
							|  |  |  |         itemTypes = {item.Type for item in self.container.itemPool if item.Category not in Restrictions.NoCheckCat} | 
					
						
							|  |  |  |         allAreas = {loc.GraphArea for loc in self.locations} | 
					
						
							|  |  |  |         items = [self.container.getNextItemInPool(itemType) for itemType in itemTypes] | 
					
						
							|  |  |  |         restrictionDict = {} | 
					
						
							|  |  |  |         for area in allAreas: | 
					
						
							|  |  |  |             restrictionDict[area] = {} | 
					
						
							|  |  |  |             for itemType in itemTypes: | 
					
						
							|  |  |  |                 restrictionDict[area][itemType] = set() | 
					
						
							|  |  |  |         for item in items: | 
					
						
							|  |  |  |             itemType = item.Type | 
					
						
							|  |  |  |             poss = self.services.possibleLocations(item, self.startAP, self.container) | 
					
						
							|  |  |  |             for loc in poss: | 
					
						
							|  |  |  |                 restrictionDict[loc.GraphArea][itemType].add(loc.Name) | 
					
						
							|  |  |  |         if self.restrictions.isEarlyMorph() and GraphUtils.isStandardStart(self.startAP): | 
					
						
							|  |  |  |             morphLocs = ['Morphing Ball'] | 
					
						
							|  |  |  |             if self.restrictions.split in ['Full', 'Major']: | 
					
						
							|  |  |  |                 dboost = self.sm.knowsCeilingDBoost() | 
					
						
							|  |  |  |                 if dboost.bool == True and dboost.difficulty <= self.settings.maxDiff: | 
					
						
							|  |  |  |                     morphLocs.append('Energy Tank, Brinstar Ceiling') | 
					
						
							|  |  |  |             for area, locDict in restrictionDict.items(): | 
					
						
							|  |  |  |                 if area == 'Crateria': | 
					
						
							|  |  |  |                     locDict['Morph'] = set(morphLocs) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     locDict['Morph'] = set() | 
					
						
							|  |  |  |         return restrictionDict | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # fill up unreachable locations with "junk" to maximize the chance of the ROM | 
					
						
							|  |  |  |     # to be finishable | 
					
						
							|  |  |  |     def fillRestrictedLocations(self): | 
					
						
							|  |  |  |         def getPred(itemType, loc): | 
					
						
							|  |  |  |             return lambda item: (itemType is None or item.Type == itemType) and self.restrictions.canPlaceAtLocation(item, loc, self.container) | 
					
						
							|  |  |  |         locs = self.restrictedLocs | 
					
						
							|  |  |  |         self.log.debug("fillRestrictedLocations. locs="+getLocListStr(locs)) | 
					
						
							|  |  |  |         for loc in locs: | 
					
						
							|  |  |  |             itemLocation = ItemLocation(None, loc) | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |             if loc.BossItemType is not None: | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred(loc.BossItemType, loc)) | 
					
						
							|  |  |  |             elif self.container.hasItemInPool(getPred('Nothing', loc)): | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Nothing', loc)) | 
					
						
							|  |  |  |             elif self.container.hasItemInPool(getPred('NoEnergy', loc)): | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('NoEnergy', loc)) | 
					
						
							|  |  |  |             elif self.container.countItems(getPred('Missile', loc)) > 3: | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Missile', loc)) | 
					
						
							|  |  |  |             elif self.container.countItems(getPred('Super', loc)) > 2: | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Super', loc)) | 
					
						
							|  |  |  |             elif self.container.countItems(getPred('PowerBomb', loc)) > 1: | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('PowerBomb', loc)) | 
					
						
							|  |  |  |             elif self.container.countItems(getPred('Reserve', loc)) > 1: | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('Reserve', loc)) | 
					
						
							|  |  |  |             elif self.container.countItems(getPred('ETank', loc)) > 3: | 
					
						
							|  |  |  |                 itemLocation.Item = self.container.getNextItemInPoolMatching(getPred('ETank', loc)) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 raise RuntimeError("Cannot fill restricted locations") | 
					
						
							|  |  |  |             self.log.debug("Fill: {}/{} at {}".format(itemLocation.Item.Type, itemLocation.Item.Class, itemLocation.Location.Name)) | 
					
						
							|  |  |  |             self.container.collect(itemLocation, False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getItemPool(self, forbidden=[]): | 
					
						
							|  |  |  |         self.itemManager.setItemPool(self.basePool[:]) # reuse base pool to have constant base item set | 
					
						
							|  |  |  |         return self.itemManager.removeForbiddenItems(self.forbiddenItems + forbidden) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # if needed, do a simplified "pre-randomization" of a few items to check start AP/area layout validity | 
					
						
							|  |  |  |     def checkStart(self): | 
					
						
							|  |  |  |         ap = getAccessPoint(self.startAP) | 
					
						
							|  |  |  |         if not self.graphSettings.areaRando or ap.Start is None or \ | 
					
						
							|  |  |  |            (('needsPreRando' not in ap.Start or not ap.Start['needsPreRando']) and\ | 
					
						
							|  |  |  |             ('areaMode' not in ap.Start or not ap.Start['areaMode'])): | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         self.log.debug("********* PRE RANDO START") | 
					
						
							|  |  |  |         container = copy.copy(self.container) | 
					
						
							| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  |         filler = FrontFiller(self.startAP, self.areaGraph, self.restrictions, container, random=self.random) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         condition = filler.createStepCountCondition(4) | 
					
						
							|  |  |  |         (isStuck, itemLocations, progItems) = filler.generateItems(condition) | 
					
						
							|  |  |  |         self.log.debug("********* PRE RANDO END") | 
					
						
							|  |  |  |         return not isStuck and len(self.services.currentLocations(filler.ap, filler.container)) > 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # in door color rando, determine mandatory beams | 
					
						
							|  |  |  |     def checkDoorBeams(self): | 
					
						
							|  |  |  |         if self.restrictions.isLateDoors(): | 
					
						
							|  |  |  |             doorBeams = ['Wave','Ice','Spazer','Plasma'] | 
					
						
							|  |  |  |             self.restrictions.mandatoryBeams = [beam for beam in doorBeams if not self.checkPool(forbidden=[beam])] | 
					
						
							|  |  |  |             self.log.debug("checkDoorBeams. mandatoryBeams="+str(self.restrictions.mandatoryBeams)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def checkPool(self, forbidden=None): | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |         self.errorMsgs = [] | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.log.debug("checkPool. forbidden=" + str(forbidden) + ", self.forbiddenItems=" + str(self.forbiddenItems)) | 
					
						
							|  |  |  |         if not self.graphSettings.isMinimizer() and not self.settings.isPlandoRando() and len(self.allLocations) > len(self.locations): | 
					
						
							|  |  |  |             # invalid graph with looped areas | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |             msg = "not all areas are connected, but minimizer param is off / not a plando rando" | 
					
						
							|  |  |  |             self.log.debug("checkPool: {}".format(msg)) | 
					
						
							|  |  |  |             self.errorMsgs.append(msg) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |             return False | 
					
						
							|  |  |  |         ret = True | 
					
						
							|  |  |  |         if forbidden is not None: | 
					
						
							|  |  |  |             pool = self.getItemPool(forbidden) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             pool = self.getItemPool() | 
					
						
							|  |  |  |         # get restricted locs | 
					
						
							|  |  |  |         totalAvailLocs = [] | 
					
						
							|  |  |  |         comeBack = {} | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             container = ItemLocContainer(self.sm, pool, self.locations) | 
					
						
							|  |  |  |         except AssertionError as e: | 
					
						
							|  |  |  |             # invalid graph altogether | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |             msg = "AssertionError when creating ItemLocContainer: {}".format(e) | 
					
						
							|  |  |  |             self.log.debug("checkPool: {}".format(msg)) | 
					
						
							|  |  |  |             self.errorMsgs.append(msg) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |             return False | 
					
						
							|  |  |  |         # restrict item pool in chozo: game should be finishable with chozo items only | 
					
						
							|  |  |  |         contPool = [] | 
					
						
							|  |  |  |         contPool += [item for item in pool if item in container.itemPool] | 
					
						
							|  |  |  |         # give us everything and beat every boss to see what we can access | 
					
						
							|  |  |  |         self.disableBossChecks() | 
					
						
							|  |  |  |         self.sm.resetItems() | 
					
						
							|  |  |  |         self.sm.addItems([item.Type for item in contPool]) # will add bosses as well | 
					
						
							|  |  |  |         self.log.debug('pool={}'.format(getItemListStr(container.itemPool))) | 
					
						
							|  |  |  |         locs = self.services.currentLocations(self.startAP, container, post=True) | 
					
						
							|  |  |  |         self.areaGraph.useCache(True) | 
					
						
							|  |  |  |         for loc in locs: | 
					
						
							|  |  |  |             ap = loc.accessPoint | 
					
						
							|  |  |  |             if ap not in comeBack: | 
					
						
							|  |  |  |                 # we chose Golden Four because it is always there. | 
					
						
							|  |  |  |                 # Start APs might not have comeback transitions | 
					
						
							|  |  |  |                 # possible start AP issues are handled in checkStart | 
					
						
							|  |  |  |                 comeBack[ap] = self.areaGraph.canAccess(self.sm, ap, 'Golden Four', self.settings.maxDiff) | 
					
						
							|  |  |  |             if comeBack[ap]: | 
					
						
							|  |  |  |                 totalAvailLocs.append(loc) | 
					
						
							|  |  |  |         self.areaGraph.useCache(False) | 
					
						
							|  |  |  |         self.lastRestricted = [loc for loc in self.locations if loc not in totalAvailLocs] | 
					
						
							|  |  |  |         self.log.debug("restricted=" + str([loc.Name for loc in self.lastRestricted])) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |         # check if objectives are compatible with accessible APs | 
					
						
							|  |  |  |         startAP = self.areaGraph.accessPoints[self.startAP] | 
					
						
							|  |  |  |         availAPs = [ap.Name for ap in self.areaGraph.getAvailableAccessPoints(startAP, self.sm, self.settings.maxDiff)] | 
					
						
							|  |  |  |         self.log.debug("availAPs="+str(availAPs)) | 
					
						
							|  |  |  |         for goal in Objectives.objDict[self.graphSettings.player].activeGoals: | 
					
						
							|  |  |  |             n, aps = goal.escapeAccessPoints | 
					
						
							|  |  |  |             if len(aps) == 0: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             escAPs = [ap for ap in aps if ap in availAPs] | 
					
						
							|  |  |  |             self.log.debug("escAPs="+str(escAPs)) | 
					
						
							|  |  |  |             if len(escAPs) < n: | 
					
						
							|  |  |  |                 msg = "goal '{}' impossible to complete due to area layout".format(goal.name) | 
					
						
							|  |  |  |                 self.log.debug("checkPool. {}".format(msg)) | 
					
						
							|  |  |  |                 self.errorMsgs.append(msg) | 
					
						
							|  |  |  |                 ret = False | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             for ap in escAPs: | 
					
						
							|  |  |  |                 if not self.areaGraph.canAccess(self.sm, ap, "Golden Four", self.settings.maxDiff): | 
					
						
							|  |  |  |                     msg = "goal '{}' impossible to complete due to area layout".format(goal.name) | 
					
						
							|  |  |  |                     self.log.debug("checkPool. {}".format(msg)) | 
					
						
							|  |  |  |                     self.errorMsgs.append(msg) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                     ret = False | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                     break | 
					
						
							|  |  |  |         # check if all inter-area APs can reach each other | 
					
						
							|  |  |  |         if ret: | 
					
						
							|  |  |  |             interAPs = [ap for ap in self.areaGraph.getAccessibleAccessPoints(self.startAP) if not ap.isInternal() and not ap.isLoop()] | 
					
						
							|  |  |  |             for startAp in interAPs: | 
					
						
							|  |  |  |                 availAccessPoints = self.areaGraph.getAvailableAccessPoints(startAp, self.sm, self.settings.maxDiff) | 
					
						
							|  |  |  |                 for ap in interAPs: | 
					
						
							|  |  |  |                     if not ap in availAccessPoints: | 
					
						
							|  |  |  |                         self.log.debug("checkPool: ap {} non accessible from {}".format(ap.Name, startAp.Name)) | 
					
						
							|  |  |  |                         ret = False | 
					
						
							|  |  |  |             if not ret: | 
					
						
							|  |  |  |                 msg = "inter-area APs check failed" | 
					
						
							|  |  |  |                 self.log.debug("checkPool. {}".format(msg)) | 
					
						
							|  |  |  |                 self.errorMsgs.append(msg) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         # cleanup | 
					
						
							|  |  |  |         self.sm.resetItems() | 
					
						
							|  |  |  |         self.restoreBossChecks() | 
					
						
							|  |  |  |         # check if we can reach/beat all bosses | 
					
						
							|  |  |  |         if ret: | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |             # always add G4 to mandatory bosses, even if not required by objectives | 
					
						
							|  |  |  |             mandatoryBosses = set(Objectives.objDict[self.sm.player].getMandatoryBosses() + Bosses.Golden4()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |             for loc in self.lastRestricted: | 
					
						
							|  |  |  |                 if loc.Name in self.bossesLocs: | 
					
						
							|  |  |  |                     ret = False | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                     msg = "unavail Boss: {}".format(loc.Name) | 
					
						
							|  |  |  |                     self.log.debug("checkPool. {}".format(msg)) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |             if ret: | 
					
						
							|  |  |  |                 # revive bosses | 
					
						
							|  |  |  |                 self.sm.addItems([item.Type for item in contPool if item.Category != 'Boss']) | 
					
						
							|  |  |  |                 maxDiff = self.settings.maxDiff | 
					
						
							|  |  |  |                 # see if phantoon doesn't block himself, and if we can reach draygon if she's alive | 
					
						
							|  |  |  |                 ret = self.areaGraph.canAccess(self.sm, self.startAP, 'PhantoonRoomIn', maxDiff)\ | 
					
						
							|  |  |  |                       and self.areaGraph.canAccess(self.sm, self.startAP, 'DraygonRoomIn', maxDiff) | 
					
						
							|  |  |  |                 if ret: | 
					
						
							|  |  |  |                     # see if we can beat bosses with this equipment (infinity as max diff for a "onlyBossesLeft" type check | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                     beatableBosses = sorted([loc.BossItemType for loc in self.services.currentLocations(self.startAP, container, diff=infinity) if loc.isBoss()]) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                     self.log.debug("checkPool. beatableBosses="+str(beatableBosses)) | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                     self.log.debug("checkPool. mandatoryBosses: {}".format(mandatoryBosses)) | 
					
						
							|  |  |  |                     ret = mandatoryBosses.issubset(set(beatableBosses)) and Objectives.objDict[self.sm.player].checkLimitObjectives(beatableBosses) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                     if ret: | 
					
						
							|  |  |  |                         # check that we can then kill mother brain | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                         self.sm.addItems(Bosses.Golden4() + Bosses.miniBosses()) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                         beatableMotherBrain = [loc.Name for loc in self.services.currentLocations(self.startAP, container, diff=infinity) if loc.Name == 'Mother Brain'] | 
					
						
							|  |  |  |                         ret = len(beatableMotherBrain) > 0 | 
					
						
							|  |  |  |                         self.log.debug("checkPool. beatable Mother Brain={}".format(ret)) | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                     else: | 
					
						
							|  |  |  |                         msg = "can't kill all mandatory bosses/minibosses: {}".format(', '.join(list(mandatoryBosses - set(beatableBosses)))) | 
					
						
							|  |  |  |                         self.log.debug("checkPool. {}".format(msg)) | 
					
						
							|  |  |  |                         self.errorMsgs.append(msg) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |                     msg = "locked by Phantoon or Draygon" | 
					
						
							|  |  |  |                     self.log.debug('checkPool. {}'.format(msg)) | 
					
						
							|  |  |  |                     self.errorMsgs.append(msg) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |                 self.log.debug('checkPool. boss access sanity check: '+str(ret)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.restrictions.isChozo() or self.restrictions.isScavenger(): | 
					
						
							|  |  |  |             # in chozo or scavenger, we cannot put other items than NoEnergy in the restricted locations, | 
					
						
							|  |  |  |             # we would be forced to put majors in there, which can make seed generation fail: | 
					
						
							|  |  |  |             # don't put more restricted major locations than removed major items | 
					
						
							|  |  |  |             # FIXME something to do there for chozo/ultra sparse, it gives us up to 3 more spots for nothing items | 
					
						
							|  |  |  |             restrictedLocs = self.restrictedLocs + [loc for loc in self.lastRestricted if loc not in self.restrictedLocs] | 
					
						
							|  |  |  |             nRestrictedMajor = sum(1 for loc in restrictedLocs if self.restrictions.isLocMajor(loc)) | 
					
						
							|  |  |  |             nNothingMajor = sum(1 for item in pool if self.restrictions.isItemMajor(item) and item.Category == 'Nothing') | 
					
						
							|  |  |  |             ret &= nRestrictedMajor <= nNothingMajor | 
					
						
							|  |  |  |             self.log.debug('checkPool. nRestrictedMajor='+str(nRestrictedMajor)+', nNothingMajor='+str(nNothingMajor)) | 
					
						
							|  |  |  |         self.log.debug('checkPool. result: '+str(ret)) | 
					
						
							|  |  |  |         return ret | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def disableBossChecks(self): | 
					
						
							|  |  |  |         self.sm.enoughStuffsKraid = self.okay | 
					
						
							|  |  |  |         self.sm.enoughStuffsPhantoon = self.okay | 
					
						
							|  |  |  |         self.sm.enoughStuffsDraygon = self.okay | 
					
						
							|  |  |  |         self.sm.enoughStuffsRidley = self.okay | 
					
						
							|  |  |  |         def mbCheck(): | 
					
						
							|  |  |  |             (possible, energyDiff) = self.sm.mbEtankCheck() | 
					
						
							|  |  |  |             if possible == True: | 
					
						
							|  |  |  |                 return self.okay() | 
					
						
							|  |  |  |             return smboolFalse | 
					
						
							|  |  |  |         self.sm.enoughStuffsMotherbrain = mbCheck | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def restoreBossChecks(self): | 
					
						
							|  |  |  |         self.sm.enoughStuffsKraid = self.bossChecks['Kraid'] | 
					
						
							|  |  |  |         self.sm.enoughStuffsPhantoon = self.bossChecks['Phantoon'] | 
					
						
							|  |  |  |         self.sm.enoughStuffsDraygon = self.bossChecks['Draygon'] | 
					
						
							|  |  |  |         self.sm.enoughStuffsRidley = self.bossChecks['Ridley'] | 
					
						
							|  |  |  |         self.sm.enoughStuffsMotherbrain = self.bossChecks['Mother Brain'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addRestricted(self): | 
					
						
							|  |  |  |         self.checkPool() | 
					
						
							|  |  |  |         for r in self.lastRestricted: | 
					
						
							|  |  |  |             if r not in self.restrictedLocs: | 
					
						
							|  |  |  |                 self.restrictedLocs.append(r) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getForbiddenItemsFromList(self, itemList): | 
					
						
							|  |  |  |         self.log.debug('getForbiddenItemsFromList: ' + str(itemList)) | 
					
						
							|  |  |  |         remove = [] | 
					
						
							| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  |         n = randGaussBounds(self.random, len(itemList)) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         for i in range(n): | 
					
						
							| 
									
										
										
										
											2025-04-01 17:14:47 +01:00
										 |  |  |             idx = self.random.randint(0, len(itemList) - 1) | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |             item = itemList.pop(idx) | 
					
						
							|  |  |  |             if item is not None: | 
					
						
							|  |  |  |                 remove.append(item) | 
					
						
							|  |  |  |         return remove | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addForbidden(self, removable): | 
					
						
							|  |  |  |         forb = None | 
					
						
							|  |  |  |         # it can take several tries if some item combination removal | 
					
						
							|  |  |  |         # forbids access to more stuff than each individually | 
					
						
							|  |  |  |         tries = 0 | 
					
						
							|  |  |  |         while forb is None and tries < 100: | 
					
						
							|  |  |  |             forb = self.getForbiddenItemsFromList(removable[:]) | 
					
						
							|  |  |  |             self.log.debug("addForbidden. forb="+str(forb)) | 
					
						
							|  |  |  |             if self.checkPool(forb) == False: | 
					
						
							|  |  |  |                 forb = None | 
					
						
							|  |  |  |             tries += 1 | 
					
						
							|  |  |  |         if forb is None: | 
					
						
							|  |  |  |             # we couldn't find a combination, just pick an item | 
					
						
							|  |  |  |             firstItem = next((itemType for itemType in removable if itemType is not None), None) | 
					
						
							|  |  |  |             if firstItem is not None: | 
					
						
							|  |  |  |                 forb = [firstItem] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 forb = [] | 
					
						
							|  |  |  |         self.forbiddenItems += forb | 
					
						
							|  |  |  |         self.addRestricted() | 
					
						
							|  |  |  |         return len(forb) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getForbiddenSuits(self): | 
					
						
							|  |  |  |         self.log.debug("getForbiddenSuits BEGIN. forbidden="+str(self.forbiddenItems)+",ap="+self.startAP) | 
					
						
							|  |  |  |         removableSuits = [suit for suit in self.suits if self.checkPool([suit])] | 
					
						
							|  |  |  |         if 'Varia' in removableSuits and self.startAP in ['Bubble Mountain', 'Firefleas Top']: | 
					
						
							|  |  |  |             # Varia has to be first item there, and checkPool can't detect it | 
					
						
							|  |  |  |             removableSuits.remove('Varia') | 
					
						
							|  |  |  |         self.log.debug("getForbiddenSuits removable="+str(removableSuits)) | 
					
						
							|  |  |  |         if len(removableSuits) > 0: | 
					
						
							|  |  |  |             # remove at least one | 
					
						
							|  |  |  |             if self.addForbidden(removableSuits) == 0: | 
					
						
							|  |  |  |                 self.forbiddenItems.append(removableSuits.pop()) | 
					
						
							|  |  |  |                 self.checkPool() | 
					
						
							|  |  |  |                 self.addRestricted() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.superFun.remove('Suits') | 
					
						
							|  |  |  |             self.log.debug("Super Fun : Could not remove any suit") | 
					
						
							|  |  |  |         self.log.debug("getForbiddenSuits END. forbidden="+str(self.forbiddenItems)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getForbiddenMovement(self): | 
					
						
							|  |  |  |         self.log.debug("getForbiddenMovement BEGIN. forbidden="+str(self.forbiddenItems)) | 
					
						
							|  |  |  |         removableMovement = [mvt for mvt in self.movementItems if self.checkPool([mvt])] | 
					
						
							| 
									
										
										
										
											2023-04-09 18:35:46 -04:00
										 |  |  |         if 'Bomb' in removableMovement and not RomPatches.has(self.sm.player, RomPatches.BombTorizoWake) and Objectives.objDict[self.sm.player].isGoalActive("activate chozo robots"): | 
					
						
							|  |  |  |             # in this objective, without VARIA tweaks, BT has to wake so give bombs | 
					
						
							|  |  |  |             removableMovement.remove('Bomb') | 
					
						
							| 
									
										
										
										
											2021-11-12 08:00:11 -05:00
										 |  |  |         self.log.debug("getForbiddenMovement removable="+str(removableMovement)) | 
					
						
							|  |  |  |         if len(removableMovement) > 0: | 
					
						
							|  |  |  |             # remove at least the most important | 
					
						
							|  |  |  |             self.forbiddenItems.append(removableMovement.pop(0)) | 
					
						
							|  |  |  |             self.addForbidden(removableMovement + [None]) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.superFun.remove('Movement') | 
					
						
							|  |  |  |             self.log.debug('Super Fun : Could not remove any movement item') | 
					
						
							|  |  |  |         self.log.debug("getForbiddenMovement END. forbidden="+str(self.forbiddenItems)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getForbiddenCombat(self): | 
					
						
							|  |  |  |         self.log.debug("getForbiddenCombat BEGIN. forbidden="+str(self.forbiddenItems)) | 
					
						
							|  |  |  |         removableCombat = [cbt for cbt in self.combatItems if self.checkPool([cbt])] | 
					
						
							|  |  |  |         self.log.debug("getForbiddenCombat removable="+str(removableCombat)) | 
					
						
							|  |  |  |         if len(removableCombat) > 0: | 
					
						
							|  |  |  |             fake = [] # placeholders to avoid tricking the gaussian into removing too much stuff | 
					
						
							|  |  |  |             if len(removableCombat) > 0: | 
					
						
							|  |  |  |                 # remove at least one if possible (will be screw or plasma) | 
					
						
							|  |  |  |                 self.forbiddenItems.append(removableCombat.pop(0)) | 
					
						
							|  |  |  |                 fake.append(None) | 
					
						
							|  |  |  |             # if plasma is still available, remove it as well if we can | 
					
						
							|  |  |  |             if len(removableCombat) > 0 and removableCombat[0] == 'Plasma' and self.checkPool([removableCombat[0]]): | 
					
						
							|  |  |  |                 self.forbiddenItems.append(removableCombat.pop(0)) | 
					
						
							|  |  |  |                 fake.append(None) | 
					
						
							|  |  |  |             self.addForbidden(removableCombat + fake) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.superFun.remove('Combat') | 
					
						
							|  |  |  |             self.log.debug('Super Fun : Could not remove any combat item') | 
					
						
							|  |  |  |         self.log.debug("getForbiddenCombat END. forbidden="+str(self.forbiddenItems)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def getForbidden(self): | 
					
						
							|  |  |  |         self.forbiddenItems = [] | 
					
						
							|  |  |  |         self.restrictedLocs = [] | 
					
						
							|  |  |  |         self.errorMsgs = [] | 
					
						
							|  |  |  |         if 'Suits' in self.superFun: # impact on movement item | 
					
						
							|  |  |  |             self.getForbiddenSuits() | 
					
						
							|  |  |  |         if 'Movement' in self.superFun: | 
					
						
							|  |  |  |             self.getForbiddenMovement() | 
					
						
							|  |  |  |         if 'Combat' in self.superFun: | 
					
						
							|  |  |  |             self.getForbiddenCombat() | 
					
						
							|  |  |  |         # if no super fun, check that there's no restricted locations (for ultra sparse) | 
					
						
							|  |  |  |         if len(self.superFun) == 0: | 
					
						
							|  |  |  |             self.addRestricted() | 
					
						
							|  |  |  |         self.log.debug("forbiddenItems: {}".format(self.forbiddenItems)) | 
					
						
							|  |  |  |         self.log.debug("restrictedLocs: {}".format([loc.Name for loc in self.restrictedLocs])) |