2021-11-12 13:00:11 +00:00
import logging
import copy
import os
import threading
2021-12-02 05:11:42 +00:00
from typing import Set , List
2021-11-12 13:00:11 +00:00
logger = logging . getLogger ( " Super Metroid " )
from . Locations import lookup_name_to_id as locations_lookup_name_to_id
from . Items import lookup_name_to_id as items_lookup_name_to_id
from . Regions import create_regions
from . Rules import set_rules , add_entrance_rule
from . Options import sm_options
2021-11-13 14:40:20 +00:00
from . Rom import get_base_rom_path , ROM_PLAYER_LIMIT
2021-11-12 13:00:11 +00:00
import Utils
from BaseClasses import Region , Entrance , Location , MultiWorld , Item , RegionType , CollectionState
from . . AutoWorld import World
import Patch
from logic . smboolmanager import SMBoolManager
from graph . vanilla . graph_locations import locationsDict
from graph . graph_utils import getAccessPoint
from rando . ItemLocContainer import ItemLocation
from rando . Items import ItemManager
from utils . parameters import *
from logic . logic import Logic
from randomizer import VariaRandomizer
class SMWorld ( World ) :
game : str = " Super Metroid "
topology_present = True
2021-11-12 13:36:34 +00:00
data_version = 1
2021-11-12 13:00:11 +00:00
options = sm_options
item_names : Set [ str ] = frozenset ( items_lookup_name_to_id )
location_names : Set [ str ] = frozenset ( locations_lookup_name_to_id )
item_name_to_id = items_lookup_name_to_id
location_name_to_id = locations_lookup_name_to_id
remote_items : bool = False
remote_start_inventory : bool = False
itemManager : ItemManager
locations = { }
2022-01-05 19:15:19 +00:00
hint_blacklist = { ' Nothing ' , ' NoEnergy ' }
2021-11-12 13:00:11 +00:00
Logic . factory ( ' vanilla ' )
def __init__ ( self , world : MultiWorld , player : int ) :
self . rom_name_available_event = threading . Event ( )
super ( ) . __init__ ( world , player )
def __new__ ( cls , world , player ) :
# Add necessary objects to CollectionState on initialization
orig_init = CollectionState . __init__
orig_copy = CollectionState . copy
def sm_init ( self , parent : MultiWorld ) :
if ( hasattr ( parent , " state " ) ) : # for unit tests where MultiWorld is instanciated before worlds
2021-12-02 05:11:42 +00:00
self . smbm = { player : SMBoolManager ( player , parent . state . smbm [ player ] . maxDiff , parent . state . smbm [ player ] . onlyBossLeft ) for player in parent . get_game_players ( " Super Metroid " ) }
2021-11-12 13:00:11 +00:00
orig_init ( self , parent )
def sm_copy ( self ) :
ret = orig_copy ( self )
ret . smbm = { player : copy . deepcopy ( self . smbm [ player ] ) for player in self . world . get_game_players ( " Super Metroid " ) }
return ret
CollectionState . __init__ = sm_init
CollectionState . copy = sm_copy
if world :
world . state . smbm = { }
return super ( ) . __new__ ( cls )
def generate_early ( self ) :
Logic . factory ( ' vanilla ' )
self . variaRando = VariaRandomizer ( self . world , get_base_rom_path ( ) , self . player )
self . world . state . smbm [ self . player ] = SMBoolManager ( self . player , self . variaRando . maxDifficulty )
# keeps Nothing items local so no player will ever pickup Nothing
# doing so reduces contribution of this world to the Multiworld the more Nothing there is though
self . world . local_items [ self . player ] . value . add ( ' Nothing ' )
2022-01-05 19:15:19 +00:00
self . world . local_items [ self . player ] . value . add ( ' NoEnergy ' )
2021-11-12 13:00:11 +00:00
if ( self . variaRando . args . morphPlacement == " early " ) :
self . world . local_items [ self . player ] . value . add ( ' Morph ' )
2021-12-02 05:11:42 +00:00
if ( len ( self . variaRando . randoExec . setup . restrictedLocs ) > 0 ) :
self . world . accessibility [ self . player ] = self . world . accessibility [ self . player ] . from_text ( " items " )
logger . warning ( f " accessibility forced to ' items ' for player { self . world . get_player_name ( self . player ) } because of ' fun ' settings " )
2021-11-12 13:00:11 +00:00
def generate_basic ( self ) :
itemPool = self . variaRando . container . itemPool
self . startItems = [ variaItem for item in self . world . precollected_items [ self . player ] for variaItem in ItemManager . Items . values ( ) if variaItem . Name == item . name ]
if self . world . start_inventory_removes_from_pool [ self . player ] :
for item in self . startItems :
if ( item in itemPool ) :
itemPool . remove ( item )
missingPool = 105 - len ( itemPool ) + 1
for i in range ( 1 , missingPool ) :
itemPool . append ( ItemManager . Items [ ' Nothing ' ] )
# Generate item pool
pool = [ ]
self . locked_items = { }
weaponCount = [ 0 , 0 , 0 ]
for item in itemPool :
isAdvancement = True
if item . Type == ' Missile ' :
if weaponCount [ 0 ] < 3 :
weaponCount [ 0 ] + = 1
else :
isAdvancement = False
elif item . Type == ' Super ' :
if weaponCount [ 1 ] < 2 :
weaponCount [ 1 ] + = 1
else :
isAdvancement = False
elif item . Type == ' PowerBomb ' :
if weaponCount [ 2 ] < 3 :
weaponCount [ 2 ] + = 1
else :
isAdvancement = False
2022-01-05 19:15:19 +00:00
elif item . Category == ' Nothing ' :
2021-11-12 13:00:11 +00:00
isAdvancement = False
itemClass = ItemManager . Items [ item . Type ] . Class
smitem = SMItem ( item . Name , isAdvancement , item . Type , None if itemClass == ' Boss ' else self . item_name_to_id [ item . Name ] , player = self . player )
if itemClass == ' Boss ' :
self . locked_items [ item . Name ] = smitem
else :
pool . append ( smitem )
self . world . itempool + = pool
for ( location , item ) in self . locked_items . items ( ) :
self . world . get_location ( location , self . player ) . place_locked_item ( item )
self . world . get_location ( location , self . player ) . address = None
startAP = self . world . get_entrance ( ' StartAP ' , self . player )
startAP . connect ( self . world . get_region ( self . variaRando . args . startLocation , self . player ) )
for src , dest in self . variaRando . randoExec . areaGraph . InterAreaTransitions :
src_region = self . world . get_region ( src . Name , self . player )
dest_region = self . world . get_region ( dest . Name , self . player )
src_region . exits . append ( Entrance ( self . player , src . Name + " -> " + dest . Name , src_region ) )
srcDestEntrance = self . world . get_entrance ( src . Name + " -> " + dest . Name , self . player )
srcDestEntrance . connect ( dest_region )
add_entrance_rule ( self . world . get_entrance ( src . Name + " -> " + dest . Name , self . player ) , self . player , getAccessPoint ( src . Name ) . traverse )
def set_rules ( self ) :
set_rules ( self . world , self . player )
def create_regions ( self ) :
create_locations ( self , self . player )
create_regions ( self , self . world , self . player )
2021-12-02 05:13:37 +00:00
def get_required_client_version ( self ) :
# changes to client DeathLink handling for 0.2.1
return max ( super ( SMWorld , self ) . get_required_client_version ( ) , ( 0 , 2 , 1 ) )
2021-11-12 13:00:11 +00:00
def getWord ( self , w ) :
return ( w & 0x00FF , ( w & 0xFF00 ) >> 8 )
2021-11-17 01:31:46 +00:00
def getWordArray ( self , w ) :
return [ w & 0x00FF , ( w & 0xFF00 ) >> 8 ]
2021-11-12 13:00:11 +00:00
# used for remote location Credits Spoiler of local items
class DummyLocation :
def __init__ ( self , name ) :
self . Name = name
def isBoss ( self ) :
return False
def convertToROMItemName ( self , itemName ) :
charMap = { " A " : 0x3CE0 ,
" B " : 0x3CE1 ,
" C " : 0x3CE2 ,
" D " : 0x3CE3 ,
" E " : 0x3CE4 ,
" F " : 0x3CE5 ,
" G " : 0x3CE6 ,
" H " : 0x3CE7 ,
" I " : 0x3CE8 ,
" J " : 0x3CE9 ,
" K " : 0x3CEA ,
" L " : 0x3CEB ,
" M " : 0x3CEC ,
" N " : 0x3CED ,
" O " : 0x3CEE ,
" P " : 0x3CEF ,
" Q " : 0x3CF0 ,
" R " : 0x3CF1 ,
" S " : 0x3CF2 ,
" T " : 0x3CF3 ,
" U " : 0x3CF4 ,
" V " : 0x3CF5 ,
" W " : 0x3CF6 ,
" X " : 0x3CF7 ,
" Y " : 0x3CF8 ,
" Z " : 0x3CF9 ,
" " : 0x3C4E ,
" ! " : 0x3CFF ,
" ? " : 0x3CFE ,
" ' " : 0x3CFD ,
" , " : 0x3CFB ,
" . " : 0x3CFA ,
" - " : 0x3CCF ,
" _ " : 0x000E ,
" 1 " : 0x3C00 ,
" 2 " : 0x3C01 ,
" 3 " : 0x3C02 ,
" 4 " : 0x3C03 ,
" 5 " : 0x3C04 ,
" 6 " : 0x3C05 ,
" 7 " : 0x3C06 ,
" 8 " : 0x3C07 ,
" 9 " : 0x3C08 ,
" 0 " : 0x3C09 ,
" % " : 0x3C0A }
data = [ ]
itemName = itemName . upper ( ) [ : 26 ]
itemName = itemName . strip ( )
itemName = itemName . center ( 26 , " " )
itemName = " ___ " + itemName + " ___ "
for char in itemName :
( w0 , w1 ) = self . getWord ( charMap . get ( char , 0x3C4E ) )
data . append ( w0 )
data . append ( w1 )
return data
def APPatchRom ( self , romPatcher ) :
multiWorldLocations = { }
multiWorldItems = { }
idx = 0
itemId = 0
2021-11-17 01:31:46 +00:00
self . playerIDMap = { }
playerIDCount = 0 # 0 is for "Archipelago" server
2021-11-12 13:00:11 +00:00
for itemLoc in self . world . get_locations ( ) :
2021-11-17 01:31:46 +00:00
romPlayerID = itemLoc . item . player if itemLoc . item . player < = ROM_PLAYER_LIMIT else 0
2021-11-12 13:00:11 +00:00
if itemLoc . player == self . player and locationsDict [ itemLoc . name ] . Id != None :
if itemLoc . item . type in ItemManager . Items :
itemId = ItemManager . Items [ itemLoc . item . type ] . Id
else :
itemId = ItemManager . Items [ ' ArchipelagoItem ' ] . Id + idx
multiWorldItems [ 0x029EA3 + idx * 64 ] = self . convertToROMItemName ( itemLoc . item . name )
idx + = 1
2021-11-17 01:31:46 +00:00
if ( romPlayerID > 0 and romPlayerID not in self . playerIDMap . keys ( ) ) :
playerIDCount + = 1
self . playerIDMap [ romPlayerID ] = playerIDCount
2021-11-12 13:00:11 +00:00
( w0 , w1 ) = self . getWord ( 0 if itemLoc . item . player == self . player else 1 )
( w2 , w3 ) = self . getWord ( itemId )
2021-11-17 01:31:46 +00:00
( w4 , w5 ) = self . getWord ( romPlayerID )
2021-11-12 13:00:11 +00:00
( w6 , w7 ) = self . getWord ( 0 if itemLoc . item . advancement else 1 )
multiWorldLocations [ 0x1C6000 + locationsDict [ itemLoc . name ] . Id * 8 ] = [ w0 , w1 , w2 , w3 , w4 , w5 , w6 , w7 ]
2021-11-17 01:31:46 +00:00
if itemLoc . item . player == self . player :
if ( romPlayerID > 0 and romPlayerID not in self . playerIDMap . keys ( ) ) :
playerIDCount + = 1
self . playerIDMap [ romPlayerID ] = playerIDCount
2021-11-12 13:00:11 +00:00
itemSprites = [ " off_world_prog_item.bin " , " off_world_item.bin " ]
idx = 0
offworldSprites = { }
for fileName in itemSprites :
with open ( Utils . local_path ( " lib " , " worlds " , " sm " , " data " , " custom_sprite " , fileName ) if Utils . is_frozen ( ) else Utils . local_path ( " worlds " , " sm " , " data " , " custom_sprite " , fileName ) , ' rb ' ) as stream :
buffer = bytearray ( stream . read ( ) )
offworldSprites [ 0x027882 + 10 * ( 21 + idx ) + 2 ] = buffer [ 0 : 8 ]
offworldSprites [ 0x049100 + idx * 256 ] = buffer [ 8 : 264 ]
idx + = 1
openTourianGreyDoors = { 0x07C823 + 5 : [ 0x0C ] , 0x07C831 + 5 : [ 0x0C ] }
2021-12-03 21:11:25 +00:00
deathLink = { 0x277f04 : [ self . world . death_link [ self . player ] . value ] }
2021-11-17 01:31:46 +00:00
playerNames = { }
playerNameIDMap = { }
playerNames [ 0x1C5000 ] = " Archipelago " . upper ( ) . center ( 16 ) . encode ( )
playerNameIDMap [ 0x1C5800 ] = self . getWordArray ( 0 )
for key , value in self . playerIDMap . items ( ) :
playerNames [ 0x1C5000 + value * 16 ] = self . world . player_name [ key ] [ : 16 ] . upper ( ) . center ( 16 ) . encode ( )
playerNameIDMap [ 0x1C5800 + value * 2 ] = self . getWordArray ( key )
2021-11-12 13:00:11 +00:00
patchDict = { ' MultiWorldLocations ' : multiWorldLocations ,
' MultiWorldItems ' : multiWorldItems ,
' offworldSprites ' : offworldSprites ,
' openTourianGreyDoors ' : openTourianGreyDoors ,
2021-11-17 01:31:46 +00:00
' deathLink ' : deathLink ,
' PlayerName ' : playerNames ,
' PlayerNameIDMap ' : playerNameIDMap }
2021-11-12 13:00:11 +00:00
romPatcher . applyIPSPatchDict ( patchDict )
# set rom name
# 21 bytes
from Main import __version__
self . romName = bytearray ( f ' SM { __version__ . replace ( " . " , " " ) [ 0 : 3 ] } _ { self . player } _ { self . world . seed : 11 } \0 ' , ' utf8 ' ) [ : 21 ]
self . romName . extend ( [ 0 ] * ( 21 - len ( self . romName ) ) )
romPatcher . applyIPSPatch ( ' ROMName ' , { ' ROMName ' : { 0x1C4F00 : self . romName , 0x007FC0 : self . romName } } )
startItemROMAddressBase = 0x2FD8B9
# current, base value or bitmask, max, base value or bitmask
startItemROMDict = { ' ETank ' : [ 0x8 , 0x64 , 0xA , 0x64 ] ,
' Missile ' : [ 0xC , 0x5 , 0xE , 0x5 ] ,
' Super ' : [ 0x10 , 0x5 , 0x12 , 0x5 ] ,
' PowerBomb ' : [ 0x14 , 0x5 , 0x16 , 0x5 ] ,
' Reserve ' : [ 0x1A , 0x64 , 0x18 , 0x64 ] ,
' Morph ' : [ 0x2 , 0x4 , 0x0 , 0x4 ] ,
' Bomb ' : [ 0x3 , 0x10 , 0x1 , 0x10 ] ,
' SpringBall ' : [ 0x2 , 0x2 , 0x0 , 0x2 ] ,
' HiJump ' : [ 0x3 , 0x1 , 0x1 , 0x1 ] ,
' Varia ' : [ 0x2 , 0x1 , 0x0 , 0x1 ] ,
' Gravity ' : [ 0x2 , 0x20 , 0x0 , 0x20 ] ,
' SpeedBooster ' : [ 0x3 , 0x20 , 0x1 , 0x20 ] ,
' SpaceJump ' : [ 0x3 , 0x2 , 0x1 , 0x2 ] ,
' ScrewAttack ' : [ 0x2 , 0x8 , 0x0 , 0x8 ] ,
' Charge ' : [ 0x7 , 0x10 , 0x5 , 0x10 ] ,
' Ice ' : [ 0x6 , 0x2 , 0x4 , 0x2 ] ,
' Wave ' : [ 0x6 , 0x1 , 0x4 , 0x1 ] ,
' Spazer ' : [ 0x6 , 0x4 , 0x4 , 0x4 ] ,
' Plasma ' : [ 0x6 , 0x8 , 0x4 , 0x8 ] ,
' Grapple ' : [ 0x3 , 0x40 , 0x1 , 0x40 ] ,
' XRayScope ' : [ 0x3 , 0x80 , 0x1 , 0x80 ]
}
mergedData = { }
hasETank = False
hasSpazer = False
hasPlasma = False
for startItem in self . startItems :
item = startItem . Type
if item == ' ETank ' : hasETank = True
if item == ' Spazer ' : hasSpazer = True
if item == ' Plasma ' : hasPlasma = True
if ( item in [ ' ETank ' , ' Missile ' , ' Super ' , ' PowerBomb ' , ' Reserve ' ] ) :
( currentValue , currentBase , maxValue , maxBase ) = startItemROMDict [ item ]
if ( startItemROMAddressBase + currentValue ) in mergedData :
mergedData [ startItemROMAddressBase + currentValue ] + = currentBase
mergedData [ startItemROMAddressBase + maxValue ] + = maxBase
else :
mergedData [ startItemROMAddressBase + currentValue ] = currentBase
mergedData [ startItemROMAddressBase + maxValue ] = maxBase
else :
( collected , currentBitmask , equipped , maxBitmask ) = startItemROMDict [ item ]
if ( startItemROMAddressBase + collected ) in mergedData :
mergedData [ startItemROMAddressBase + collected ] | = currentBitmask
mergedData [ startItemROMAddressBase + equipped ] | = maxBitmask
else :
mergedData [ startItemROMAddressBase + collected ] = currentBitmask
mergedData [ startItemROMAddressBase + equipped ] = maxBitmask
if hasETank :
mergedData [ startItemROMAddressBase + 0x8 ] + = 99
mergedData [ startItemROMAddressBase + 0xA ] + = 99
if hasSpazer and hasPlasma :
mergedData [ startItemROMAddressBase + 0x4 ] & = ~ 0x4
for key , value in mergedData . items ( ) :
if ( key - startItemROMAddressBase > 7 ) :
( w0 , w1 ) = self . getWord ( value )
mergedData [ key ] = [ w0 , w1 ]
else :
mergedData [ key ] = [ value ]
startItemPatch = { ' startItemPatch ' : mergedData }
romPatcher . applyIPSPatch ( ' startItemPatch ' , startItemPatch )
romPatcher . commitIPS ( )
itemLocs = [ ItemLocation ( ItemManager . Items [ itemLoc . item . type if itemLoc . item . type in ItemManager . Items else ' ArchipelagoItem ' ] , locationsDict [ itemLoc . name ] , True ) for itemLoc in self . world . get_locations ( ) if itemLoc . player == self . player ]
romPatcher . writeItemsLocs ( itemLocs )
itemLocs = [ ItemLocation ( ItemManager . Items [ itemLoc . item . type ] , locationsDict [ itemLoc . name ] if itemLoc . name in locationsDict and itemLoc . player == self . player else self . DummyLocation ( self . world . get_player_name ( itemLoc . player ) + " " + itemLoc . name ) , True ) for itemLoc in self . world . get_locations ( ) if itemLoc . item . player == self . player ]
progItemLocs = [ ItemLocation ( ItemManager . Items [ itemLoc . item . type ] , locationsDict [ itemLoc . name ] if itemLoc . name in locationsDict and itemLoc . player == self . player else self . DummyLocation ( self . world . get_player_name ( itemLoc . player ) + " " + itemLoc . name ) , True ) for itemLoc in self . world . get_locations ( ) if itemLoc . item . player == self . player and itemLoc . item . advancement == True ]
# progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.world.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True]
# romPatcher.writeSplitLocs(self.variaRando.args.majorsSplit, itemLocs, progItemLocs)
romPatcher . writeSpoiler ( itemLocs , progItemLocs )
romPatcher . writeRandoSettings ( self . variaRando . randoExec . randoSettings , itemLocs )
def generate_output ( self , output_directory : str ) :
try :
outfilebase = ' AP_ ' + self . world . seed_name
outfilepname = f ' _P { self . player } '
outfilepname + = f " _ { self . world . player_name [ self . player ] . replace ( ' ' , ' _ ' ) } " \
outputFilename = os . path . join ( output_directory , f ' { outfilebase } { outfilepname } .sfc ' )
self . variaRando . PatchRom ( outputFilename , self . APPatchRom )
self . write_crc ( outputFilename )
Patch . create_patch_file ( outputFilename , player = self . player , player_name = self . world . player_name [ self . player ] , game = Patch . GAME_SM )
os . unlink ( outputFilename )
self . rom_name = self . romName
except :
raise
finally :
self . rom_name_available_event . set ( ) # make sure threading continues and errors are collected
def checksum_mirror_sum ( self , start , length , mask = 0x800000 ) :
while ( not ( length & mask ) and mask ) :
mask >> = 1
part1 = sum ( start [ : mask ] ) & 0xFFFF
part2 = 0
next_length = length - mask
if next_length :
part2 = self . checksum_mirror_sum ( start [ mask : ] , next_length , mask >> 1 )
while ( next_length < mask ) :
next_length + = next_length
part2 + = part2
length = mask + mask
return ( part1 + part2 ) & 0xFFFF
def write_bytes ( self , buffer , startaddress : int , values ) :
buffer [ startaddress : startaddress + len ( values ) ] = values
def write_crc ( self , romName ) :
with open ( romName , ' rb ' ) as stream :
buffer = bytearray ( stream . read ( ) )
crc = self . checksum_mirror_sum ( buffer , len ( buffer ) )
inv = crc ^ 0xFFFF
self . write_bytes ( buffer , 0x7FDC , [ inv & 0xFF , ( inv >> 8 ) & 0xFF , crc & 0xFF , ( crc >> 8 ) & 0xFF ] )
with open ( romName , ' wb ' ) as outfile :
outfile . write ( buffer )
def modify_multidata ( self , multidata : dict ) :
import base64
# wait for self.rom_name to be available.
self . rom_name_available_event . wait ( )
rom_name = getattr ( self , " rom_name " , None )
# we skip in case of error, so that the original error in the output thread is the one that gets raised
if rom_name :
new_name = base64 . b64encode ( bytes ( self . rom_name ) ) . decode ( )
payload = multidata [ " connect_names " ] [ self . world . player_name [ self . player ] ]
multidata [ " connect_names " ] [ new_name ] = payload
del ( multidata [ " connect_names " ] [ self . world . player_name [ self . player ] ] )
def fill_slot_data ( self ) :
slot_data = { }
return slot_data
def collect ( self , state : CollectionState , item : Item ) - > bool :
2022-02-05 14:49:19 +00:00
state . smbm [ self . player ] . addItem ( item . type )
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 )
2021-11-12 13:00:11 +00:00
def create_item ( self , name : str ) - > Item :
item = next ( x for x in ItemManager . Items . values ( ) if x . Name == name )
return SMItem ( item . Name , True , item . Type , self . item_name_to_id [ item . Name ] , player = self . player )
def pre_fill ( self ) :
if ( self . variaRando . args . morphPlacement == " early " ) and next ( ( item for item in self . world . itempool if item . player == self . player and item . name == " Morph Ball " ) , False ) :
viable = [ ]
for location in self . world . get_locations ( ) :
if location . player == self . player \
and location . item is None \
and location . can_reach ( self . world . state ) :
viable . append ( location )
self . world . random . shuffle ( viable )
key = self . world . create_item ( " Morph Ball " , self . player )
loc = viable . pop ( )
loc . place_locked_item ( key )
self . world . itempool [ : ] = [ item for item in self . world . itempool if
item . player != self . player or
item . name != " Morph Ball " ]
@classmethod
def stage_fill_hook ( cls , world , progitempool , nonexcludeditempool , localrestitempool , nonlocalrestitempool ,
restitempool , fill_locations ) :
if world . get_game_players ( " Super Metroid " ) :
progitempool . sort (
key = lambda item : 1 if ( item . name == ' Morph Ball ' ) else 0 )
2021-12-02 05:11:42 +00:00
def post_fill ( self ) :
new_state = CollectionState ( self . world )
progitempool = [ ]
for item in self . world . itempool :
if item . player == self . player and item . advancement :
progitempool . append ( item )
for item in progitempool :
new_state . collect ( item , True )
bossesLoc = [ ' Draygon ' , ' Kraid ' , ' Ridley ' , ' Phantoon ' , ' Mother Brain ' ]
for bossLoc in bossesLoc :
if ( not self . world . get_location ( bossLoc , self . player ) . can_reach ( new_state ) ) :
self . world . state . smbm [ self . player ] . onlyBossLeft = True
break
2021-11-12 13:00:11 +00:00
def create_locations ( self , player : int ) :
for name , id in locations_lookup_name_to_id . items ( ) :
self . locations [ name ] = SMLocation ( player , name , id )
def create_region ( self , world : MultiWorld , player : int , name : str , locations = None , exits = None ) :
ret = Region ( name , RegionType . LightWorld , name , player )
ret . world = 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 ) :
game : str = " Super Metroid "
def __init__ ( self , player : int , name : str , address = None , parent = None ) :
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 ) ) )
class SMItem ( Item ) :
game = " Super Metroid "
def __init__ ( self , name , advancement , type , code , player : int = None ) :
super ( SMItem , self ) . __init__ ( name , advancement , code , player )
self . type = type