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(0, 0x46).sub_group(1, 0x49).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([70, 73, 28, 82], {0xe4, 0xf0}) # old man # various npcs sheets[did(5)].add_sprite_to_sheet([75, 77, 74, 90], {0xf3, 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([31, None, 39, 82], {0x24, 0xb4, 0xb5, 0xc6, 0xc7, 0xd6}) # somaria platforms # not sure 31 is needed above free_sheet_reqs = [ ([75, None, None, None], [0xff, 0x11f]), # shopkeepers ([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, 0x76]), # 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, 0x76]), # 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([21, None, None, 21], {0x62}) # Smith DW 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 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