2022-03-15 08:55:57 -04:00
from enum import Enum
from logging import exception
from typing import Any , Callable , List , Sequence
import random
import typing
from BaseClasses import Location
from worlds . smz3 . TotalSMZ3 . Item import Item , ItemType
from worlds . smz3 . TotalSMZ3 . Location import LocationType
from worlds . smz3 . TotalSMZ3 . Region import IMedallionAccess , IReward , RewardType , SMRegion , Z3Region
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . EasternPalace import EasternPalace
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . DesertPalace import DesertPalace
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . TowerOfHera import TowerOfHera
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . PalaceOfDarkness import PalaceOfDarkness
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . SwampPalace import SwampPalace
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . SkullWoods import SkullWoods
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . ThievesTown import ThievesTown
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . IcePalace import IcePalace
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . MiseryMire import MiseryMire
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . TurtleRock import TurtleRock
from worlds . smz3 . TotalSMZ3 . Regions . Zelda . GanonsTower import GanonsTower
from worlds . smz3 . TotalSMZ3 . Text . StringTable import StringTable
from worlds . smz3 . TotalSMZ3 . World import World
from worlds . smz3 . TotalSMZ3 . Config import Config , GameMode , GanonInvincible
from worlds . smz3 . TotalSMZ3 . Text . Texts import Texts
from worlds . smz3 . TotalSMZ3 . Text . Dialog import Dialog
class KeycardPlaque :
Level1 = 0xe0
Level2 = 0xe1
Boss = 0xe2
Null = 0x00
class KeycardDoors :
Left = 0xd414
Right = 0xd41a
Up = 0xd420
Down = 0xd426
BossLeft = 0xc842
BossRight = 0xc848
class KeycardEvents :
CrateriaLevel1 = 0x0000
CrateriaLevel2 = 0x0100
CrateriaBoss = 0x0200
BrinstarLevel1 = 0x0300
BrinstarLevel2 = 0x0400
BrinstarBoss = 0x0500
NorfairLevel1 = 0x0600
NorfairLevel2 = 0x0700
NorfairBoss = 0x0800
MaridiaLevel1 = 0x0900
MaridiaLevel2 = 0x0a00
MaridiaBoss = 0x0b00
WreckedShipLevel1 = 0x0c00
WreckedShipBoss = 0x0d00
LowerNorfairLevel1 = 0x0e00
LowerNorfairBoss = 0x0f00
class DropPrize ( Enum ) :
Heart = 0xD8
Green = 0xD9
Blue = 0xDA
Red = 0xDB
Bomb1 = 0xDC
Bomb4 = 0xDD
Bomb8 = 0xDE
Magic = 0xDF
FullMagic = 0xE0
Arrow5 = 0xE1
Arrow10 = 0xE2
Fairy = 0xE3
class Patch :
Major = 0
Minor = 1
allWorlds : List [ World ]
myWorld : World
seedGuid : str
seed : int
rnd : random . Random
patches : Sequence [ Any ]
stringTable : StringTable
silversWorldID : int
def __init__ ( self , myWorld : World , allWorlds : List [ World ] , seedGuid : str , seed : int , rnd : random . Random , playerNames : List [ str ] , silversWorldID : int ) :
self . myWorld = myWorld
self . allWorlds = allWorlds
self . seedGuid = seedGuid
self . seed = seed
self . rnd = rnd
self . playerNames = playerNames
self . playerIDToNames = { id : name for name , id in playerNames . items ( ) }
self . silversWorldID = silversWorldID
def Create ( self , config : Config ) :
self . stringTable = StringTable ( )
self . patches = [ ]
self . title = " "
self . WriteMedallions ( )
self . WriteRewards ( )
self . WriteDungeonMusic ( config . Keysanity )
self . WriteDiggingGameRng ( )
self . WritePrizeShuffle ( )
self . WriteRemoveEquipmentFromUncle ( self . myWorld . GetLocation ( " Link ' s Uncle " ) . APLocation . item . item if
self . myWorld . GetLocation ( " Link ' s Uncle " ) . APLocation . item . game == " SMZ3 " else
Item ( ItemType . Something ) )
self . WriteGanonInvicible ( config . GanonInvincible )
self . WriteRngBlock ( )
self . WriteSaveAndQuitFromBossRoom ( )
self . WriteWorldOnAgahnimDeath ( )
self . WriteTexts ( config )
self . WriteSMLocations ( [ loc for region in self . myWorld . Regions for loc in region . Locations if isinstance ( region , SMRegion ) ] )
self . WriteZ3Locations ( [ loc for region in self . myWorld . Regions for loc in region . Locations if isinstance ( region , Z3Region ) ] )
self . WriteStringTable ( )
self . WriteSMKeyCardDoors ( )
self . WriteZ3KeysanityFlags ( )
self . WritePlayerNames ( )
self . WriteSeedData ( )
self . WriteGameTitle ( )
self . WriteCommonFlags ( )
return { patch [ 0 ] : patch [ 1 ] for patch in self . patches }
def WriteMedallions ( self ) :
turtleRock = next ( region for region in self . myWorld . Regions if isinstance ( region , TurtleRock ) )
miseryMire = next ( region for region in self . myWorld . Regions if isinstance ( region , MiseryMire ) )
turtleRockAddresses = [ 0x308023 , 0xD020 , 0xD0FF , 0xD1DE ]
miseryMireAddresses = [ 0x308022 , 0xCFF2 , 0xD0D1 , 0xD1B0 ]
if turtleRock . Medallion == ItemType . Bombos :
turtleRockValues = [ 0x00 , 0x51 , 0x10 , 0x00 ]
elif turtleRock . Medallion == ItemType . Ether :
turtleRockValues = [ 0x01 , 0x51 , 0x18 , 0x00 ]
elif turtleRock . Medallion == ItemType . Quake :
turtleRockValues = [ 0x02 , 0x14 , 0xEF , 0xC4 ]
else :
raise exception ( f " Tried using { turtleRock . Medallion } in place of Turtle Rock medallion " )
if miseryMire . Medallion == ItemType . Bombos :
miseryMireValues = [ 0x00 , 0x51 , 0x00 , 0x00 ]
elif miseryMire . Medallion == ItemType . Ether :
miseryMireValues = [ 0x01 , 0x13 , 0x9F , 0xF1 ]
elif miseryMire . Medallion == ItemType . Quake :
miseryMireValues = [ 0x02 , 0x51 , 0x08 , 0x00 ]
else :
raise exception ( f " Tried using { miseryMire . Medallion } in place of Misery Mire medallion " )
self . patches + = [ ( Snes ( addr ) , [ value ] ) for addr , value in zip ( turtleRockAddresses , turtleRockValues ) ]
self . patches + = [ ( Snes ( addr ) , [ value ] ) for addr , value in zip ( miseryMireAddresses , miseryMireValues ) ]
def WriteRewards ( self ) :
crystalsBlue = [ 1 , 2 , 3 , 4 , 7 ]
self . rnd . shuffle ( crystalsBlue )
crystalsRed = [ 5 , 6 ]
self . rnd . shuffle ( crystalsRed )
crystalRewards = crystalsBlue + crystalsRed
pendantsGreen = [ 1 ]
pendantsBlueRed = [ 2 , 3 ]
self . rnd . shuffle ( pendantsBlueRed )
pendantRewards = pendantsGreen + pendantsBlueRed
regions = [ region for region in self . myWorld . Regions if isinstance ( region , IReward ) ]
crystalRegions = [ region for region in regions if region . Reward == RewardType . CrystalBlue ] + [ region for region in regions if region . Reward == RewardType . CrystalRed ]
pendantRegions = [ region for region in regions if region . Reward == RewardType . PendantGreen ] + [ region for region in regions if region . Reward == RewardType . PendantNonGreen ]
self . patches + = self . RewardPatches ( crystalRegions , crystalRewards , self . CrystalValues )
self . patches + = self . RewardPatches ( pendantRegions , pendantRewards , self . PendantValues )
def RewardPatches ( self , regions : List [ IReward ] , rewards : List [ int ] , rewardValues : Callable ) :
addresses = [ self . RewardAddresses ( region ) for region in regions ]
values = [ rewardValues ( reward ) for reward in rewards ]
associations = zip ( addresses , values )
return [ ( Snes ( i ) , [ b ] ) for association in associations for i , b in zip ( association [ 0 ] , association [ 1 ] ) ]
def RewardAddresses ( self , region : IReward ) :
regionType = {
EasternPalace : [ 0x2A09D , 0xABEF8 , 0xABEF9 , 0x308052 , 0x30807C , 0x1C6FE ] ,
DesertPalace : [ 0x2A09E , 0xABF1C , 0xABF1D , 0x308053 , 0x308078 , 0x1C6FF ] ,
TowerOfHera : [ 0x2A0A5 , 0xABF0A , 0xABF0B , 0x30805A , 0x30807A , 0x1C706 ] ,
PalaceOfDarkness : [ 0x2A0A1 , 0xABF00 , 0xABF01 , 0x308056 , 0x30807D , 0x1C702 ] ,
SwampPalace : [ 0x2A0A0 , 0xABF6C , 0xABF6D , 0x308055 , 0x308071 , 0x1C701 ] ,
SkullWoods : [ 0x2A0A3 , 0xABF12 , 0xABF13 , 0x308058 , 0x30807B , 0x1C704 ] ,
ThievesTown : [ 0x2A0A6 , 0xABF36 , 0xABF37 , 0x30805B , 0x308077 , 0x1C707 ] ,
IcePalace : [ 0x2A0A4 , 0xABF5A , 0xABF5B , 0x308059 , 0x308073 , 0x1C705 ] ,
MiseryMire : [ 0x2A0A2 , 0xABF48 , 0xABF49 , 0x308057 , 0x308075 , 0x1C703 ] ,
TurtleRock : [ 0x2A0A7 , 0xABF24 , 0xABF25 , 0x30805C , 0x308079 , 0x1C708 ]
}
result = regionType . get ( type ( region ) , None )
if result is None :
raise exception ( f " Region { region } should not be a dungeon reward region " )
else :
return result
def CrystalValues ( self , crystal : int ) :
crystalMap = {
1 : [ 0x02 , 0x34 , 0x64 , 0x40 , 0x7F , 0x06 ] ,
2 : [ 0x10 , 0x34 , 0x64 , 0x40 , 0x79 , 0x06 ] ,
3 : [ 0x40 , 0x34 , 0x64 , 0x40 , 0x6C , 0x06 ] ,
4 : [ 0x20 , 0x34 , 0x64 , 0x40 , 0x6D , 0x06 ] ,
5 : [ 0x04 , 0x32 , 0x64 , 0x40 , 0x6E , 0x06 ] ,
6 : [ 0x01 , 0x32 , 0x64 , 0x40 , 0x6F , 0x06 ] ,
7 : [ 0x08 , 0x34 , 0x64 , 0x40 , 0x7C , 0x06 ] ,
}
result = crystalMap . get ( crystal , None )
if result is None :
raise exception ( f " Tried using { crystal } as a crystal number " )
else :
return result
def PendantValues ( self , pendant : int ) :
pendantMap = {
1 : [ 0x04 , 0x38 , 0x62 , 0x00 , 0x69 , 0x01 ] ,
2 : [ 0x01 , 0x32 , 0x60 , 0x00 , 0x69 , 0x03 ] ,
3 : [ 0x02 , 0x34 , 0x60 , 0x00 , 0x69 , 0x02 ] ,
}
result = pendantMap . get ( pendant , None )
if result is None :
raise exception ( f " Tried using { pendant } as a pendant number " )
else :
return result
def WriteSMLocations ( self , locations : List [ Location ] ) :
def GetSMItemPLM ( location : Location ) :
itemMap = {
ItemType . ETank : 0xEED7 ,
ItemType . Missile : 0xEEDB ,
ItemType . Super : 0xEEDF ,
ItemType . PowerBomb : 0xEEE3 ,
ItemType . Bombs : 0xEEE7 ,
ItemType . Charge : 0xEEEB ,
ItemType . Ice : 0xEEEF ,
ItemType . HiJump : 0xEEF3 ,
ItemType . SpeedBooster : 0xEEF7 ,
ItemType . Wave : 0xEEFB ,
ItemType . Spazer : 0xEEFF ,
ItemType . SpringBall : 0xEF03 ,
ItemType . Varia : 0xEF07 ,
ItemType . Plasma : 0xEF13 ,
ItemType . Grapple : 0xEF17 ,
ItemType . Morph : 0xEF23 ,
ItemType . ReserveTank : 0xEF27 ,
ItemType . Gravity : 0xEF0B ,
ItemType . XRay : 0xEF0F ,
ItemType . SpaceJump : 0xEF1B ,
ItemType . ScrewAttack : 0xEF1F
}
plmId = 0xEFE0 if self . myWorld . Config . GameMode == GameMode . Multiworld else \
itemMap . get ( location . APLocation . item . item . Type , 0xEFE0 )
if ( plmId == 0xEFE0 ) :
plmId + = 4 if location . Type == LocationType . Chozo else 8 if location . Type == LocationType . Hidden else 0
else :
plmId + = 0x54 if location . Type == LocationType . Chozo else 0xA8 if location . Type == LocationType . Hidden else 0
return plmId
for location in locations :
if ( self . myWorld . Config . GameMode == GameMode . Multiworld ) :
self . patches . append ( ( Snes ( location . Address ) , getWordArray ( GetSMItemPLM ( location ) ) ) )
self . patches . append ( self . ItemTablePatch ( location , self . GetZ3ItemId ( location ) ) )
else :
plmId = GetSMItemPLM ( location )
self . patches . append ( ( Snes ( location . Address ) , getWordArray ( plmId ) ) )
if ( plmId > = 0xEFE0 ) :
self . patches . append ( ( Snes ( location . Address + 5 ) , [ self . GetZ3ItemId ( location ) ] ) )
def WriteZ3Locations ( self , locations : List [ Location ] ) :
for location in locations :
if ( location . Type == LocationType . HeraStandingKey ) :
self . patches . append ( ( Snes ( 0x9E3BB ) , [ 0xE4 ] if location . APLocation . item . game == " SMZ3 " and location . APLocation . item . item . Type == ItemType . KeyTH else [ 0xEB ] ) )
elif ( location . Type in [ LocationType . Pedestal , LocationType . Ether , LocationType . Bombos ] ) :
text = Texts . ItemTextbox ( location . APLocation . item . item if location . APLocation . item . game == " SMZ3 " else Item ( ItemType . Something ) )
dialog = Dialog . Simple ( text )
if ( location . Type == LocationType . Pedestal ) :
self . stringTable . SetPedestalText ( text )
self . patches . append ( ( Snes ( 0x308300 ) , dialog ) )
elif ( location . Type == LocationType . Ether ) :
self . stringTable . SetEtherText ( text )
self . patches . append ( ( Snes ( 0x308F00 ) , dialog ) )
elif ( location . Type == LocationType . Bombos ) :
self . stringTable . SetBombosText ( text )
self . patches . append ( ( Snes ( 0x309000 ) , dialog ) )
if ( self . myWorld . Config . GameMode == GameMode . Multiworld ) :
self . patches . append ( ( Snes ( location . Address ) , [ ( location . Id - 256 ) ] ) )
self . patches . append ( self . ItemTablePatch ( location , self . GetZ3ItemId ( location ) ) )
else :
self . patches . append ( ( Snes ( location . Address ) , [ self . GetZ3ItemId ( location ) ] ) )
def GetZ3ItemId ( self , location : Location ) :
if ( location . APLocation . item . game == " SMZ3 " ) :
item = location . APLocation . item . item
itemDungeon = None
if item . IsKey ( ) :
itemDungeon = ItemType . Key if ( not item . World . Config . Keysanity or item . Type != ItemType . KeyHC ) else ItemType . KeyHC
elif item . IsBigKey ( ) :
itemDungeon = ItemType . BigKey
elif item . IsMap ( ) :
itemDungeon = ItemType . Map if ( not item . World . Config . Keysanity or item . Type != ItemType . MapHC ) else ItemType . MapHC
elif item . IsCompass ( ) :
itemDungeon = ItemType . Compass
value = item . Type if location . Type == LocationType . NotInDungeon or \
not ( item . IsDungeonItem ( ) and location . Region . IsRegionItem ( item ) and item . World == self . myWorld ) else itemDungeon
return value . value
else :
return ItemType . Something . value
def ItemTablePatch ( self , location : Location , itemId : int ) :
itemtype = 0 if location . APLocation . item . player == location . Region . world . Id else 1
2022-03-25 21:35:55 -04:00
owner = location . APLocation . item . player if location . APLocation . item . player < 256 else 0
2022-03-15 08:55:57 -04:00
return ( 0x386000 + ( location . Id * 8 ) , getWordArray ( itemtype ) + getWordArray ( itemId ) + getWordArray ( owner ) )
def WriteDungeonMusic ( self , keysanity : bool ) :
if ( not keysanity ) :
regions = [ region for region in self . myWorld . Regions if isinstance ( region , IReward ) ]
music = [ ]
pendantRegions = [ region for region in regions if region . Reward in [ RewardType . PendantGreen , RewardType . PendantNonGreen ] ]
crystalRegions = [ region for region in regions if region . Reward in [ RewardType . CrystalBlue , RewardType . CrystalRed ] ]
regions = pendantRegions + crystalRegions
music = [
0x11 , 0x11 , 0x11 , 0x16 , 0x16 ,
0x16 , 0x16 , 0x16 , 0x16 , 0x16 ,
]
self . patches + = self . MusicPatches ( regions , music )
#IEnumerable<byte> RandomDungeonMusic() {
# while (true) yield return rnd.Next(2) == 0 ? (byte)0x11 : (byte)0x16;
#}
def MusicPatches ( self , regions : List [ IReward ] , music : List [ int ] ) :
addresses = [ self . MusicAddresses ( region ) for region in regions ]
associations = zip ( addresses , music )
return [ ( Snes ( i ) , [ association [ 1 ] ] ) for association in associations for i in association [ 0 ] ]
def MusicAddresses ( self , region : IReward ) :
regionMap = {
EasternPalace : [ 0x2D59A ] ,
DesertPalace : [ 0x2D59B , 0x2D59C , 0x2D59D , 0x2D59E ] ,
TowerOfHera : [ 0x2D5C5 , 0x2907A , 0x28B8C ] ,
PalaceOfDarkness : [ 0x2D5B8 ] ,
SwampPalace : [ 0x2D5B7 ] ,
SkullWoods : [ 0x2D5BA , 0x2D5BB , 0x2D5BC , 0x2D5BD , 0x2D608 , 0x2D609 , 0x2D60A , 0x2D60B ] ,
ThievesTown : [ 0x2D5C6 ] ,
IcePalace : [ 0x2D5BF ] ,
MiseryMire : [ 0x2D5B9 ] ,
TurtleRock : [ 0x2D5C7 , 0x2D5A7 , 0x2D5AA , 0x2D5AB ] ,
}
result = regionMap . get ( type ( region ) , None )
if result is None :
raise exception ( f " Region { region } should not be a dungeon music region " )
else :
return result
def WritePrizeShuffle ( self ) :
prizePackItems = 56
treePullItems = 3
bytes = [ ]
drop = 0
final = 0
pool = [
DropPrize . Heart , DropPrize . Heart , DropPrize . Heart , DropPrize . Heart , DropPrize . Green , DropPrize . Heart , DropPrize . Heart , DropPrize . Green , #// pack 1
DropPrize . Blue , DropPrize . Green , DropPrize . Blue , DropPrize . Red , DropPrize . Blue , DropPrize . Green , DropPrize . Blue , DropPrize . Blue , #// pack 2
DropPrize . FullMagic , DropPrize . Magic , DropPrize . Magic , DropPrize . Blue , DropPrize . FullMagic , DropPrize . Magic , DropPrize . Heart , DropPrize . Magic , #// pack 3
DropPrize . Bomb1 , DropPrize . Bomb1 , DropPrize . Bomb1 , DropPrize . Bomb4 , DropPrize . Bomb1 , DropPrize . Bomb1 , DropPrize . Bomb8 , DropPrize . Bomb1 , #// pack 4
DropPrize . Arrow5 , DropPrize . Heart , DropPrize . Arrow5 , DropPrize . Arrow10 , DropPrize . Arrow5 , DropPrize . Heart , DropPrize . Arrow5 , DropPrize . Arrow10 , #// pack 5
DropPrize . Magic , DropPrize . Green , DropPrize . Heart , DropPrize . Arrow5 , DropPrize . Magic , DropPrize . Bomb1 , DropPrize . Green , DropPrize . Heart , #// pack 6
DropPrize . Heart , DropPrize . Fairy , DropPrize . FullMagic , DropPrize . Red , DropPrize . Bomb8 , DropPrize . Heart , DropPrize . Red , DropPrize . Arrow10 , #// pack 7
DropPrize . Green , DropPrize . Blue , DropPrize . Red , #// from pull trees
DropPrize . Green , DropPrize . Red , #// from prize crab
DropPrize . Green , #// stunned prize
DropPrize . Red , #// saved fish prize
]
prizes = pool
self . rnd . shuffle ( prizes )
#/* prize pack drop order */
( bytes , prizes ) = SplitOff ( prizes , prizePackItems )
self . patches . append ( ( Snes ( 0x6FA78 ) , [ byte . value for byte in bytes ] ) )
#/* tree pull prizes */
( bytes , prizes ) = SplitOff ( prizes , treePullItems )
self . patches . append ( ( Snes ( 0x1DFBD4 ) , [ byte . value for byte in bytes ] ) )
#/* crab prizes */
( drop , final , prizes ) = ( prizes [ 0 ] , prizes [ 1 ] , prizes [ 2 : ] )
self . patches . append ( ( Snes ( 0x6A9C8 ) , [ drop . value ] ) )
self . patches . append ( ( Snes ( 0x6A9C4 ) , [ final . value ] ) )
#/* stun prize */
( drop , prizes ) = ( prizes [ 0 ] , prizes [ 1 : ] )
self . patches . append ( ( Snes ( 0x6F993 ) , [ drop . value ] ) )
#/* fish prize */
drop = prizes [ 0 ]
self . patches . append ( ( Snes ( 0x1D82CC ) , [ drop . value ] ) )
self . patches + = self . EnemyPrizePackDistribution ( )
#/* Pack drop chance */
#/* Normal difficulty is 50%. 0 => 100%, 1 => 50%, 3 => 25% */
nrPacks = 7
probability = 1
self . patches . append ( ( Snes ( 0x6FA62 ) , [ probability ] * nrPacks ) )
def EnemyPrizePackDistribution ( self ) :
( prizePacks , duplicatePacks ) = self . EnemyPrizePacks ( )
n = sum ( len ( x [ 1 ] ) for x in prizePacks )
randomization = self . PrizePackRandomization ( n , 1 )
patches = [ ]
for prizepack in prizePacks :
( packs , randomization ) = SplitOff ( randomization , len ( prizepack [ 1 ] ) )
patches . append ( ( prizepack [ 0 ] , [ ( b | p ) for b , p in zip ( prizepack [ 1 ] , packs ) ] ) )
duplicates = [ ( d [ 1 ] , p [ 1 ] )
for d in duplicatePacks
for p in patches
if p [ 0 ] == d [ 0 ] ]
patches + = duplicates
return [ ( Snes ( x [ 0 ] ) , x [ 1 ] ) for x in patches ]
#/* Guarantees at least s of each prize pack, over a total of n packs.
#* In each iteration, from the product n * m, use the guaranteed number
#* at k, where k is the "row" (integer division by m), when k falls
#* within the list boundary. Otherwise use the "column" (modulo by m)
#* as the random element.
#*/
def PrizePackRandomization ( self , n : int , s : int ) :
m = 7
g = list ( range ( 0 , m ) ) * s
def randomization ( n : int ) :
result = [ ]
n = m * n
while ( n > 0 ) :
r = self . rnd . randrange ( 0 , n )
k = r / / m
result . append ( g [ k ] if k < len ( g ) else r % m )
if ( k < len ( g ) ) : del g [ k ]
n - = m
return result
return [ ( x + 1 ) for x in randomization ( n ) ]
#/* Todo: Deadrock turns into $8F Blob when powdered, but those "onion blobs" always drop prize pack 1. */
def EnemyPrizePacks ( self ) :
offset = 0xDB632
patches = [
#/* sprite_prep */
( 0x6888D , [ 0x00 ] ) , #// Keese DW
( 0x688A8 , [ 0x00 ] ) , #// Rope
( 0x68967 , [ 0x00 , 0x00 ] ) , #// Crow/Dacto
( 0x69125 , [ 0x00 , 0x00 ] ) , #// Red/Blue Hardhat Bettle
#/* sprite properties */
( offset + 0x01 , [ 0x90 ] ) , #// Vulture
( offset + 0x08 , [ 0x00 ] ) , #// Octorok (One Way)
( offset + 0x0A , [ 0x00 ] ) , #// Octorok (Four Way)
( offset + 0x0D , [ 0x80 , 0x90 ] ) , #// Buzzblob, Snapdragon
( offset + 0x11 , [ 0x90 , 0x90 , 0x00 ] ) , #// Hinox, Moblin, Mini Helmasaur
( offset + 0x18 , [ 0x90 , 0x90 ] ) , #// Mini Moldorm, Poe/Hyu
( offset + 0x20 , [ 0x00 ] ) , #// Sluggula
( offset + 0x22 , [ 0x80 , 0x00 , 0x00 ] ) , #// Ropa, Red Bari, Blue Bari
#// Blue Soldier/Tarus, Green Soldier, Red Spear Soldier
#// Blue Assault Soldier, Red Assault Spear Soldier/Tarus
#// Blue Archer, Green Archer
#// Red Javelin Soldier, Red Bush Javelin Soldier
#// Red Bomb Soldiers, Green Soldier Recruits,
#// Geldman, Toppo
( offset + 0x41 , [ 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x10 , 0x90 , 0x90 , 0x80 ] ) ,
( offset + 0x4F , [ 0x80 ] ) , #// Popo 2
( offset + 0x51 , [ 0x80 ] ) , #// Armos
( offset + 0x55 , [ 0x00 , 0x00 ] ) , #// Ku, Zora
( offset + 0x58 , [ 0x90 ] ) , #// Crab
( offset + 0x64 , [ 0x80 ] ) , #// Devalant (Shooter)
( offset + 0x6A , [ 0x90 , 0x90 ] ) , #// Ball N' Chain Trooper, Cannon Soldier
( offset + 0x6D , [ 0x80 , 0x80 ] ) , #// Rat/Buzz, (Stal)Rope
( offset + 0x71 , [ 0x80 ] ) , #// Leever
( offset + 0x7C , [ 0x90 ] ) , #// Initially Floating Stal
( offset + 0x81 , [ 0xC0 ] ) , #// Hover
#// Green Eyegore/Mimic, Red Eyegore/Mimic
#// Detached Stalfos Body, Kodongo
( offset + 0x83 , [ 0x10 , 0x10 , 0x10 , 0x00 ] ) ,
( offset + 0x8B , [ 0x10 ] ) , #// Gibdo
( offset + 0x8E , [ 0x00 , 0x00 ] ) , #// Terrorpin, Blob
( offset + 0x91 , [ 0x10 ] ) , #// Stalfos Knight
( offset + 0x99 , [ 0x10 ] ) , #// Pengator
( offset + 0x9B , [ 0x10 ] ) , #// Wizzrobe
#// Blue Zazak, Red Zazak, Stalfos
#// Green Zirro, Blue Zirro, Pikit
( offset + 0xA5 , [ 0x10 , 0x10 , 0x10 , 0x80 , 0x80 , 0x80 ] ) ,
( offset + 0xC7 , [ 0x10 ] ) , #// Hokku-Bokku
( offset + 0xC9 , [ 0x10 ] ) , #// Tektite
( offset + 0xD0 , [ 0x10 ] ) , #// Lynel
( offset + 0xD3 , [ 0x00 ] ) , #// Stal
]
duplicates = [
#/* Popo2 -> Popo. Popo is not used in vanilla Z3, but we duplicate from Popo2 just to be sure */
( offset + 0x4F , offset + 0x4E ) ,
]
return ( patches , duplicates )
def WriteTexts ( self , config : Config ) :
regions = [ region for region in self . myWorld . Regions if isinstance ( region , IReward ) ]
greenPendantDungeon = [ region for region in regions if region . Reward == RewardType . PendantGreen ] [ 0 ]
redCrystalDungeons = [ region for region in regions if region . Reward == RewardType . CrystalRed ]
sahasrahla = Texts . SahasrahlaReveal ( greenPendantDungeon )
self . patches . append ( ( Snes ( 0x308A00 ) , Dialog . Simple ( sahasrahla ) ) )
self . stringTable . SetSahasrahlaRevealText ( sahasrahla )
bombShop = Texts . BombShopReveal ( redCrystalDungeons )
self . patches . append ( ( Snes ( 0x308E00 ) , Dialog . Simple ( bombShop ) ) )
self . stringTable . SetBombShopRevealText ( bombShop )
blind = Texts . Blind ( self . rnd )
self . patches . append ( ( Snes ( 0x308800 ) , Dialog . Simple ( blind ) ) )
self . stringTable . SetBlindText ( blind )
tavernMan = Texts . TavernMan ( self . rnd )
self . patches . append ( ( Snes ( 0x308C00 ) , Dialog . Simple ( tavernMan ) ) )
self . stringTable . SetTavernManText ( tavernMan )
ganon = Texts . GanonFirstPhase ( self . rnd )
self . patches . append ( ( Snes ( 0x308600 ) , Dialog . Simple ( ganon ) ) )
self . stringTable . SetGanonFirstPhaseText ( ganon )
#// Todo: Verify these two are correct if ganon invincible patch is ever added
#// ganon_fall_in_alt in v30
ganonFirstPhaseInvincible = " You think you \n are ready to \n face me? \n \n I will not die \n \n unless you \n complete your \n goals. Dingus! "
self . patches . append ( ( Snes ( 0x309100 ) , Dialog . Simple ( ganonFirstPhaseInvincible ) ) )
#// ganon_phase_3_alt in v30
ganonThirdPhaseInvincible = " Got wax in \n your ears? \n I cannot die! "
self . patches . append ( ( Snes ( 0x309200 ) , Dialog . Simple ( ganonThirdPhaseInvincible ) ) )
#// ---
silversLocation = [ loc for world in self . allWorlds for loc in world . Locations if loc . ItemIs ( ItemType . SilverArrows , self . myWorld ) ]
if len ( silversLocation ) == 0 :
silvers = Texts . GanonThirdPhaseMulti ( None , self . myWorld , self . silversWorldID , self . playerIDToNames [ self . silversWorldID ] )
else :
silvers = Texts . GanonThirdPhaseMulti ( silversLocation [ 0 ] . Region , self . myWorld ) if config . GameMode == GameMode . Multiworld else \
Texts . GanonThirdPhaseSingle ( silversLocation [ 0 ] . Region )
self . patches . append ( ( Snes ( 0x308700 ) , Dialog . Simple ( silvers ) ) )
self . stringTable . SetGanonThirdPhaseText ( silvers )
triforceRoom = Texts . TriforceRoom ( self . rnd )
self . patches . append ( ( Snes ( 0x308400 ) , Dialog . Simple ( triforceRoom ) ) )
self . stringTable . SetTriforceRoomText ( triforceRoom )
def WriteStringTable ( self ) :
#// Todo: v12, base table in asm, use move instructions in seed patch
self . patches . append ( ( Snes ( 0x1C8000 ) , self . stringTable . GetPaddedBytes ( ) ) )
def WritePlayerNames ( self ) :
self . patches + = [ ( 0x385000 + ( 0 * 16 ) , self . PlayerNameBytes ( " Archipelago " ) ) ]
2022-03-25 21:35:55 -04:00
self . patches + = [ ( 0x385000 + ( id * 16 ) , self . PlayerNameBytes ( name ) ) for name , id in self . playerNames . items ( ) if id < 256 ]
2022-03-15 08:55:57 -04:00
def PlayerNameBytes ( self , name : str ) :
name = ( name [ : 16 ] if len ( name ) > 16 else name ) . center ( 16 )
return bytearray ( name , ' utf8 ' )
def WriteSeedData ( self ) :
configField = \
( ( 1 if self . myWorld . Config . Race else 0 ) << 15 ) | \
( ( 1 if self . myWorld . Config . Keysanity else 0 ) << 13 ) | \
( ( 1 if self . myWorld . Config . GameMode == Config . GameMode . Multiworld else 0 ) << 12 ) | \
( self . myWorld . Config . Z3Logic . value << 10 ) | \
( self . myWorld . Config . SMLogic . value << 8 ) | \
( Patch . Major << 4 ) | \
( Patch . Minor << 0 )
self . patches . append ( ( Snes ( 0x80FF50 ) , getWordArray ( self . myWorld . Id ) ) )
self . patches . append ( ( Snes ( 0x80FF52 ) , getWordArray ( configField ) ) )
self . patches . append ( ( Snes ( 0x80FF54 ) , getDoubleWordArray ( self . seed ) ) )
#/* Reserve the rest of the space for future use */
self . patches . append ( ( Snes ( 0x80FF58 ) , [ 0x00 ] * 8 ) )
self . patches . append ( ( Snes ( 0x80FF60 ) , bytearray ( self . seedGuid , ' utf8 ' ) ) )
self . patches . append ( ( Snes ( 0x80FF80 ) , bytearray ( self . myWorld . Guid , ' utf8 ' ) ) )
def WriteCommonFlags ( self ) :
#/* Common Combo Configuration flags at [asm]/config.asm */
if ( self . myWorld . Config . GameMode == GameMode . Multiworld ) :
self . patches . append ( ( Snes ( 0xF47000 ) , getWordArray ( 0x0001 ) ) )
if ( self . myWorld . Config . Keysanity ) :
self . patches . append ( ( Snes ( 0xF47006 ) , getWordArray ( 0x0001 ) ) )
def WriteGameTitle ( self ) :
z3Glitch = " N " if self . myWorld . Config . Z3Logic == Config . Z3Logic . Nmg else \
" O " if self . myWorld . Config . Z3Logic == Config . Z3Logic . Owg else \
" C "
smGlitch = " N " if self . myWorld . Config . SMLogic == Config . SMLogic . Normal else \
" H " if self . myWorld . Config . SMLogic == Config . SMLogic . Hard else \
" X "
self . title = f " ZSM { Patch . Major } { Patch . Minor } { z3Glitch } { smGlitch } { self . myWorld . Id } { self . seed : 08x } " . ljust ( 21 ) [ : 21 ]
self . patches . append ( ( Snes ( 0x00FFC0 ) , bytearray ( self . title , ' utf8 ' ) ) )
self . patches . append ( ( Snes ( 0x80FFC0 ) , bytearray ( self . title , ' utf8 ' ) ) )
def WriteZ3KeysanityFlags ( self ) :
if ( self . myWorld . Config . Keysanity ) :
self . patches . append ( ( Snes ( 0x40003B ) , [ 1 ] ) ) #// MapMode #$00 = Always On (default) - #$01 = Require Map Item
self . patches . append ( ( Snes ( 0x400045 ) , [ 0x0f ] ) ) #// display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass
def WriteSMKeyCardDoors ( self ) :
if ( not self . myWorld . Config . Keysanity ) :
return
plaquePLm = 0xd410
doorList = [
#// RoomId Door Facing yyxx Keycard Event Type Plaque type yyxx, Address (if 0 a dynamic PLM is created)
#// Crateria
[ 0x91F8 , KeycardDoors . Right , 0x2601 , KeycardEvents . CrateriaLevel1 , KeycardPlaque . Level1 , 0x2400 , 0x0000 ] , #// Crateria - Landing Site - Door to gauntlet
[ 0x91F8 , KeycardDoors . Left , 0x168E , KeycardEvents . CrateriaLevel1 , KeycardPlaque . Level1 , 0x148F , 0x801E ] , #// Crateria - Landing Site - Door to landing site PB
[ 0x948C , KeycardDoors . Left , 0x062E , KeycardEvents . CrateriaLevel2 , KeycardPlaque . Level2 , 0x042F , 0x8222 ] , #// Crateria - Before Moat - Door to moat (overwrite PB door)
[ 0x99BD , KeycardDoors . Left , 0x660E , KeycardEvents . CrateriaBoss , KeycardPlaque . Boss , 0x640F , 0x8470 ] , #// Crateria - Before G4 - Door to G4
[ 0x9879 , KeycardDoors . Left , 0x062E , KeycardEvents . CrateriaBoss , KeycardPlaque . Boss , 0x042F , 0x8420 ] , #// Crateria - Before BT - Door to Bomb Torizo
#// Brinstar
[ 0x9F11 , KeycardDoors . Left , 0x060E , KeycardEvents . BrinstarLevel1 , KeycardPlaque . Level1 , 0x040F , 0x8784 ] , #// Brinstar - Blue Brinstar - Door to ceiling e-tank room
[ 0x9AD9 , KeycardDoors . Right , 0xA601 , KeycardEvents . BrinstarLevel2 , KeycardPlaque . Level2 , 0xA400 , 0x0000 ] , #// Brinstar - Green Brinstar - Door to etecoon area
[ 0x9D9C , KeycardDoors . Down , 0x0336 , KeycardEvents . BrinstarBoss , KeycardPlaque . Boss , 0x0234 , 0x863A ] , #// Brinstar - Pink Brinstar - Door to spore spawn
[ 0xA130 , KeycardDoors . Left , 0x161E , KeycardEvents . BrinstarLevel2 , KeycardPlaque . Level2 , 0x141F , 0x881C ] , #// Brinstar - Pink Brinstar - Door to wave gate e-tank
[ 0xA0A4 , KeycardDoors . Left , 0x062E , KeycardEvents . BrinstarLevel2 , KeycardPlaque . Level2 , 0x042F , 0x0000 ] , #// Brinstar - Pink Brinstar - Door to spore spawn super
[ 0xA56B , KeycardDoors . Left , 0x161E , KeycardEvents . BrinstarBoss , KeycardPlaque . Boss , 0x141F , 0x8A1A ] , #// Brinstar - Before Kraid - Door to Kraid
#// Upper Norfair
[ 0xA7DE , KeycardDoors . Right , 0x3601 , KeycardEvents . NorfairLevel1 , KeycardPlaque . Level1 , 0x3400 , 0x8B00 ] , #// Norfair - Business Centre - Door towards Ice
[ 0xA923 , KeycardDoors . Right , 0x0601 , KeycardEvents . NorfairLevel1 , KeycardPlaque . Level1 , 0x0400 , 0x0000 ] , #// Norfair - Pre-Crocomire - Door towards Ice
[ 0xA788 , KeycardDoors . Left , 0x162E , KeycardEvents . NorfairLevel2 , KeycardPlaque . Level2 , 0x142F , 0x8AEA ] , #// Norfair - Lava Missile Room - Door towards Bubble Mountain
[ 0xAF72 , KeycardDoors . Left , 0x061E , KeycardEvents . NorfairLevel2 , KeycardPlaque . Level2 , 0x041F , 0x0000 ] , #// Norfair - After frog speedway - Door to Bubble Mountain
[ 0xAEDF , KeycardDoors . Down , 0x0206 , KeycardEvents . NorfairLevel2 , KeycardPlaque . Level2 , 0x0204 , 0x0000 ] , #// Norfair - Below bubble mountain - Door to Bubble Mountain
[ 0xAD5E , KeycardDoors . Right , 0x0601 , KeycardEvents . NorfairLevel2 , KeycardPlaque . Level2 , 0x0400 , 0x0000 ] , #// Norfair - LN Escape - Door to Bubble Mountain
[ 0xA923 , KeycardDoors . Up , 0x2DC6 , KeycardEvents . NorfairBoss , KeycardPlaque . Boss , 0x2EC4 , 0x8B96 ] , #// Norfair - Pre-Crocomire - Door to Crocomire
#// Lower Norfair
[ 0xB4AD , KeycardDoors . Left , 0x160E , KeycardEvents . LowerNorfairLevel1 , KeycardPlaque . Level1 , 0x140F , 0x0000 ] , #// Lower Norfair - WRITG - Door to Amphitheatre
[ 0xAD5E , KeycardDoors . Left , 0x065E , KeycardEvents . LowerNorfairLevel1 , KeycardPlaque . Level1 , 0x045F , 0x0000 ] , #// Lower Norfair - Exit - Door to "Reverse LN Entry"
[ 0xB37A , KeycardDoors . Right , 0x0601 , KeycardEvents . LowerNorfairBoss , KeycardPlaque . Boss , 0x0400 , 0x8EA6 ] , #// Lower Norfair - Pre-Ridley - Door to Ridley
#// Maridia
[ 0xD0B9 , KeycardDoors . Left , 0x065E , KeycardEvents . MaridiaLevel1 , KeycardPlaque . Level1 , 0x045F , 0x0000 ] , #// Maridia - Mt. Everest - Door to Pink Maridia
[ 0xD5A7 , KeycardDoors . Right , 0x1601 , KeycardEvents . MaridiaLevel1 , KeycardPlaque . Level1 , 0x1400 , 0x0000 ] , #// Maridia - Aqueduct - Door towards Beach
[ 0xD617 , KeycardDoors . Left , 0x063E , KeycardEvents . MaridiaLevel2 , KeycardPlaque . Level2 , 0x043F , 0x0000 ] , #// Maridia - Pre-Botwoon - Door to Botwoon
[ 0xD913 , KeycardDoors . Right , 0x2601 , KeycardEvents . MaridiaLevel2 , KeycardPlaque . Level2 , 0x2400 , 0x0000 ] , #// Maridia - Pre-Colloseum - Door to post-botwoon
[ 0xD78F , KeycardDoors . Right , 0x2601 , KeycardEvents . MaridiaBoss , KeycardPlaque . Boss , 0x2400 , 0xC73B ] , #// Maridia - Precious Room - Door to Draygon
[ 0xDA2B , KeycardDoors . BossLeft , 0x164E , 0x00f0 , KeycardPlaque . Null , 0x144F , 0x0000 ] , #// Maridia - Change Cac Alley Door to Boss Door (prevents key breaking)
#// Wrecked Ship
[ 0x93FE , KeycardDoors . Left , 0x167E , KeycardEvents . WreckedShipLevel1 , KeycardPlaque . Level1 , 0x147F , 0x0000 ] , #// Wrecked Ship - Outside Wrecked Ship West - Door to Reserve Tank Check
[ 0x968F , KeycardDoors . Left , 0x060E , KeycardEvents . WreckedShipLevel1 , KeycardPlaque . Level1 , 0x040F , 0x0000 ] , #// Wrecked Ship - Outside Wrecked Ship West - Door to Bowling Alley
[ 0xCE40 , KeycardDoors . Left , 0x060E , KeycardEvents . WreckedShipLevel1 , KeycardPlaque . Level1 , 0x040F , 0x0000 ] , #// Wrecked Ship - Gravity Suit - Door to Bowling Alley
[ 0xCC6F , KeycardDoors . Left , 0x064E , KeycardEvents . WreckedShipBoss , KeycardPlaque . Boss , 0x044F , 0xC29D ] , #// Wrecked Ship - Pre-Phantoon - Door to Phantoon
]
doorId = 0x0000
plmTablePos = 0xf800
for door in doorList :
doorArgs = doorId | door [ 3 ] if door [ 4 ] != KeycardPlaque . Null else door [ 3 ]
if ( door [ 6 ] == 0 ) :
#// Write dynamic door
doorData = [ ]
for x in door [ 0 : 3 ] :
doorData + = getWordArray ( x )
doorData + = getWordArray ( doorArgs )
self . patches . append ( ( Snes ( 0x8f0000 + plmTablePos ) , doorData ) )
plmTablePos + = 0x08
else :
#// Overwrite existing door
doorData = [ ]
for x in door [ 1 : 3 ] :
doorData + = getWordArray ( x )
doorData + = getWordArray ( doorArgs )
self . patches . append ( ( Snes ( 0x8f0000 + door [ 6 ] ) , doorData ) )
if ( ( door [ 3 ] == KeycardEvents . BrinstarBoss and door [ 0 ] != 0x9D9C ) or door [ 3 ] == KeycardEvents . LowerNorfairBoss or door [ 3 ] == KeycardEvents . MaridiaBoss or door [ 3 ] == KeycardEvents . WreckedShipBoss ) :
#// Overwrite the extra parts of the Gadora with a PLM that just deletes itself
self . patches . append ( ( Snes ( 0x8f0000 + door [ 6 ] + 0x06 ) , [ 0x2F , 0xB6 , 0x00 , 0x00 , 0x00 , 0x00 , 0x2F , 0xB6 , 0x00 , 0x00 , 0x00 , 0x00 ] ) )
#// Plaque data
if ( door [ 4 ] != KeycardPlaque . Null ) :
plaqueData = getWordArray ( door [ 0 ] ) + getWordArray ( plaquePLm ) + getWordArray ( door [ 5 ] ) + getWordArray ( door [ 4 ] )
self . patches . append ( ( Snes ( 0x8f0000 + plmTablePos ) , plaqueData ) )
plmTablePos + = 0x08
doorId + = 1
self . patches . append ( ( Snes ( 0x8f0000 + plmTablePos ) , [ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ] ) )
def WriteDiggingGameRng ( self ) :
digs = ( self . rnd . randrange ( 30 ) + 1 )
self . patches . append ( ( Snes ( 0x308020 ) , [ digs ] ) )
self . patches . append ( ( Snes ( 0x1DFD95 ) , [ digs ] ) )
#// Removes Sword/Shield from Uncle by moving the tiles for
#// sword/shield to his head and replaces them with his head.
def WriteRemoveEquipmentFromUncle ( self , item : Item ) :
if ( item . Type != ItemType . ProgressiveSword ) :
self . patches + = [
( Snes ( 0xDD263 ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD26B ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD293 ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD29B ) , [ 0x00 , 0x00 , 0xF7 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD2B3 ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x02 , 0x0E ] ) ,
( Snes ( 0xDD2BB ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x02 , 0x0E ] ) ,
( Snes ( 0xDD2E3 ) , [ 0x00 , 0x00 , 0xF7 , 0xFF , 0x02 , 0x0E ] ) ,
( Snes ( 0xDD2EB ) , [ 0x00 , 0x00 , 0xF7 , 0xFF , 0x02 , 0x0E ] ) ,
( Snes ( 0xDD31B ) , [ 0x00 , 0x00 , 0xE4 , 0xFF , 0x08 , 0x0E ] ) ,
( Snes ( 0xDD323 ) , [ 0x00 , 0x00 , 0xE4 , 0xFF , 0x08 , 0x0E ] ) ,
]
if ( item . Type != ItemType . ProgressiveShield ) :
self . patches + = [
( Snes ( 0xDD253 ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD25B ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD283 ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD28B ) , [ 0x00 , 0x00 , 0xF7 , 0xFF , 0x00 , 0x0E ] ) ,
( Snes ( 0xDD2CB ) , [ 0x00 , 0x00 , 0xF6 , 0xFF , 0x02 , 0x0E ] ) ,
( Snes ( 0xDD2FB ) , [ 0x00 , 0x00 , 0xF7 , 0xFF , 0x02 , 0x0E ] ) ,
( Snes ( 0xDD313 ) , [ 0x00 , 0x00 , 0xE4 , 0xFF , 0x08 , 0x0E ] ) ,
]
def WriteGanonInvicible ( self , invincible : GanonInvincible ) :
#/* Defaults to $00 (never) at [asm]/z3/randomizer/tables.asm */
invincibleMap = {
GanonInvincible . Never : 0x00 ,
GanonInvincible . Always : 0x01 ,
GanonInvincible . BeforeAllDungeons : 0x02 ,
GanonInvincible . BeforeCrystals : 0x03 ,
}
value = invincibleMap . get ( invincible , None )
if ( value is None ) :
raise exception ( f " Unknown Ganon invincible value { invincible } " )
else :
self . patches . append ( ( Snes ( 0x30803E ) , [ value ] ) )
def WriteRngBlock ( self ) :
#/* Repoint RNG Block */
self . patches . append ( ( 0x420000 , [ self . rnd . randrange ( 0 , 0x100 ) for x in range ( 0 , 1024 ) ] ) )
def WriteSaveAndQuitFromBossRoom ( self ) :
#/* Defaults to $00 at [asm]/z3/randomizer/tables.asm */
self . patches . append ( ( Snes ( 0x308042 ) , [ 0x01 ] ) )
def WriteWorldOnAgahnimDeath ( self ) :
pass
#/* Defaults to $01 at [asm]/z3/randomizer/tables.asm */
#// Todo: Z3r major glitches disables this, reconsider extending or dropping with glitched logic later.
#//patches.Add((Snes(0x3080A3), new byte[] { 0x01 }));
def Snes ( addr : int ) :
#/* Redirect hi bank $30 access into ExHiRom lo bank $40 */
if ( addr & 0xFF8000 ) == 0x308000 :
addr = 0x400000 | ( addr & 0x7FFF )
else : #/* General case, add ExHi offset for banks < $80, and collapse mirroring */
addr = ( 0x400000 if addr < 0x800000 else 0 ) | ( addr & 0x3FFFFF )
if ( addr > 0x600000 ) :
raise Exception ( f " Unmapped pc address target $ { addr : x } " )
return addr
def getWord ( w ) :
return ( w & 0x00FF , ( w & 0xFF00 ) >> 8 )
def getWordArray ( w ) :
return [ w & 0x00FF , ( w & 0xFF00 ) >> 8 ]
def getDoubleWordArray ( w ) :
return [ w & 0x000000FF , ( w & 0x0000FF00 ) >> 8 , ( w & 0x00FF0000 ) >> 16 , ( w & 0xFF000000 ) >> 24 ]
"""
byte [ ] UintBytes ( int value ) = > BitConverter . GetBytes ( ( uint ) value ) ;
byte [ ] UshortBytes ( int value ) = > BitConverter . GetBytes ( ( ushort ) value ) ;
byte [ ] AsAscii ( string text ) = > Encoding . ASCII . GetBytes ( text ) ;
}
}
"""
def SplitOff ( source : List [ Any ] , count : int ) :
head = source [ : count ]
tail = source [ count : ]
return ( head , tail )