SM: comeback fix6 and some refactor (#1756)
refactored and cleaned a bit SMWorld class for best practices: - moved content of Regions.py and Rules.py in SMWorld - moved appropiate code to their dedicated World core functions - moved some Entrances being created in generate_basic to create_regions more comeback check fixes: - fixed setting progression door openers items local if doors_colors_rando is used - enable comeback check only for filling stage as later stages (progression balancing, accessibility and spoiler playthrough) are prone to fail with it
This commit is contained in:
parent
ab5cb7adad
commit
67c3076572
|
@ -1,42 +0,0 @@
|
||||||
def create_regions(self, world, player: int):
|
|
||||||
from . import create_region
|
|
||||||
from BaseClasses import Entrance
|
|
||||||
from .variaRandomizer.logic.logic import Logic
|
|
||||||
from .variaRandomizer.graph.vanilla.graph_locations import locationsDict
|
|
||||||
|
|
||||||
regions = []
|
|
||||||
for accessPoint in Logic.accessPoints:
|
|
||||||
if not accessPoint.Escape:
|
|
||||||
regions.append(create_region(self,
|
|
||||||
world,
|
|
||||||
player,
|
|
||||||
accessPoint.Name,
|
|
||||||
None,
|
|
||||||
[accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()]))
|
|
||||||
|
|
||||||
world.regions += regions
|
|
||||||
|
|
||||||
# create a region for each location and link each to what the location has access
|
|
||||||
# we make them one way so that the filler (and spoiler log) doesnt try to use those region as intermediary path
|
|
||||||
# this is required in AP because a location cant have multiple parent regions
|
|
||||||
locationRegions = []
|
|
||||||
for locationName, value in locationsDict.items():
|
|
||||||
locationRegions.append(create_region( self,
|
|
||||||
world,
|
|
||||||
player,
|
|
||||||
locationName,
|
|
||||||
[locationName]))
|
|
||||||
for key in value.AccessFrom.keys():
|
|
||||||
currentRegion =world.get_region(key, player)
|
|
||||||
currentRegion.exits.append(Entrance(player, key + "->"+ locationName, currentRegion))
|
|
||||||
|
|
||||||
world.regions += locationRegions
|
|
||||||
#create entrances
|
|
||||||
regionConcat = regions + locationRegions
|
|
||||||
for region in regionConcat:
|
|
||||||
for exit in region.exits:
|
|
||||||
exit.connect(world.get_region(exit.name[exit.name.find("->") + 2:], player))
|
|
||||||
|
|
||||||
world.regions += [
|
|
||||||
create_region(self, world, player, 'Menu', None, ['StartAP'])
|
|
||||||
]
|
|
|
@ -1,38 +0,0 @@
|
||||||
from worlds.generic.Rules import set_rule, add_rule
|
|
||||||
|
|
||||||
from .variaRandomizer.graph.vanilla.graph_locations import locationsDict
|
|
||||||
from .variaRandomizer.logic.logic import Logic
|
|
||||||
|
|
||||||
def evalSMBool(smbool, maxDiff):
|
|
||||||
return smbool.bool == True and smbool.difficulty <= maxDiff
|
|
||||||
|
|
||||||
def add_accessFrom_rule(location, player, accessFrom):
|
|
||||||
add_rule(location, lambda state: any((state.can_reach(accessName, player=player) and evalSMBool(rule(state.smbm[player]), state.smbm[player].maxDiff)) for accessName, rule in accessFrom.items()))
|
|
||||||
|
|
||||||
def add_postAvailable_rule(location, player, func):
|
|
||||||
add_rule(location, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
|
||||||
|
|
||||||
def set_available_rule(location, player, func):
|
|
||||||
set_rule(location, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
|
||||||
|
|
||||||
def set_entrance_rule(entrance, player, func):
|
|
||||||
set_rule(entrance, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
|
||||||
|
|
||||||
def add_entrance_rule(entrance, player, func):
|
|
||||||
add_rule(entrance, lambda state: evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
|
||||||
|
|
||||||
def set_rules(world, player):
|
|
||||||
world.completion_condition[player] = lambda state: state.has('Mother Brain', player)
|
|
||||||
|
|
||||||
for key, value in locationsDict.items():
|
|
||||||
location = world.get_location(key, player)
|
|
||||||
set_available_rule(location, player, value.Available)
|
|
||||||
if value.AccessFrom is not None:
|
|
||||||
add_accessFrom_rule(location, player, value.AccessFrom)
|
|
||||||
if value.PostAvailable is not None:
|
|
||||||
add_postAvailable_rule(location, player, value.PostAvailable)
|
|
||||||
|
|
||||||
for accessPoint in Logic.accessPoints:
|
|
||||||
if not accessPoint.Escape:
|
|
||||||
for key, value1 in accessPoint.intraTransitions.items():
|
|
||||||
set_entrance_rule(world.get_entrance(accessPoint.Name + "->" + key, player), player, value1)
|
|
|
@ -10,11 +10,10 @@ from typing import Any, Dict, Iterable, List, Set, TextIO, TypedDict
|
||||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, CollectionState, Tutorial
|
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, CollectionState, Tutorial
|
||||||
from Fill import fill_restrictive
|
from Fill import fill_restrictive
|
||||||
from worlds.AutoWorld import World, AutoLogicRegister, WebWorld
|
from worlds.AutoWorld import World, AutoLogicRegister, WebWorld
|
||||||
|
from worlds.generic.Rules import set_rule, add_rule, add_item_rule
|
||||||
|
|
||||||
logger = logging.getLogger("Super Metroid")
|
logger = logging.getLogger("Super Metroid")
|
||||||
|
|
||||||
from .Regions import create_regions
|
|
||||||
from .Rules import set_rules, add_entrance_rule
|
|
||||||
from .Options import sm_options
|
from .Options import sm_options
|
||||||
from .Client import SMSNIClient
|
from .Client import SMSNIClient
|
||||||
from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols
|
from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols
|
||||||
|
@ -106,6 +105,7 @@ class SMWorld(World):
|
||||||
def __init__(self, world: MultiWorld, player: int):
|
def __init__(self, world: MultiWorld, player: int):
|
||||||
self.rom_name_available_event = threading.Event()
|
self.rom_name_available_event = threading.Event()
|
||||||
self.locations = {}
|
self.locations = {}
|
||||||
|
self.need_comeback_check = True
|
||||||
super().__init__(world, player)
|
super().__init__(world, player)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -134,7 +134,7 @@ class SMWorld(World):
|
||||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
||||||
logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings")
|
logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings")
|
||||||
|
|
||||||
def generate_basic(self):
|
def create_items(self):
|
||||||
itemPool = self.variaRando.container.itemPool
|
itemPool = self.variaRando.container.itemPool
|
||||||
self.startItems = [variaItem for item in self.multiworld.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name]
|
self.startItems = [variaItem for item in self.multiworld.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name]
|
||||||
if self.multiworld.start_inventory_removes_from_pool[self.player]:
|
if self.multiworld.start_inventory_removes_from_pool[self.player]:
|
||||||
|
@ -150,7 +150,6 @@ class SMWorld(World):
|
||||||
pool = []
|
pool = []
|
||||||
self.locked_items = {}
|
self.locked_items = {}
|
||||||
self.NothingPool = []
|
self.NothingPool = []
|
||||||
self.prefilled_locked_items = []
|
|
||||||
weaponCount = [0, 0, 0]
|
weaponCount = [0, 0, 0]
|
||||||
for item in itemPool:
|
for item in itemPool:
|
||||||
isAdvancement = True
|
isAdvancement = True
|
||||||
|
@ -180,12 +179,9 @@ class SMWorld(World):
|
||||||
player=self.player)
|
player=self.player)
|
||||||
|
|
||||||
beamItems = ['Spazer', 'Ice', 'Wave' ,'Plasma']
|
beamItems = ['Spazer', 'Ice', 'Wave' ,'Plasma']
|
||||||
self.ammoItems = ['Missile', 'Super', 'PowerBomb']
|
|
||||||
if self.multiworld.doors_colors_rando[self.player].value != 0:
|
if self.multiworld.doors_colors_rando[self.player].value != 0:
|
||||||
if item.Type in beamItems:
|
if item.Type in beamItems:
|
||||||
self.multiworld.local_items[self.player].value.add(item.Name)
|
self.multiworld.local_items[self.player].value.add(item.Name)
|
||||||
elif item.Type in self.ammoItems and isAdvancement:
|
|
||||||
self.prefilled_locked_items.append(smitem)
|
|
||||||
|
|
||||||
if itemClass == 'Boss':
|
if itemClass == 'Boss':
|
||||||
self.locked_items[item.Name] = smitem
|
self.locked_items[item.Name] = smitem
|
||||||
|
@ -200,8 +196,95 @@ class SMWorld(World):
|
||||||
self.multiworld.get_location(location, self.player).place_locked_item(item)
|
self.multiworld.get_location(location, self.player).place_locked_item(item)
|
||||||
self.multiworld.get_location(location, self.player).address = None
|
self.multiworld.get_location(location, self.player).address = None
|
||||||
|
|
||||||
startAP = self.multiworld.get_entrance('StartAP', self.player)
|
def evalSMBool(self, smbool, maxDiff):
|
||||||
startAP.connect(self.multiworld.get_region(self.variaRando.args.startLocation, self.player))
|
return smbool.bool == True and smbool.difficulty <= maxDiff
|
||||||
|
|
||||||
|
def add_entrance_rule(self, entrance, player, func):
|
||||||
|
add_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||||
|
|
||||||
|
def set_rules(self):
|
||||||
|
def add_accessFrom_rule(location, player, accessFrom):
|
||||||
|
add_rule(location, lambda state: any((state.can_reach(accessName, player=player) and self.evalSMBool(rule(state.smbm[player]), state.smbm[player].maxDiff)) for accessName, rule in accessFrom.items()))
|
||||||
|
|
||||||
|
def add_postAvailable_rule(location, player, func):
|
||||||
|
add_rule(location, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||||
|
|
||||||
|
def set_available_rule(location, player, func):
|
||||||
|
set_rule(location, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||||
|
|
||||||
|
def set_entrance_rule(entrance, player, func):
|
||||||
|
set_rule(entrance, lambda state: self.evalSMBool(func(state.smbm[player]), state.smbm[player].maxDiff))
|
||||||
|
|
||||||
|
self.multiworld.completion_condition[self.player] = lambda state: state.has('Mother Brain', self.player)
|
||||||
|
|
||||||
|
ammoItems = ['Missile', 'Super', 'PowerBomb']
|
||||||
|
for key, value in locationsDict.items():
|
||||||
|
location = self.multiworld.get_location(key, self.player)
|
||||||
|
set_available_rule(location, self.player, value.Available)
|
||||||
|
if value.AccessFrom is not None:
|
||||||
|
add_accessFrom_rule(location, self.player, value.AccessFrom)
|
||||||
|
if value.PostAvailable is not None:
|
||||||
|
add_postAvailable_rule(location, self.player, value.PostAvailable)
|
||||||
|
|
||||||
|
if self.multiworld.doors_colors_rando[self.player].value != 0:
|
||||||
|
add_item_rule(location, lambda item: item.type not in ammoItems or
|
||||||
|
(item.type in ammoItems and \
|
||||||
|
(not item.advancement or (item.advancement and item.player == self.player))))
|
||||||
|
|
||||||
|
for accessPoint in Logic.accessPoints:
|
||||||
|
if not accessPoint.Escape:
|
||||||
|
for key, value1 in accessPoint.intraTransitions.items():
|
||||||
|
set_entrance_rule(self.multiworld.get_entrance(accessPoint.Name + "->" + key, self.player), self.player, value1)
|
||||||
|
|
||||||
|
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||||
|
ret = Region(name, player, world)
|
||||||
|
if locations:
|
||||||
|
for loc in locations:
|
||||||
|
location = self.locations[loc]
|
||||||
|
location.parent_region = ret
|
||||||
|
ret.locations.append(location)
|
||||||
|
if exits:
|
||||||
|
for exit in exits:
|
||||||
|
ret.exits.append(Entrance(player, exit, ret))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def create_regions(self):
|
||||||
|
# create locations
|
||||||
|
for name in locationsDict:
|
||||||
|
self.locations[name] = SMLocation(self.player, name, self.location_name_to_id.get(name, None))
|
||||||
|
|
||||||
|
# create regions
|
||||||
|
regions = []
|
||||||
|
for accessPoint in Logic.accessPoints:
|
||||||
|
if not accessPoint.Escape:
|
||||||
|
regions.append(self.create_region( self.multiworld,
|
||||||
|
self.player,
|
||||||
|
accessPoint.Name,
|
||||||
|
None,
|
||||||
|
[accessPoint.Name + "->" + key for key in accessPoint.intraTransitions.keys()]))
|
||||||
|
|
||||||
|
self.multiworld.regions += regions
|
||||||
|
|
||||||
|
# create a region for each location and link each to what the location has access
|
||||||
|
# we make them one way so that the filler (and spoiler log) doesnt try to use those region as intermediary path
|
||||||
|
# this is required in AP because a location cant have multiple parent regions
|
||||||
|
locationRegions = []
|
||||||
|
for locationName, value in locationsDict.items():
|
||||||
|
locationRegions.append(self.create_region( self.multiworld,
|
||||||
|
self.player,
|
||||||
|
locationName,
|
||||||
|
[locationName]))
|
||||||
|
for key in value.AccessFrom.keys():
|
||||||
|
currentRegion = self.multiworld.get_region(key, self.player)
|
||||||
|
currentRegion.exits.append(Entrance(self.player, key + "->"+ locationName, currentRegion))
|
||||||
|
|
||||||
|
self.multiworld.regions += locationRegions
|
||||||
|
|
||||||
|
#create entrances
|
||||||
|
regionConcat = regions + locationRegions
|
||||||
|
for region in regionConcat:
|
||||||
|
for exit in region.exits:
|
||||||
|
exit.connect(self.multiworld.get_region(exit.name[exit.name.find("->") + 2:], self.player))
|
||||||
|
|
||||||
for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions:
|
for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions:
|
||||||
src_region = self.multiworld.get_region(src.Name, self.player)
|
src_region = self.multiworld.get_region(src.Name, self.player)
|
||||||
|
@ -210,26 +293,125 @@ class SMWorld(World):
|
||||||
src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region))
|
src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region))
|
||||||
srcDestEntrance = self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player)
|
srcDestEntrance = self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player)
|
||||||
srcDestEntrance.connect(dest_region)
|
srcDestEntrance.connect(dest_region)
|
||||||
add_entrance_rule(self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse)
|
self.add_entrance_rule(self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse)
|
||||||
|
|
||||||
def set_rules(self):
|
self.multiworld.regions += [
|
||||||
set_rules(self.multiworld, self.player)
|
self.create_region(self.multiworld, self.player, 'Menu', None, ['StartAP'])
|
||||||
|
]
|
||||||
|
|
||||||
def create_regions(self):
|
startAP = self.multiworld.get_entrance('StartAP', self.player)
|
||||||
create_locations(self, self.player)
|
startAP.connect(self.multiworld.get_region(self.variaRando.args.startLocation, self.player))
|
||||||
create_regions(self, self.multiworld, self.player)
|
|
||||||
|
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||||
|
state.smbm[self.player].addItem(item.type)
|
||||||
|
if item.location != None and item.location.game == self.game:
|
||||||
|
for entrance in self.multiworld.get_region(item.location.parent_region.name, item.location.player).entrances:
|
||||||
|
if (entrance.parent_region.can_reach(state)):
|
||||||
|
state.smbm[item.location.player].lastAP = entrance.parent_region.name
|
||||||
|
break
|
||||||
|
return super(SMWorld, self).collect(state, item)
|
||||||
|
|
||||||
|
def remove(self, state: CollectionState, item: Item) -> bool:
|
||||||
|
state.smbm[self.player].removeItem(item.type)
|
||||||
|
return super(SMWorld, self).remove(state, item)
|
||||||
|
|
||||||
|
def create_item(self, name: str) -> Item:
|
||||||
|
item = next(x for x in ItemManager.Items.values() if x.Name == name)
|
||||||
|
return SMItem(item.Name, ItemClassification.progression if item.Class != 'Minor' else ItemClassification.filler, item.Type, self.item_name_to_id[item.Name],
|
||||||
|
player=self.player)
|
||||||
|
|
||||||
|
def get_filler_item_name(self) -> str:
|
||||||
|
if self.multiworld.random.randint(0, 100) < self.multiworld.minor_qty[self.player].value:
|
||||||
|
power_bombs = self.multiworld.power_bomb_qty[self.player].value
|
||||||
|
missiles = self.multiworld.missile_qty[self.player].value
|
||||||
|
super_missiles = self.multiworld.super_qty[self.player].value
|
||||||
|
roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles)
|
||||||
|
if roll <= power_bombs:
|
||||||
|
return "Power Bomb"
|
||||||
|
elif roll <= power_bombs + missiles:
|
||||||
|
return "Missile"
|
||||||
|
else:
|
||||||
|
return "Super Missile"
|
||||||
|
else:
|
||||||
|
return "Nothing"
|
||||||
|
|
||||||
def pre_fill(self):
|
def pre_fill(self):
|
||||||
from Fill import fill_restrictive
|
if len(self.NothingPool) > 0:
|
||||||
if len(self.prefilled_locked_items) > 0:
|
nonChozoLoc = []
|
||||||
locations = [loc for loc in self.locations.values() if loc.item is None]
|
chozoLoc = []
|
||||||
self.multiworld.random.shuffle(locations)
|
|
||||||
all_state = self.multiworld.get_all_state(False)
|
|
||||||
for item in self.ammoItems:
|
|
||||||
while (all_state.has(item.name, self.player, 1)):
|
|
||||||
all_state.remove(item)
|
|
||||||
|
|
||||||
fill_restrictive(self.multiworld, all_state, locations, self.prefilled_locked_items, True, True)
|
for loc in self.locations.values():
|
||||||
|
if loc.item is None:
|
||||||
|
if locationsDict[loc.name].isChozo():
|
||||||
|
chozoLoc.append(loc)
|
||||||
|
else:
|
||||||
|
nonChozoLoc.append(loc)
|
||||||
|
|
||||||
|
self.multiworld.random.shuffle(nonChozoLoc)
|
||||||
|
self.multiworld.random.shuffle(chozoLoc)
|
||||||
|
missingCount = len(self.NothingPool) - len(nonChozoLoc)
|
||||||
|
locations = nonChozoLoc
|
||||||
|
if (missingCount > 0):
|
||||||
|
locations += chozoLoc[:missingCount]
|
||||||
|
locations = locations[:len(self.NothingPool)]
|
||||||
|
for item, loc in zip(self.NothingPool, locations):
|
||||||
|
loc.place_locked_item(item)
|
||||||
|
loc.address = loc.item.code = None
|
||||||
|
|
||||||
|
def post_fill(self):
|
||||||
|
self.itemLocs = [
|
||||||
|
ItemLocation(ItemManager.Items[itemLoc.item.type
|
||||||
|
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
|
||||||
|
'ArchipelagoItem'],
|
||||||
|
locationsDict[itemLoc.name], itemLoc.item.player, True)
|
||||||
|
for itemLoc in self.multiworld.get_locations(self.player)
|
||||||
|
]
|
||||||
|
self.progItemLocs = [
|
||||||
|
ItemLocation(ItemManager.Items[itemLoc.item.type
|
||||||
|
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
|
||||||
|
'ArchipelagoItem'],
|
||||||
|
locationsDict[itemLoc.name], itemLoc.item.player, True)
|
||||||
|
for itemLoc in self.multiworld.get_locations(self.player) if itemLoc.item.advancement
|
||||||
|
]
|
||||||
|
for itemLoc in self.itemLocs:
|
||||||
|
if itemLoc.Item.Class == "Boss":
|
||||||
|
itemLoc.Item.Class = "Minor"
|
||||||
|
for itemLoc in self.progItemLocs:
|
||||||
|
if itemLoc.Item.Class == "Boss":
|
||||||
|
itemLoc.Item.Class = "Minor"
|
||||||
|
|
||||||
|
localItemLocs = [il for il in self.itemLocs if il.player == self.player]
|
||||||
|
localprogItemLocs = [il for il in self.progItemLocs if il.player == self.player]
|
||||||
|
|
||||||
|
escapeTrigger = (localItemLocs, localprogItemLocs, 'Full') if self.variaRando.randoExec.randoSettings.restrictions["EscapeTrigger"] else None
|
||||||
|
escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger)
|
||||||
|
assert escapeOk, "Could not find a solution for escape"
|
||||||
|
|
||||||
|
self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph,
|
||||||
|
self.variaRando.args.area, self.variaRando.args.bosses,
|
||||||
|
self.variaRando.args.escapeRando)
|
||||||
|
|
||||||
|
self.variaRando.randoExec.postProcessItemLocs(self.itemLocs, self.variaRando.args.hideItems)
|
||||||
|
|
||||||
|
self.need_comeback_check = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def stage_post_fill(cls, world):
|
||||||
|
new_state = CollectionState(world)
|
||||||
|
progitempool = []
|
||||||
|
for item in world.itempool:
|
||||||
|
if item.game == "Super Metroid" and item.advancement:
|
||||||
|
progitempool.append(item)
|
||||||
|
|
||||||
|
for item in progitempool:
|
||||||
|
new_state.collect(item, True)
|
||||||
|
|
||||||
|
bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']
|
||||||
|
for player in world.get_game_players("Super Metroid"):
|
||||||
|
for bossLoc in bossesLoc:
|
||||||
|
if not world.get_location(bossLoc, player).can_reach(new_state):
|
||||||
|
world.state.smbm[player].onlyBossLeft = True
|
||||||
|
break
|
||||||
|
|
||||||
def getWordArray(self, w: int) -> List[int]:
|
def getWordArray(self, w: int) -> List[int]:
|
||||||
""" little-endian convert a 16-bit number to an array of numbers <= 255 each """
|
""" little-endian convert a 16-bit number to an array of numbers <= 255 each """
|
||||||
|
@ -669,115 +851,6 @@ class SMWorld(World):
|
||||||
|
|
||||||
return slot_data
|
return slot_data
|
||||||
|
|
||||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
|
||||||
state.smbm[self.player].addItem(item.type)
|
|
||||||
if item.location != None and item.location.game == self.game:
|
|
||||||
for entrance in self.multiworld.get_region(item.location.parent_region.name, item.location.player).entrances:
|
|
||||||
if (entrance.parent_region.can_reach(state)):
|
|
||||||
state.smbm[item.location.player].lastAP = entrance.parent_region.name
|
|
||||||
break
|
|
||||||
return super(SMWorld, self).collect(state, item)
|
|
||||||
|
|
||||||
def remove(self, state: CollectionState, item: Item) -> bool:
|
|
||||||
state.smbm[self.player].removeItem(item.type)
|
|
||||||
return super(SMWorld, self).remove(state, item)
|
|
||||||
|
|
||||||
def create_item(self, name: str) -> Item:
|
|
||||||
item = next(x for x in ItemManager.Items.values() if x.Name == name)
|
|
||||||
return SMItem(item.Name, ItemClassification.progression if item.Class != 'Minor' else ItemClassification.filler, item.Type, self.item_name_to_id[item.Name],
|
|
||||||
player=self.player)
|
|
||||||
|
|
||||||
def get_filler_item_name(self) -> str:
|
|
||||||
if self.multiworld.random.randint(0, 100) < self.multiworld.minor_qty[self.player].value:
|
|
||||||
power_bombs = self.multiworld.power_bomb_qty[self.player].value
|
|
||||||
missiles = self.multiworld.missile_qty[self.player].value
|
|
||||||
super_missiles = self.multiworld.super_qty[self.player].value
|
|
||||||
roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles)
|
|
||||||
if roll <= power_bombs:
|
|
||||||
return "Power Bomb"
|
|
||||||
elif roll <= power_bombs + missiles:
|
|
||||||
return "Missile"
|
|
||||||
else:
|
|
||||||
return "Super Missile"
|
|
||||||
else:
|
|
||||||
return "Nothing"
|
|
||||||
|
|
||||||
def pre_fill(self):
|
|
||||||
if len(self.NothingPool) > 0:
|
|
||||||
nonChozoLoc = []
|
|
||||||
chozoLoc = []
|
|
||||||
|
|
||||||
for loc in self.locations.values():
|
|
||||||
if loc.item is None:
|
|
||||||
if locationsDict[loc.name].isChozo():
|
|
||||||
chozoLoc.append(loc)
|
|
||||||
else:
|
|
||||||
nonChozoLoc.append(loc)
|
|
||||||
|
|
||||||
self.multiworld.random.shuffle(nonChozoLoc)
|
|
||||||
self.multiworld.random.shuffle(chozoLoc)
|
|
||||||
missingCount = len(self.NothingPool) - len(nonChozoLoc)
|
|
||||||
locations = nonChozoLoc
|
|
||||||
if (missingCount > 0):
|
|
||||||
locations += chozoLoc[:missingCount]
|
|
||||||
locations = locations[:len(self.NothingPool)]
|
|
||||||
for item, loc in zip(self.NothingPool, locations):
|
|
||||||
loc.place_locked_item(item)
|
|
||||||
loc.address = loc.item.code = None
|
|
||||||
|
|
||||||
def post_fill(self):
|
|
||||||
self.itemLocs = [
|
|
||||||
ItemLocation(ItemManager.Items[itemLoc.item.type
|
|
||||||
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
|
|
||||||
'ArchipelagoItem'],
|
|
||||||
locationsDict[itemLoc.name], itemLoc.item.player, True)
|
|
||||||
for itemLoc in self.multiworld.get_locations(self.player)
|
|
||||||
]
|
|
||||||
self.progItemLocs = [
|
|
||||||
ItemLocation(ItemManager.Items[itemLoc.item.type
|
|
||||||
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
|
|
||||||
'ArchipelagoItem'],
|
|
||||||
locationsDict[itemLoc.name], itemLoc.item.player, True)
|
|
||||||
for itemLoc in self.multiworld.get_locations(self.player) if itemLoc.item.advancement
|
|
||||||
]
|
|
||||||
for itemLoc in self.itemLocs:
|
|
||||||
if itemLoc.Item.Class == "Boss":
|
|
||||||
itemLoc.Item.Class = "Minor"
|
|
||||||
for itemLoc in self.progItemLocs:
|
|
||||||
if itemLoc.Item.Class == "Boss":
|
|
||||||
itemLoc.Item.Class = "Minor"
|
|
||||||
|
|
||||||
localItemLocs = [il for il in self.itemLocs if il.player == self.player]
|
|
||||||
localprogItemLocs = [il for il in self.progItemLocs if il.player == self.player]
|
|
||||||
|
|
||||||
escapeTrigger = (localItemLocs, localprogItemLocs, 'Full') if self.variaRando.randoExec.randoSettings.restrictions["EscapeTrigger"] else None
|
|
||||||
escapeOk = self.variaRando.randoExec.graphBuilder.escapeGraph(self.variaRando.container, self.variaRando.randoExec.areaGraph, self.variaRando.randoExec.randoSettings.maxDiff, escapeTrigger)
|
|
||||||
assert escapeOk, "Could not find a solution for escape"
|
|
||||||
|
|
||||||
self.variaRando.doors = GraphUtils.getDoorConnections(self.variaRando.randoExec.areaGraph,
|
|
||||||
self.variaRando.args.area, self.variaRando.args.bosses,
|
|
||||||
self.variaRando.args.escapeRando)
|
|
||||||
|
|
||||||
self.variaRando.randoExec.postProcessItemLocs(self.itemLocs, self.variaRando.args.hideItems)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def stage_post_fill(cls, world):
|
|
||||||
new_state = CollectionState(world)
|
|
||||||
progitempool = []
|
|
||||||
for item in world.itempool:
|
|
||||||
if item.game == "Super Metroid" and item.advancement:
|
|
||||||
progitempool.append(item)
|
|
||||||
|
|
||||||
for item in progitempool:
|
|
||||||
new_state.collect(item, True)
|
|
||||||
|
|
||||||
bossesLoc = ['Draygon', 'Kraid', 'Ridley', 'Phantoon', 'Mother Brain']
|
|
||||||
for player in world.get_game_players("Super Metroid"):
|
|
||||||
for bossLoc in bossesLoc:
|
|
||||||
if not world.get_location(bossLoc, player).can_reach(new_state):
|
|
||||||
world.state.smbm[player].onlyBossLeft = True
|
|
||||||
break
|
|
||||||
|
|
||||||
def write_spoiler(self, spoiler_handle: TextIO):
|
def write_spoiler(self, spoiler_handle: TextIO):
|
||||||
if self.multiworld.area_randomization[self.player].value != 0:
|
if self.multiworld.area_randomization[self.player].value != 0:
|
||||||
spoiler_handle.write('\n\nArea Transitions:\n\n')
|
spoiler_handle.write('\n\nArea Transitions:\n\n')
|
||||||
|
@ -793,39 +866,18 @@ class SMWorld(World):
|
||||||
'<=>',
|
'<=>',
|
||||||
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss]))
|
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss]))
|
||||||
|
|
||||||
def create_locations(self, player: int):
|
|
||||||
for name in locationsDict:
|
|
||||||
self.locations[name] = SMLocation(player, name, self.location_name_to_id.get(name, None))
|
|
||||||
|
|
||||||
|
|
||||||
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
|
||||||
ret = Region(name, player, world)
|
|
||||||
if locations:
|
|
||||||
for loc in locations:
|
|
||||||
location = self.locations[loc]
|
|
||||||
location.parent_region = ret
|
|
||||||
ret.locations.append(location)
|
|
||||||
if exits:
|
|
||||||
for exit in exits:
|
|
||||||
ret.exits.append(Entrance(player, exit, ret))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class SMLocation(Location):
|
class SMLocation(Location):
|
||||||
game: str = "Super Metroid"
|
game: str = "Super Metroid"
|
||||||
|
|
||||||
def __init__(self, player: int, name: str, address=None, parent=None):
|
def __init__(self, player: int, name: str, address=None, parent=None):
|
||||||
super(SMLocation, self).__init__(player, name, address, parent)
|
super(SMLocation, self).__init__(player, name, address, parent)
|
||||||
|
|
||||||
def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool:
|
|
||||||
return self.always_allow(state, item) or (self.item_rule(item) and (not check_access or self.can_reach(state)))
|
|
||||||
|
|
||||||
def can_reach(self, state: CollectionState) -> bool:
|
def can_reach(self, state: CollectionState) -> bool:
|
||||||
# self.access_rule computes faster on average, so placing it first for faster abort
|
# self.access_rule computes faster on average, so placing it first for faster abort
|
||||||
assert self.parent_region, "Can't reach location without region"
|
assert self.parent_region, "Can't reach location without region"
|
||||||
return self.access_rule(state) and \
|
return super(SMLocation, self).can_reach(state) and \
|
||||||
self.parent_region.can_reach(state) and \
|
(not state.multiworld.worlds[self.player].need_comeback_check or \
|
||||||
self.can_comeback(state, self.item)
|
self.can_comeback(state, self.item))
|
||||||
|
|
||||||
def can_comeback(self, state: CollectionState, item):
|
def can_comeback(self, state: CollectionState, item):
|
||||||
randoExec = state.multiworld.worlds[self.player].variaRando.randoExec
|
randoExec = state.multiworld.worlds[self.player].variaRando.randoExec
|
||||||
|
|
Loading…
Reference in New Issue