Add SMZ3 support (#270)
This commit is contained in:
809
worlds/smz3/TotalSMZ3/Patch.py
Normal file
809
worlds/smz3/TotalSMZ3/Patch.py
Normal file
@@ -0,0 +1,809 @@
|
||||
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
|
||||
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()]
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user