Files
alttpr-python/source/enemizer/SpriteSheets.py

797 lines
44 KiB
Python

import logging
from collections import defaultdict
import RaceRandom as random
from source.dungeon.EnemyList import EnemySprite, SpriteType, enemy_names, sprite_translation, overlord_names
from source.dungeon.RoomConstants import *
class SpriteRequirement:
def __init__(self, sprite, overlord=0):
self.sprite = sprite
self.overlord = overlord
self.boss = False
self.static = False # npcs and do not randomize
self.killable = True
self.can_drop = True
self.water_only = False
self.dont_use = False
self.ow_valid = True
self.uw_valid = True
self.can_randomize = True
self.water_phobic = False
self.bush_valid = True
self.groups = []
self.sub_groups = defaultdict(list)
self.excluded_rooms = set()
self.allowed_rooms = set()
def can_spawn_in_room(self, room_id):
return room_id not in self.excluded_rooms and (self.sprite != EnemySprite.Wallmaster or room_id < 0x100)
def no_drop(self):
self.can_drop = False
return self
def sub_group(self, key, subs):
if isinstance(subs, list):
self.sub_groups[key].extend(subs)
else:
self.sub_groups[key].append(subs)
return self
def group(self, group_id):
self.groups.append(group_id)
return self
def exclude(self, exclusions):
self.excluded_rooms.update(exclusions)
return self
def allow(self, allowed):
self.allowed_rooms.update(allowed)
return self
def affix(self):
self.static = True
self.killable = False
self.can_drop = False
return self
def stasis(self):
self.can_randomize = False
return self
def exalt(self):
self.boss = True
self.static = True # not randomized by sprite sheet
return self
def immune(self):
self.killable = False
self.can_drop = False
return self
def immerse(self):
self.water_only = True
return self
def aquaphobia(self):
self.water_phobic = True
return self
def skip(self):
self.dont_use = True
return self
def ow_skip(self):
self.ow_valid = False
return self
def uw_skip(self):
self.uw_valid = False
return self
def no_bush(self):
self.bush_valid = False
return self
def good_for_uw_water(self):
return self.water_only and not self.static and not self.dont_use and self.uw_valid
def good_for_shutter(self, forbidden):
if self.sprite in forbidden:
return False
return self.killable and not self.static and not self.dont_use and self.uw_valid
def good_for_key_drop(self, forbidden):
return self.good_for_shutter(forbidden) and self.can_drop
def __str__(self):
return f'Req for {enemy_names[self.sprite] if self.overlord != 0x7 else overlord_names[self.sprite]}'
NoFlyingRooms = {0xd2, 0x10c} # Mire 2, Mimic Cave
NoBeamosOrTrapRooms = {0xb, 0x16, 0x19, 0x1e, 0x26, 0x27, 0x36, 0x3f, 0x40, 0x42, 0x46, 0x49, 0x4b, 0x4e, 0x55, 0x57,
0x5f, 0x65, 0x6a, 0x74, 0x76, 0x7d, 0x7f, 0x83, 0x84, 0x85, 0x8c, 0x8d, 0x92, 0x95, 0x98, 0x9b,
0x9c, 0x9d, 0x9e, 0xa0, 0xaa, 0xaf, 0xb3, 0xba, 0xbb, 0xbc, 0xc6, 0xcb, 0xce, 0xd0, 0xd2, 0xd5,
0xd8, 0xdc, 0xdf, 0xe4, 0xe7, 0xee, 0xf9, 0xfd, 0x10c}
LenientTrapsForTesting = {0x16, 0x26, 0x3f, 0x40, 0x42, 0x46, 0x49, 0x4e, 0x57,
0x65, 0x6a, 0x74, 0x76, 0x7d, 0x98,
0x9e, 0xaf, 0xba, 0xc6, 0xcb, 0xce, 0xd2, 0xd5,
0xd8, 0xdf, 0xe4, 0xe7, 0xee, 0xfd, 0x10c}
# wallmasters must not be on tiles near spiral staircases. Unknown if other stairs have issues
WallmasterInvalidRooms = {
HC_NorthCorridor, HC_SwitchRoom, TR_CrystalRollerRoom,
PalaceofDarkness0x09, PoD_StalfosTrapRoom, GT_EntranceRoom, Ice_EntranceRoom,
HC_BombableStockRoom, TurtleRock0x15,
Swamp_SwimmingTreadmill, Hera_MoldormFallRoom, PoD_BigChestRoom,
GT_IceArmos, GT_FinalHallway, Ice_BombFloor_BariRoom,
Swamp_StatueRoom, Hera_BigChest,
Swamp_EntranceRoom, Hera_HardhatBeetlesRoom,
Swamp_PushBlockPuzzle_Pre_BigKeyRoom,
Swamp_KeyPotRoom,
PoD_BombableFloorRoom,
Ice_MapChestRoom, Tower_FinalBridgeRoom, HC_FirstDarkRoom, HC_6RopesRoom,
TT_JailCellsRoom, PoD_EntranceRoom,
GT_Mini_HelmasaurConveyorRoom, GT_MoldormRoom, Ice_Bomb_JumpRoom,
Desert_Popos2_BeamosHellwayRoom,
GT_Ganon_BallZ,
GT_Gauntlet1_2_3, Ice_HiddenChest_SpikeFloorRoom,
Desert_FinalSectionEntranceRoom, TT_WestAtticRoom,
Swamp_HiddenChest_HiddenDoorRoom, PoD_RupeeRoom, GT_MimicsRooms,
GT_LanmolasRoom, Ice_PengatorsRoom, HC_SmallCorridortoJailCells, HC_BoomerangChestRoom,
HC_MapChestRoom, Swamp_WaterDrainRoom,
Hera_EntranceRoom,
Ice_BigSpikeTrapsRoom, HC_JailCellRoom,
Hera_TileRoom,
GT_EastandWestDownstairs_BigChestRoom,
IcePalace0x8E, Mire_FinalSwitchRoom,
Mire_DarkCaneFloorSwitchPuzzleRoom, Mire_TorchPuzzle_MovingWallRoom,
Mire_EntranceRoom, Eastern_EyegoreKeyRoom,
Ice_BigChestRoom, Mire_Pre_VitreousRoom,
Mire_BridgeKeyChestRoom, GT_WizzrobesRooms, GT_MoldormFallRoom,
TT_MovingSpikes_KeyPotRoom,
IcePalace0xAE, Tower_CircleofPots,
TR_DarkMaze, TR_ChainChompsRoom,
TT_ConveyorToilet, Ice_BlockPuzzleRoom,
Tower_DarkBridgeRoom,
UnknownRoom,
Tower_DarkMaze, Mire_ConveyorSlug_BigKeyRoom, Mire_Mire02_WizzrobesRoom,
EasternPalace,
Tower_EntranceRoom, Cave_BackwardsDeathMountainTopFloor, Cave0xE8, Cave_SpectacleRockHP, Cave0xEB, Cave0xED,
Cave_CrystalSwitch_5ChestsRoom, Cave0xF8, Cave0xFA, Cave0xFB, Cave0xFD, Cave0xFF
}
def init_sprite_requirements():
reqs = [
SpriteRequirement(EnemySprite.Raven).no_drop().sub_group(3, [0x11, 0x19]).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.Vulture).no_drop().sub_group(2, 0x12).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.CorrectPullSwitch).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.WrongPullSwitch).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.Octorok).aquaphobia().sub_group(2, [0xc, 0x18]),
SpriteRequirement(EnemySprite.Moldorm).exalt().sub_group(2, 0x30),
SpriteRequirement(EnemySprite.Octorok4Way).aquaphobia().sub_group(2, 0xc),
SpriteRequirement(EnemySprite.Cucco).immune().sub_group(3, [0x15, 0x50]).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.Buzzblob).sub_group(3, 0x11),
SpriteRequirement(EnemySprite.Snapdragon).sub_group(0, 0x16).sub_group(2, 0x17),
SpriteRequirement(EnemySprite.Octoballoon).no_drop().sub_group(2, 0xc).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.Hinox).sub_group(0, 0x16),
SpriteRequirement(EnemySprite.Moblin).sub_group(2, 0x17),
SpriteRequirement(EnemySprite.MiniHelmasaur).aquaphobia().sub_group(1, 0x1e),
SpriteRequirement(EnemySprite.AntiFairy).no_drop().sub_group(3, [0x52, 0x53])
.exclude(NoFlyingRooms).exclude({0x40}), # no anti-fairies in aga tower bridge room
SpriteRequirement(EnemySprite.Wiseman).affix().sub_group(2, 0x4c),
SpriteRequirement(EnemySprite.Hoarder).sub_group(3, 0x11).exclude({0x10c}),
SpriteRequirement(EnemySprite.MiniMoldorm).aquaphobia().sub_group(1, 0x1e),
SpriteRequirement(EnemySprite.Poe).no_drop().sub_group(3, 0x15).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.Smithy).affix().sub_group(1, 0x1d).sub_group(3, 0x15),
SpriteRequirement(EnemySprite.Statue).stasis().immune().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.CrystalSwitch).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.SickKid).affix().sub_group(0, 0x51),
SpriteRequirement(EnemySprite.Sluggula).sub_group(2, 0x25),
SpriteRequirement(EnemySprite.WaterSwitch).affix().sub_group(3, 0x53),
SpriteRequirement(EnemySprite.Ropa).sub_group(0, 0x16),
SpriteRequirement(EnemySprite.RedBari).sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.BlueBari).sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.TalkingTree).affix().sub_group(3, [0x15, 0x1B]),
SpriteRequirement(EnemySprite.HardhatBeetle).sub_group(1, 0x1e),
SpriteRequirement(EnemySprite.Deadrock).sub_group(3, 0x10).exclude({0x7f, 0x10c}),
SpriteRequirement(EnemySprite.DarkWorldHintNpc).affix(), # no groups?
SpriteRequirement(EnemySprite.AdultNpc).affix().sub_group(0, [0xe, 0x4f]),
SpriteRequirement(EnemySprite.SweepingLady).affix().group(6), # no sub groups?
SpriteRequirement(EnemySprite.Lumberjacks).affix().sub_group(2, 0x4a),
SpriteRequirement(EnemySprite.RaceGameLady).affix().group(6),
SpriteRequirement(EnemySprite.FortuneTeller).affix().sub_group(0, 0x4b),
SpriteRequirement(EnemySprite.ArgueBros).affix().sub_group(0, 0x4f),
SpriteRequirement(EnemySprite.RupeePull).affix(),
SpriteRequirement(EnemySprite.YoungSnitch).affix().group(6),
SpriteRequirement(EnemySprite.Innkeeper).affix(), # no groups?
SpriteRequirement(EnemySprite.Witch).affix().sub_group(2, 0x7c),
SpriteRequirement(EnemySprite.Waterfall).affix(),
SpriteRequirement(EnemySprite.EyeStatue).affix(),
SpriteRequirement(EnemySprite.Locksmith).affix().sub_group(3, 0x11),
SpriteRequirement(EnemySprite.MagicBat).affix().sub_group(3, 0x1d),
SpriteRequirement(EnemySprite.KidInKak).affix().group(6),
SpriteRequirement(EnemySprite.OldSnitch).affix().group(6),
SpriteRequirement(EnemySprite.Hoarder2).sub_group(3, 0x11).exclude({0x10c}),
SpriteRequirement(EnemySprite.TutorialGuard).affix(),
SpriteRequirement(EnemySprite.LightningGate).affix().sub_group(3, 0x3f),
SpriteRequirement(EnemySprite.BlueGuard).aquaphobia().sub_group(1, [0xd, 0x49]),
SpriteRequirement(EnemySprite.GreenGuard).aquaphobia().sub_group(1, 0x49),
SpriteRequirement(EnemySprite.RedSpearGuard).aquaphobia().sub_group(1, [0xd, 0x49]),
SpriteRequirement(EnemySprite.BluesainBolt).aquaphobia().sub_group(0, 0x46).sub_group(1, [0xd, 0x49]),
SpriteRequirement(EnemySprite.UsainBolt).aquaphobia().sub_group(1, [0xd, 0x49]),
SpriteRequirement(EnemySprite.BlueArcher).sub_group(0, 0x48).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.GreenBushGuard).sub_group(0, 0x48).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.RedJavelinGuard).sub_group(0, 0x46).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.RedBushGuard).sub_group(0, 0x46).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.BombGuard).sub_group(0, 0x46).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.GreenKnifeGuard).sub_group(1, 0x49).sub_group(2, 0x13),
SpriteRequirement(EnemySprite.Geldman).sub_group(2, 0x12).exclude({0x10c}),
SpriteRequirement(EnemySprite.Toppo).immune().sub_group(3, 0x11),
SpriteRequirement(EnemySprite.Popo).sub_group(1, 0x2c),
SpriteRequirement(EnemySprite.Popo2).sub_group(1, 0x2c),
SpriteRequirement(EnemySprite.ArmosStatue).sub_group(3, 0x10).exclude({0x10c}),
SpriteRequirement(EnemySprite.KingZora).affix().sub_group(3, 0x44),
SpriteRequirement(EnemySprite.ArmosKnight).exalt().sub_group(3, 0x1d),
SpriteRequirement(EnemySprite.Lanmolas).exalt().sub_group(3, 0x31),
SpriteRequirement(EnemySprite.FireballZora).immerse().no_drop().sub_group(2, [0xc, 0x18]), # .uw_skip() test
SpriteRequirement(EnemySprite.Zora).sub_group(2, 0xc).sub_group(3, 0x44).uw_skip(),
SpriteRequirement(EnemySprite.DesertStatue).affix().sub_group(2, 0x12),
SpriteRequirement(EnemySprite.Crab).sub_group(2, 0xc),
SpriteRequirement(EnemySprite.LostWoodsBird).affix().sub_group(2, 0x37).sub_group(3, 0x36),
SpriteRequirement(EnemySprite.LostWoodsSquirrel).affix().sub_group(2, 0x37).sub_group(3, 0x36),
SpriteRequirement(EnemySprite.SparkCW).immune().sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.SparkCCW).immune().sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.RollerVerticalUp).immune().sub_group(2, 0x27).exclude(NoBeamosOrTrapRooms),
SpriteRequirement(EnemySprite.RollerVerticalDown).immune().sub_group(2, 0x27).exclude(NoBeamosOrTrapRooms),
SpriteRequirement(EnemySprite.RollerHorizontalLeft).immune().sub_group(2, 0x27).exclude(NoBeamosOrTrapRooms),
SpriteRequirement(EnemySprite.RollerHorizontalRight).immune().sub_group(2, 0x27).exclude(NoBeamosOrTrapRooms),
SpriteRequirement(EnemySprite.Beamos).no_drop().sub_group(1, 0x2c).exclude(NoBeamosOrTrapRooms),
SpriteRequirement(EnemySprite.MasterSword).affix().sub_group(2, 0x37).sub_group(3, 0x36),
SpriteRequirement(EnemySprite.DebirandoPit).sub_group(0, 0x2f), # skip
SpriteRequirement(EnemySprite.Debirando).sub_group(0, 0x2f), # skip
SpriteRequirement(EnemySprite.ArcheryNpc).affix().sub_group(0, 0x4b),
SpriteRequirement(EnemySprite.WallCannonVertLeft).affix().sub_group(0, 0x2f),
SpriteRequirement(EnemySprite.WallCannonVertRight).affix().sub_group(0, 0x2f),
SpriteRequirement(EnemySprite.WallCannonHorzTop).affix().sub_group(0, 0x2f),
SpriteRequirement(EnemySprite.WallCannonHorzBottom).affix().sub_group(0, 0x2f),
SpriteRequirement(EnemySprite.BallNChain).sub_group(0, 0x46).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.CannonTrooper).sub_group(0, 0x46).sub_group(1, 0x49),
SpriteRequirement(EnemySprite.CricketRat).sub_group(2, [0x1c, 0x24]),
SpriteRequirement(EnemySprite.Snake).sub_group(2, [0x1c, 0x24]),
SpriteRequirement(EnemySprite.Keese).no_drop().sub_group(2, [0x1c, 0x24]),
SpriteRequirement(EnemySprite.Leever).sub_group(0, 0x2f),
SpriteRequirement(EnemySprite.FairyPondTrigger).affix().sub_group(3, 0x36),
SpriteRequirement(EnemySprite.UnclePriest).affix().sub_group(0, [0x47, 0x51]),
SpriteRequirement(EnemySprite.RunningNpc).affix().group(6),
SpriteRequirement(EnemySprite.BottleMerchant).affix().group(6),
SpriteRequirement(EnemySprite.Zelda).affix(),
SpriteRequirement(EnemySprite.Grandma).affix().sub_group(0, 0x4b).sub_group(1, 0x4d).sub_group(2, 0x4a),
SpriteRequirement(EnemySprite.Agahnim).exalt().sub_group(0, 0x55).sub_group(1, [0x1a, 0x3d]).sub_group(2, 0x42)
.sub_group(3, 0x43),
SpriteRequirement(EnemySprite.FloatingSkull).sub_group(0, 0x1f).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.BigSpike).sub_group(3, [0x52, 0x53]).no_drop(),
SpriteRequirement(EnemySprite.FirebarCW).immune().sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.FirebarCCW).immune().sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.Firesnake).no_drop().sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.Hover).sub_group(2, 0x22), # .exclude(NoFlyingRooms), might be okay now
SpriteRequirement(EnemySprite.AntiFairyCircle).no_drop().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.GreenEyegoreMimic).sub_group(2, 0x2e),
SpriteRequirement(EnemySprite.RedEyegoreMimic).sub_group(2, 0x2e),
SpriteRequirement(EnemySprite.Kodongo).sub_group(2, 0x2a),
# SpriteRequirement(EnemySprite.YellowStalfos).sub_group(0, 0x1f), # doesn't spawn
SpriteRequirement(EnemySprite.Mothula).exalt().sub_group(2, 0x38).sub_group(3, 0x52),
SpriteRequirement(EnemySprite.SpikeBlock).immune().sub_group(3, [0x52, 0x53]).exclude(NoBeamosOrTrapRooms)
.exclude({0x28}), # why exclude sp entrance?
SpriteRequirement(EnemySprite.Gibdo).sub_group(2, 0x23),
SpriteRequirement(EnemySprite.Arrghus).exalt().sub_group(2, 0x39),
SpriteRequirement(EnemySprite.Arrghi).exalt().sub_group(2, 0x39),
SpriteRequirement(EnemySprite.Terrorpin).sub_group(2, 0x2a).exclude({0x10c}), # probably fine in mimic now
SpriteRequirement(EnemySprite.Blob).sub_group(1, 0x20),
SpriteRequirement(EnemySprite.Wallmaster).immune().ow_skip().sub_group(2, 0x23)
.exclude(WallmasterInvalidRooms),
SpriteRequirement(EnemySprite.StalfosKnight).sub_group(1, 0x20).exclude({0x10c}),
SpriteRequirement(EnemySprite.HelmasaurKing).exalt().sub_group(2, 0x3a).sub_group(3, 0x3e),
SpriteRequirement(EnemySprite.Bumper).immune().aquaphobia().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.LaserEyeLeft).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.LaserEyeRight).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.LaserEyeTop).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.LaserEyeBottom).affix().sub_group(3, [0x52, 0x53]),
SpriteRequirement(EnemySprite.Pengator).sub_group(2, 0x26),
SpriteRequirement(EnemySprite.Kyameron).no_drop().immerse().sub_group(2, 0x22),
SpriteRequirement(EnemySprite.Wizzrobe).sub_group(2, [0x25, 0x29]),
SpriteRequirement(EnemySprite.Zoro).sub_group(1, 0x20),
SpriteRequirement(EnemySprite.Babasu).sub_group(1, 0x20),
SpriteRequirement(EnemySprite.GroveOstritch).affix().sub_group(2, 0x4e),
SpriteRequirement(EnemySprite.GroveRabbit).affix(),
SpriteRequirement(EnemySprite.GroveBird).affix().sub_group(2, 0x4e),
SpriteRequirement(EnemySprite.Freezor).stasis().skip().sub_group(2, 0x26),
SpriteRequirement(EnemySprite.Kholdstare).exalt().sub_group(2, 0x3c),
SpriteRequirement(EnemySprite.KholdstareShell).exalt(),
SpriteRequirement(EnemySprite.FallingIce).exalt().sub_group(2, 0x3c),
SpriteRequirement(EnemySprite.BlueZazak).sub_group(2, 0x28),
SpriteRequirement(EnemySprite.RedZazak).sub_group(2, 0x28),
SpriteRequirement(EnemySprite.Stalfos).sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.GreenZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.BlueZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms),
SpriteRequirement(EnemySprite.Pikit).sub_group(3, 0x1b),
SpriteRequirement(EnemySprite.CrystalMaiden).affix(),
SpriteRequirement(EnemySprite.OldMan).affix().sub_group(2, 0x1c),
SpriteRequirement(EnemySprite.PipeDown).affix(),
SpriteRequirement(EnemySprite.PipeUp).affix(),
SpriteRequirement(EnemySprite.PipeRight).affix(),
SpriteRequirement(EnemySprite.PipeLeft).affix(),
SpriteRequirement(EnemySprite.GoodBee).affix().sub_group(0, 0x1f),
SpriteRequirement(EnemySprite.PurpleChest).affix().sub_group(3, 0x15),
SpriteRequirement(EnemySprite.BombShopGuy).affix().sub_group(1, 0x4d),
SpriteRequirement(EnemySprite.Kiki).affix().sub_group(3, 0x19),
SpriteRequirement(EnemySprite.BlindMaiden).affix(),
# dialog tester.sub_group(1, 0x2c)
SpriteRequirement(EnemySprite.BullyPinkBall).affix().sub_group(3, 0x14),
# shop keepers in complex thing below
SpriteRequirement(EnemySprite.Drunkard).affix().sub_group(0, 0x4f).sub_group(1, 0x4d).sub_group(2, 0x4a).
sub_group(3, 0x50),
SpriteRequirement(EnemySprite.Vitreous).exalt().sub_group(3, 0x3d),
SpriteRequirement(EnemySprite.Catfish).affix().sub_group(2, 0x18),
SpriteRequirement(EnemySprite.CutsceneAgahnim).affix().sub_group(0, 0x55).sub_group(1, 0x3d)
.sub_group(2, 0x42).sub_group(3, 0x43),
SpriteRequirement(EnemySprite.Boulder).affix().sub_group(3, 0x10),
SpriteRequirement(EnemySprite.Gibo).sub_group(2, 0x28),
SpriteRequirement(EnemySprite.Thief).immune().uw_skip().sub_group(0, [0xe, 0x15]),
SpriteRequirement(EnemySprite.Medusa).affix(),
SpriteRequirement(EnemySprite.FourWayShooter).affix(),
SpriteRequirement(EnemySprite.Pokey).sub_group(2, 0x27),
SpriteRequirement(EnemySprite.BigFairy).affix().sub_group(2, 0x39).sub_group(3, 0x36),
SpriteRequirement(EnemySprite.Tektite).sub_group(3, 0x10),
SpriteRequirement(EnemySprite.Chainchomp).immune().sub_group(2, 0x27),
SpriteRequirement(EnemySprite.TrinexxRockHead).exalt().sub_group(0, 0x40).sub_group(3, 0x3f),
SpriteRequirement(EnemySprite.TrinexxFireHead).exalt().sub_group(0, 0x40).sub_group(3, 0x3f),
SpriteRequirement(EnemySprite.TrinexxIceHead).exalt().sub_group(0, 0x40).sub_group(3, 0x3f),
SpriteRequirement(EnemySprite.Blind).exalt().sub_group(1, 0x2c).sub_group(2, 0x3b),
SpriteRequirement(EnemySprite.Swamola).no_drop().sub_group(3, 0x19),
SpriteRequirement(EnemySprite.Lynel).sub_group(3, 0x14),
SpriteRequirement(EnemySprite.BunnyBeam).no_drop().ow_skip(),
SpriteRequirement(EnemySprite.FloppingFish).uw_skip().immune(),
SpriteRequirement(EnemySprite.Stal),
SpriteRequirement(EnemySprite.Landmine).skip(),
SpriteRequirement(EnemySprite.DiggingGameNPC).affix().sub_group(1, 0x2a),
SpriteRequirement(EnemySprite.Ganon).exalt().sub_group(0, 0x21).sub_group(1, 0x41)
.sub_group(2, 0x45).sub_group(3, 0x33),
SpriteRequirement(EnemySprite.Faerie).immune(),
SpriteRequirement(EnemySprite.FakeMasterSword).immune().sub_group(3, 0x11),
SpriteRequirement(EnemySprite.MagicShopAssistant).affix().sub_group(0, 0x4b).sub_group(3, 0x5a),
SpriteRequirement(EnemySprite.SomariaPlatform).affix().sub_group(2, 0x27),
SpriteRequirement(EnemySprite.CastleMantle).affix().sub_group(0, 0x5d),
SpriteRequirement(EnemySprite.GreenMimic).sub_group(1, 0x2c).no_bush(),
SpriteRequirement(EnemySprite.RedMimic).sub_group(1, 0x2c).no_bush(),
SpriteRequirement(EnemySprite.MedallionTablet).affix().sub_group(2, 0x12),
# overlord requirements - encapsulated mostly in the required sheets
SpriteRequirement(2, 7).affix().sub_group(2, 46),
SpriteRequirement(3, 7).affix().sub_group(2, 46),
SpriteRequirement(5, 7).affix().sub_group(0, 31),
SpriteRequirement(6, 7).affix().sub_group(2, [28, 36]),
SpriteRequirement(7, 7).affix(),
SpriteRequirement(8, 7).affix().sub_group(1, 32),
SpriteRequirement(9, 7).affix().sub_group(2, 35),
SpriteRequirement(0xa, 7).affix().sub_group(3, 82),
SpriteRequirement(0xb, 7).affix().sub_group(3, 82),
SpriteRequirement(0x10, 7).affix().sub_group(2, 34),
SpriteRequirement(0x11, 7).affix().sub_group(2, 34),
SpriteRequirement(0x12, 7).affix().sub_group(2, 34),
SpriteRequirement(0x13, 7).affix().sub_group(2, 34),
SpriteRequirement(0x14, 7).affix(),
SpriteRequirement(0x15, 7).affix().sub_group(2, [37, 41]),
SpriteRequirement(0x16, 7).affix().sub_group(1, 32),
SpriteRequirement(0x17, 7).affix().sub_group(0, 31),
SpriteRequirement(0x18, 7).affix().sub_group(0, 31),
SpriteRequirement(0x19, 7).affix(),
SpriteRequirement(0x1a, 7).affix(),
]
simple = {(r.sprite, r.overlord): r for r in reqs}
shopkeeper = [
SpriteRequirement(EnemySprite.Shopkeeper).affix().sub_group(0, 75).sub_group(2, 74).sub_group(3, 90)
.allow({0xff, 0x112, 0x11f}),
SpriteRequirement(EnemySprite.Shopkeeper).affix().sub_group(0, 75).sub_group(1, 77).sub_group(2, 74)
.sub_group(3, 90).allow({0x10f, 0x110, 0x11f}),
SpriteRequirement(EnemySprite.Shopkeeper).affix().sub_group(0, 79).sub_group(2, 74).sub_group(3, 90)
.allow({0x118}),
SpriteRequirement(EnemySprite.Shopkeeper).affix().sub_group(0, 14).sub_group(2, 74).sub_group(3, 90)
.allow({0x123, 0x124}),
SpriteRequirement(EnemySprite.Shopkeeper).affix().sub_group(0, 14).sub_group(2, 74).sub_group(3, 80)
.allow({0x125, 0x100}),
SpriteRequirement(EnemySprite.Shopkeeper).affix().sub_group(0, 21).allow({0x11e}),
]
complex_r = {}
for req in shopkeeper:
for r in req.allowed_rooms:
complex_r[r] = req
simple[(EnemySprite.Shopkeeper, 0)] = complex_r
return simple
# sheet 1 and 1c have group 4 modified from vanilla for murahdahla
vanilla_sheets = [
(0x00, 0x49, 0x00, 0x00), (0x46, 0x49, 0x0C, 0x3F), (0x48, 0x49, 0x13, 0x3F), (0x46, 0x49, 0x13, 0x0E),
(0x48, 0x49, 0x0C, 0x11), (0x48, 0x49, 0x0C, 0x10), (0x4F, 0x49, 0x4A, 0x50), (0x0E, 0x49, 0x4A, 0x11),
(0x46, 0x49, 0x12, 0x00), (0x00, 0x49, 0x00, 0x50), (0x00, 0x49, 0x00, 0x11), (0x48, 0x49, 0x0C, 0x00),
(0x00, 0x00, 0x37, 0x36), (0x48, 0x49, 0x4C, 0x11), (0x5D, 0x2C, 0x0C, 0x44), (0x00, 0x00, 0x4E, 0x00),
(0x0F, 0x00, 0x12, 0x10), (0x00, 0x00, 0x00, 0x4C), (0x00, 0x0D, 0x17, 0x00), (0x16, 0x0D, 0x17, 0x1B),
(0x16, 0x0D, 0x17, 0x14), (0x15, 0x0D, 0x17, 0x15), (0x16, 0x0D, 0x18, 0x19), (0x16, 0x0D, 0x17, 0x19),
(0x16, 0x0D, 0x00, 0x00), (0x16, 0x0D, 0x18, 0x1B), (0x0F, 0x49, 0x4A, 0x11), (0x4B, 0x2A, 0x5C, 0x15),
(0x16, 0x49, 0x17, 0x3F), (0x00, 0x00, 0x00, 0x15), (0x16, 0x0D, 0x17, 0x10), (0x16, 0x49, 0x12, 0x00),
(0x16, 0x49, 0x0C, 0x11), (0x00, 0x00, 0x12, 0x10), (0x16, 0x0D, 0x00, 0x11), (0x16, 0x49, 0x0C, 0x00),
(0x16, 0x0D, 0x4C, 0x11), (0x0E, 0x0D, 0x4A, 0x11), (0x16, 0x1A, 0x17, 0x1B), (0x4F, 0x34, 0x4A, 0x50),
(0x35, 0x4D, 0x65, 0x36), (0x4A, 0x34, 0x4E, 0x00), (0x0E, 0x34, 0x4A, 0x11), (0x51, 0x34, 0x5D, 0x59),
(0x4B, 0x49, 0x4C, 0x11), (0x2D, 0x00, 0x00, 0x00), (0x5D, 0x00, 0x12, 0x59), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x47, 0x49, 0x2B, 0x2D), (0x46, 0x49, 0x1C, 0x52), (0x00, 0x49, 0x1C, 0x52), (0x5D, 0x49, 0x00, 0x52),
(0x46, 0x49, 0x13, 0x52), (0x4B, 0x4D, 0x4A, 0x5A), (0x47, 0x49, 0x1C, 0x52), (0x4B, 0x4D, 0x39, 0x36),
(0x1F, 0x2C, 0x2E, 0x52), (0x1F, 0x2C, 0x2E, 0x1D), (0x2F, 0x2C, 0x2E, 0x52), (0x2F, 0x2C, 0x2E, 0x31),
(0x1F, 0x1E, 0x30, 0x52), (0x51, 0x49, 0x13, 0x00), (0x4F, 0x49, 0x13, 0x50), (0x4F, 0x4D, 0x4A, 0x50),
(0x4B, 0x49, 0x4C, 0x2B), (0x1F, 0x20, 0x22, 0x53), (0x55, 0x3D, 0x42, 0x43), (0x1F, 0x1E, 0x23, 0x52),
(0x1F, 0x1E, 0x39, 0x3A), (0x1F, 0x1E, 0x3A, 0x3E), (0x1F, 0x1E, 0x3C, 0x3D), (0x40, 0x1E, 0x27, 0x3F),
(0x55, 0x1A, 0x42, 0x43), (0x1F, 0x1E, 0x2A, 0x52), (0x1F, 0x1E, 0x38, 0x52), (0x1F, 0x20, 0x28, 0x52),
(0x1F, 0x20, 0x26, 0x52), (0x1F, 0x2C, 0x25, 0x52), (0x1F, 0x20, 0x27, 0x52), (0x1F, 0x1E, 0x29, 0x52),
(0x1F, 0x2C, 0x3B, 0x52), (0x46, 0x49, 0x24, 0x52), (0x21, 0x41, 0x45, 0x33), (0x1F, 0x2C, 0x28, 0x31),
(0x1F, 0x0D, 0x29, 0x52), (0x1F, 0x1E, 0x27, 0x52), (0x1F, 0x20, 0x27, 0x53), (0x48, 0x49, 0x13, 0x52),
(0x0E, 0x1E, 0x4A, 0x50), (0x1F, 0x20, 0x26, 0x53), (0x15, 0x00, 0x00, 0x00), (0x1F, 0x00, 0x2A, 0x52),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x08), (0x5D, 0x49, 0x00, 0x52), (0x55, 0x49, 0x42, 0x43),
(0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50),
(0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x56, 0x57, 0x50), (0x61, 0x62, 0x63, 0x50),
(0x61, 0x62, 0x63, 0x50), (0x61, 0x56, 0x57, 0x50), (0x61, 0x56, 0x63, 0x50), (0x61, 0x56, 0x57, 0x50),
(0x61, 0x56, 0x33, 0x50), (0x61, 0x56, 0x57, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50)
]
required_boss_sheets = {EnemySprite.ArmosKnight: 9, EnemySprite.Lanmolas: 11, EnemySprite.Moldorm: 12,
EnemySprite.HelmasaurKing: 21, EnemySprite.Arrghus: 20, EnemySprite.Mothula: 26,
EnemySprite.Blind: 32, EnemySprite.Kholdstare: 22, EnemySprite.Vitreous: 22,
EnemySprite.TrinexxRockHead: 23}
class SpriteSheet:
def __init__(self, id, default_sub_groups):
self.id = id
self.sub_groups = list(default_sub_groups)
self.locked = [False] * 4
self.room_set = set()
def dungeon_id(self):
return self.id + 0x40
def valid_sprite(self, requirement):
if requirement.groups and self.id not in requirement.groups:
return False
for idx, sub in enumerate(self.sub_groups):
if requirement.sub_groups[idx] and sub not in requirement.sub_groups[idx]:
return False
return True
def lock_sprite_in(self, sprite):
for i, options in sprite.sub_groups.items():
self.locked[i] = len(options) > 0
def add_sprite_to_sheet(self, groups, rooms=None):
for idx, g in enumerate(groups):
if g is not None:
self.sub_groups[idx] = g
self.locked[idx] = True
if rooms is not None:
self.room_set.update(rooms)
def write_to_rom(self, rom, base_address):
rom.write_bytes(base_address + self.id * 4, self.sub_groups)
def __str__(self):
return f'{self.id} => [{", ".join([str(x) for x in self.sub_groups])}]'
# convert to dungeon id
def did(n):
return n + 0x40
def init_sprite_sheets(requirements):
sheets = {id: SpriteSheet(id, def_sheet) for id, def_sheet in enumerate(vanilla_sheets)}
# wait until bosses are randomized to determine which are randomized
for sprite, sheet_num in required_boss_sheets.items():
sheet = sheets[did(sheet_num)] # convert to dungeon sheet id
boss_sprite = requirements[(sprite, 0)]
sheet.lock_sprite_in(boss_sprite)
return sheets
def setup_required_dungeon_groups(sheets, data_tables):
sheets[did(1)].add_sprite_to_sheet([None, None, 28, None], {0xe4, 0xf0}) # old man
# various npcs
sheets[did(5)].add_sprite_to_sheet([75, 77, 74, 90], {0xf3, 0xff, 0x109, 0x10e, 0x10f, 0x110, 0x111, 0x112,
0x11a, 0x11c, 0x11f, 0x122})
sheets[did(7)].add_sprite_to_sheet([75, 77, 57, 54], {0x8, 0x2c, 0x114, 0x115, 0x116}) # big fairies
sheets[did(13)].add_sprite_to_sheet([81, None, None, None], {0x55, 0x102, 0x104}) # uncle, sick kid
sheets[did(14)].add_sprite_to_sheet([71, 73, 76, 80], {0x12, 0x105, 0x10a}) # wisemen
sheets[did(15)].add_sprite_to_sheet([79, 77, 74, 80], {0xf4, 0xf5, 0x101, 0x103, 0x106, 0x118, 0x119}) # more npcs
sheets[did(18)].add_sprite_to_sheet([85, 61, 66, 67], {0x20, 0x30}) # aga alter, aga1
sheets[did(24)].add_sprite_to_sheet([85, 26, 66, 67], {0xd}) # aga2
sheets[did(34)].add_sprite_to_sheet([33, 65, 69, 51], {0}) # ganon
sheets[did(40)].add_sprite_to_sheet([14, None, 74, 80], {0x124, 0x125, 0x126}) # fairy + rupee npcs
sheets[did(9)].add_sprite_to_sheet([None, None, None, 29], {0xe3}) # magic bat
sheets[did(28)].add_sprite_to_sheet([None, None, 38, 82], {0xe, 0x7e, 0x8e, 0x9e}) # freezors
sheets[did(3)].add_sprite_to_sheet([93, None, None, None], {0x51}) # mantle
sheets[did(42)].add_sprite_to_sheet([21, None, None, None], {0x11e}) # hype cave
sheets[did(10)].add_sprite_to_sheet([47, None, 46, None], {0x5c, 0x75, 0xb9, 0xd9}) # cannonballs
sheets[did(37)].add_sprite_to_sheet([None, None, 39, 82], {0x24, 0xb4, 0xb5, 0xc6, 0xc7, 0xd6}) # somaria platforms
free_sheet_reqs = [
([None, 77, None, 21], [0x121]), # smithy
([None, None, None, 80], [0x108]), # chicken house
([14, 30, None, None], [0x123]), # mini moldorm (shutter door)
([None, None, 34, None], [0x36, 0x46, 0x66]), # pirogusu spawners
([None, 32, None, None], [0x9f]), # babasu spawners
([31, None, None, None], [0x7f]), # force baris
([None, None, 35, None], [0x39, 0x49]), # wallmasters
# bumpers - why the split - because of other requirements -
([None, None, None, (82, 83)], [0x17, 0x2a, 0x4c, 0x59, 0x67, 0x7e, 0x8b, 0xeb, 0xfb]),
# crystal switches - split for some reason
([None, None, None, (82, 83)], [0xb, 0x13, 0x1b, 0x1e, 0x2a, 0x2b, 0x31, 0x5b, 0x6b, 0x77, 0x8b,
0x91, 0x92, 0x9b, 0x9d, 0xa1, 0xab, 0xbf, 0xc4, 0xef]),
# laser eyes - split for some reason
([None, None, None, (82, 83)], [0x13, 0x23, 0x96, 0xa5, 0xc5, 0xd5]),
# statues - split for some reason
([None, None, None, (82, 83)], [0x26, 0x2b, 0x40, 0x4a, 0x6b, 0x7b]),
([None, None, None, 83], [0x43, 0x63, 0x87]), # tile rooms
# non-optional
([None, None, None, 82], [0x58, 0x8c, 0x10b]), # pull switches
([None, None, (28, 36), 82], [0x2, 0x64]), # pull switches (snakes)
([None, None, None, 82], [0x1a, 0x3d, 0x44, 0x5e, 0x7c, 0x95, 0xc3]), # collapsing bridges
([None, None, None, 83], [0x3f, 0xce]), # pull tongue
([None, None, None, 83], [0x35, 0x37]), # swamp drains
([None, None, 34, None], [0x28]), # tektike forced? - spawn chest
([None, None, 37, None], [0x97]), # wizzrobe spawner - in middle of room...
# combined
([None, 32, None, (82, 83)], [0x3e]), # babasu spawners + crystal switch
([None, 32, None, 83], [0x4]), # zoro spawners + crystal switch + pull switch
([None, None, 35, 82], [0x56]), # wallmaster + collasping bridge
([None, None, 35, (82, 83)], [0x57, 0x68]), # wallmaster + statue and wallmaster + bumpers
([None, None, 34, 83], [0x76]), # swamp drain + pirogusu spawners
([None, None, 35, 83], [0x8d]), # wallmaster + tile room
([None, None, None, 83], [0xb6, 0xc1]), # tile room + crystal switch
# allow some sprites / increase odds:
([72, 73, None, None], []), # allow for blue archer + greenbush
([None, 73, 19, None], []), # allow for green knife guard
([None, None, 12, 68], []), # increase odds for zora
([22, None, 23, None], []), # increase odds for snapdragon
]
data_tables.room_requirements = {}
# find home for the free_sheet_reqs
for pair in free_sheet_reqs:
groups, room_list = pair
for room in room_list:
data_tables.room_requirements[room] = groups
find_matching_sheet(groups, sheets, range(65, 124), room_list)
# RandomizeRooms(optionFlags);
# roomCollection.LoadRooms()
# roomCollection.RandomizeRoomSpriteGroups(spriteGroupCollection, optionFlags);
# more stuff
sub_group_choices = {
0: [22, 31, 47, 14],
1: [44, 30, 32], # 73, 13
2: [12, 18, 23, 24, 28, 46, 34, 35, 39, 40, 38, 41, 36, 37, 42],
3: [17, 16, 27, 20, 82, 83, 25] # 25 for Swamola
}
# 70, 72 for guards
# 0: 72 specifically for BlueArcher/GreenBush (but needs combination)
# 0: 70 for guards but needs combination
# 2: 19 for green knife guard, but needs combination
# 3: 68 for Zora, but needs combination
def combine_req(sub_groups, requirement):
for i in range(0, 4):
if requirement.sub_groups[i]:
if len(sub_groups[i]) == 0:
sub_groups[i].update(requirement.sub_groups[i])
else:
if len(sub_groups[i].intersection(requirement.sub_groups[i])) == 0:
raise IncompatibleEnemyException
sub_groups[i].intersection_update(requirement.sub_groups[i])
def setup_custom_enemy_sheets(custom_enemies, sheets, data_tables, sheet_range, uw=True):
requirements = data_tables.sprite_requirements
for room_id, enemy_map in custom_enemies.items():
if uw:
original_list = data_tables.uw_enemy_table.room_map[room_id]
else:
original_list = data_tables.ow_enemy_table[room_id]
sub_groups_choices = [set(), set(), set(), set()]
for idx, sprite in enumerate(original_list):
if idx in enemy_map:
key = (sprite_translation[enemy_map[idx]], 0)
if key not in requirements:
continue
req = requirements[key]
try:
combine_req(sub_groups_choices, req)
except IncompatibleEnemyException:
logging.getLogger('').warning(f'Incompatible enemy: {hex(room_id)}:{idx} {enemy_map[idx]}')
else:
sprite_secondary = 0 if sprite.sub_type != SpriteType.Overlord else sprite.sub_type
key = (sprite.kind, sprite_secondary)
if key not in requirements:
continue
req = requirements[key]
if isinstance(req, dict):
req = req[room_id]
if req.static or not req.can_randomize:
try:
combine_req(sub_groups_choices, req)
except IncompatibleEnemyException:
raise IncompatibleEnemyException(f'Incompatible enemy: {hex(room_id)}:{idx} {str(req)}')
sheet_req = [None if not x else tuple(x) for x in sub_groups_choices]
find_matching_sheet(sheet_req, sheets, sheet_range, [room_id], True)
def randomize_underworld_sprite_sheets(sheets, data_tables, custom_enemies):
setup_required_dungeon_groups(sheets, data_tables)
setup_custom_enemy_sheets(custom_enemies, sheets, data_tables, range(65, 124), True)
for num in range(65, 124): # sheets 0x41 to 0x7B inclusive
sheet = sheets[num]
# if not sheet.locked[1] and num in [65, 66, 67, 68]: # guard stuff, kind of
# sheet.locked[1] = True
# sheet.sub_groups[1] = random.choice([13, 73])
free_slots = [idx for idx in range(0, 4) if not sheet.locked[idx]]
while free_slots:
choices = [c for c in data_tables.sheet_choices if all(slot in free_slots for slot in c.slots)]
weights = [c.weight for c in choices]
choice = random.choices(choices, weights, k=1)[0]
for idx, val in choice.assignments.items():
v = random.choice(val) if isinstance(val, list) else val
sheet.sub_groups[idx] = v
sheet.locked[idx] = True
free_slots = [idx for idx in range(0, 4) if not sheet.locked[idx]]
def setup_required_overworld_groups(sheets):
sheets[7].add_sprite_to_sheet([None, None, 74, None], {0x2}) # lumberjacks
sheets[16].add_sprite_to_sheet([None, None, 18, 16], {0x3, 0x93}) # WDM (pre/post-Aga)
sheets[7].add_sprite_to_sheet([None, None, None, 17], {0xA, 0x9A}) # DM Foothills? (pre/post-Aga)
sheets[4].add_sprite_to_sheet([None, None, None, None], {0xF, 0x9F}) # Waterfall of wishing (pre/post-Aga)
sheets[3].add_sprite_to_sheet([None, None, None, 14], {0x14, 0xA4}) # Graveyard (pre/post-Aga)
sheets[1].add_sprite_to_sheet([None, None, 76, 0x3F], {0x1B, 0xAB}) # Hyrule Castle (pre/post-Aga)
## group 0 set to 0x48 for tutortial guards
## group 1 & 2 set for green knife guards (and probably normal green guard)
## group 3 set for lightning gate
sheets[2].add_sprite_to_sheet([0x48, 0x49, 0x13, 0x3F], {}) # Hyrule Castle - rain state
# Smithy/Race/Kak (pre/post-Aga)
sheets[6].add_sprite_to_sheet([0x4F, 0x49, 0x4A, 0x50], {0x18, 0x22, 0x28, 0xA8, 0xB2, 0xB8})
sheets[8].add_sprite_to_sheet([None, None, 18, None], {0x30, 0xC0}) # Desert (pre/post-Aga)
sheets[10].add_sprite_to_sheet([None, None, None, 17], {0x3A, 0xCA}) # M-rock (pre/post-Aga)
sheets[22].add_sprite_to_sheet([None, None, 24, None], {0x4F}) # Catfish
sheets[21].add_sprite_to_sheet([None, None, None, 21], {0x62, 0x69}) # Smith DW/VoO South
sheets[27].add_sprite_to_sheet([None, 42, None, None], {0x68}) # Dig Game
sheets[13].add_sprite_to_sheet([None, None, 76, None], {0x16, 0xA6}) # Witch hut (pre/post-Aga)
#sheets[29].add_sprite_to_sheet([None, 77, None, 21], {0x69}) # VoO South
sheets[15].add_sprite_to_sheet([None, None, 78, None], {0x2A, 0xBA}) # Haunted Grove (pre/post-Aga)
sheets[17].add_sprite_to_sheet([None, None, None, 76], {0x6A}) # Stumpy
sheets[12].add_sprite_to_sheet([None, None, 55, 54], {0x80}) # Specials
sheets[14].add_sprite_to_sheet([None, None, 12, 68], {0x81}) # Zora's Domain
sheets[26].add_sprite_to_sheet([15, None, None, None], {0x92}) # Lumberjacks post-Aga
sheets[23].add_sprite_to_sheet([None, None, None, 25], {0x5E}) # PoD
sheets[19].add_sprite_to_sheet([None, 26, None, None], {0x5B}) # Pyramid post-Aga2 bat crash
free_sheet_reqs = [
[None, None, None, 0x14], # bully+pink ball needs this
[72, 73, None, None], # allow for blue archer + green bush
[None, 73, 19, None], # allow for green knife guard
[22, None, 23, None], # increase odds for snapdragon
[70, 73, None, None], # guards group (ballnchain, redbush, redjav, cannon, bomb, bluesain
[None, None, None, 0x15], # an option for talking trees
[None, None, None, 0x1B], # an option for talking trees
]
for group in free_sheet_reqs:
find_matching_sheet(group, sheets, range(1, 64))
class NoMatchingSheetException(Exception):
pass
class IncompatibleEnemyException(Exception):
pass
def find_matching_sheet(groups, sheets, search_sheets, room_list=None, lock_match=False):
possible_sheets = []
found_match = False
for num in search_sheets:
if num in {6, 65, 69, 71, 78, 79, 82, 88, 98}: # these are not useful sheets for randomization
continue
sheet = sheets[num]
valid = True
match = True
for idx, value in enumerate(groups):
if value is not None and sheet.locked[idx]:
valid = False
if (sheet.sub_groups[idx] not in value if isinstance(value, tuple)
else value != sheet.sub_groups[idx]):
match = False
elif value is not None:
match = False
if match:
found_match = True
if lock_match and room_list is not None:
sheet.room_set.update(room_list)
break
if valid:
possible_sheets.append(sheet)
if not found_match:
if len(possible_sheets) == 0:
raise NoMatchingSheetException
chosen_sheet = random.choice(possible_sheets)
chosen_groups = [(random.choice(g) if isinstance(g, tuple) else g) for g in groups]
chosen_sheet.add_sprite_to_sheet(chosen_groups, room_list)
def randomize_overworld_sprite_sheets(sheets, data_tables, custom_enemies):
setup_required_overworld_groups(sheets)
setup_custom_enemy_sheets(custom_enemies, sheets, data_tables, range(1, 64), False)
for num in range(1, 64): # sheets 0x1 to 0x3F inclusive
sheet = sheets[num]
if num == 6: # skip this group - it is locked for kakariko
continue
free_slots = [idx for idx in range(0, 4) if not sheet.locked[idx]]
while free_slots:
choices = [c for c in data_tables.sheet_choices if all(slot in free_slots for slot in c.slots)]
weights = [c.weight for c in choices]
choice = random.choices(choices, weights, k=1)[0]
for idx, val in choice.assignments.items():
v = random.choice(val) if isinstance(val, list) else val
sheet.sub_groups[idx] = v
sheet.locked[idx] = True
free_slots = [idx for idx in range(0, 4) if not sheet.locked[idx]]
class SheetChoice:
def __init__(self, slots, assignments, weight):
self.slots = slots
self.assignments = assignments
self.weight = weight