810 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			810 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			Python
		
	
	
	
| 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
 | |
|         owner = location.APLocation.item.player if location.APLocation.item.player < 256 else 0
 | |
|         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\nare ready to\nface me?\n\nI will not die\n\nunless you\ncomplete your\ngoals. Dingus!"
 | |
|         self.patches.append((Snes(0x309100), Dialog.Simple(ganonFirstPhaseInvincible)))
 | |
| 
 | |
|         #// ganon_phase_3_alt in v30
 | |
|         ganonThirdPhaseInvincible = "Got wax in\nyour ears?\nI 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"))]
 | |
|         self.patches += [(0x385000 + (id * 16), self.PlayerNameBytes(name)) for name, id in self.playerNames.items() if id < 256]
 | |
| 
 | |
|     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)
 |