Fixed enemizer sprite gfx for soldier enemies falling down pits Remove unnecessary sprite sheet subgroup for somaria platforms Freeing up unnecessary sprite sheet subgroups for old man screens Fix paradox cave and kakariko shop gfx in enemizer Fix swamp drain gfx in enemizer Fix bat crash sprite gfx in enemizer Remove unused subgroups and consolidate purple chest/frog sprite sheets Remove invalid OW IDs from sprite sheets
803 lines
44 KiB
Python
803 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}
|
|
PitRooms = {0x17, 0x1a, 0x2a, 0x31, 0x3c, 0x3d, 0x40, 0x44, 0x49, 0x4e, 0x56, 0x58, 0x5c, 0x67, 0x72,
|
|
0x7b, 0x7c, 0x7d, 0x7f, 0x82, 0x8b, 0x8d, 0x95, 0x96, 0x9b, 0x9c, 0x9d, 0x9e, 0xa0, 0xa5,
|
|
0xaf, 0xbc, 0xc0, 0xc5, 0xc6, 0xd1, 0xd5, 0xe7, 0xe8, 0xee, 0xf0, 0xf1, 0xfb, 0x123}
|
|
|
|
# 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]).exclude(PitRooms),
|
|
SpriteRequirement(EnemySprite.BlueGuard).aquaphobia().sub_group(1, [0xd, 0x49]).sub_group(2, [0x29, 0x13]),
|
|
SpriteRequirement(EnemySprite.GreenGuard).aquaphobia().sub_group(1, 0x49).exclude(PitRooms),
|
|
SpriteRequirement(EnemySprite.GreenGuard).aquaphobia().sub_group(1, 0x49).sub_group(2, 0x13),
|
|
SpriteRequirement(EnemySprite.RedSpearGuard).aquaphobia().sub_group(1, [0xd, 0x49]).exclude(PitRooms),
|
|
SpriteRequirement(EnemySprite.RedSpearGuard).aquaphobia().sub_group(1, [0xd, 0x49]).sub_group(2, [0x29, 0x13]),
|
|
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
|