Overworld enemizer work

This commit is contained in:
aerinon
2022-10-25 09:14:19 -06:00
parent 1529ec9473
commit 57c479e1c2
11 changed files with 1494 additions and 61 deletions

View File

@@ -37,8 +37,6 @@ class EnemyStats:
# Raven light/dark world
class EnemySprite(FastEnum):
Raven = 0x00
Vulture = 0x01
@@ -84,7 +82,7 @@ class EnemySprite(FastEnum):
TelepathicTile = 0x2d
FluteKid = 0x2e
RaceGameLady = 0x2f
RaceGameGuy = 0x30
FortuneTeller = 0x31
ArgueBros = 0x32
RupeePull = 0x33
@@ -156,6 +154,7 @@ class EnemySprite(FastEnum):
BottleMerchant = 0x75
Zelda = 0x76
Grandma = 0x78
Bee = 0x79
Agahnim = 0x7a
FloatingSkull = 0x7c
BigSpike = 0x7d
@@ -203,7 +202,7 @@ class EnemySprite(FastEnum):
BlueZirro = 0xa9
Pikit = 0xaa
CrystalMaiden = 0xab
# ... OW
Apple = 0xac
OldMan = 0xad
PipeDown = 0xae
PipeUp = 0xaf
@@ -242,17 +241,29 @@ class EnemySprite(FastEnum):
BunnyBeam = 0xd1
FloppingFish = 0xd2
Stal = 0xd3 # alive skull rock?
Landmine = 0xd4
DiggingGameNPC = 0xd5
Ganon = 0xd6
SmallHeart = 0xd8
BlueRupee = 0xda
RedRupee = 0xdb
BombRefill1 = 0xdc
BombRefill4 = 0xdd
BombRefill8 = 0xde
LargeMagic = 0xe0
Faerie = 0xe3
SmallKey = 0xe4
Mushroom = 0xe7
FakeMasterSword = 0xe8
MagicShopAssistant = 0xe9
HeartPiece = 0xeb
SomariaPlatform = 0xed
CastleMantle = 0xee
MedallionTablet = 0xf2
PositionTarget = 0xf3
Boulders = 0xf4
class SpriteType(FastEnum):
@@ -488,10 +499,13 @@ class Sprite(object):
self.drop_item_kind = drop_item_kind
self.location = None
self.original_address = None
def copy(self):
return Sprite(self.super_tile, self.kind, self.sub_type, self.layer, self.tile_x, self.tile_y, self.region,
self.drops_item, self.drop_item_kind)
sprite = Sprite(self.super_tile, self.kind, self.sub_type, self.layer, self.tile_x, self.tile_y, self.region,
self.drops_item, self.drop_item_kind)
sprite.original_address = self.original_address
return sprite
def sprite_data(self):
data = [(self.layer << 7) | ((self.sub_type & 0x18) << 2) | self.tile_y,
@@ -506,6 +520,9 @@ class Sprite(object):
data.append(code)
return data
def sprite_data_ow(self):
return [self.tile_y, self.tile_x, self.kind]
# map of super_tile to list of Sprite objects:
vanilla_sprites = {}
@@ -519,6 +536,8 @@ def create_sprite(super_tile, kind, sub_type, layer, tile_x, tile_y, region=None
def init_vanilla_sprites():
if vanilla_sprites:
return
create_sprite(0x0000, EnemySprite.Ganon, 0x00, 0, 0x17, 0x05, 'Pyramid')
create_sprite(0x0002, EnemySprite.CricketRat, 0x00, 1, 0x12, 0x05, 'Sewers Yet More Rats')
create_sprite(0x0002, EnemySprite.CricketRat, 0x00, 1, 0x15, 0x06, 'Sewers Yet More Rats')
@@ -2066,7 +2085,6 @@ layered_oam_rooms = {
class EnemyTable:
def __init__(self):
self.room_map = defaultdict(list)
self.multiworld_count = 0
def write_sprite_data_to_rom(self, rom):
pointer_address = snes_to_pc(0x09D62E)

View File

@@ -44,6 +44,14 @@ class Room:
rom.write_bytes(address + offset, [0xFF, 0xFF])
return door_start, offset + 2 # how many bytes were written
def find_all_pots(self):
pots = []
pots.extend([x for x in self.layer1 if x.data[2] == 0xFA])
pots.extend([x for x in self.layer2 if x.data[2] == 0xFA])
pots.extend([x for x in self.layer3 if x.data[2] == 0xFA])
return pots
Room0006 = Room([0xE1, 0x00],
[RoomObject(0x1FA15C, [0x1B, 0xA3, 0xC8]),
@@ -405,6 +413,93 @@ Room00C8 = Room([0xE1, 0x00],
Room00DE = Room([0xE4, 0x00], [], [RoomObject(0x1FCAE5, [0xAD, 0x21, 0xF9])], [])
Room010C = Room([0xE0, 0x08],
[RoomObject(0x03F25D, [0xFC, 0x62, 0x00]),
RoomObject(0x03F260, [0x19, 0x33, 0x61]),
RoomObject(0x03F263, [0xFC, 0x66, 0x81]),
RoomObject(0x03F266, [0x29, 0x22, 0x01]),
RoomObject(0x03F269, [0xFD, 0x62, 0x02]),
RoomObject(0x03F26C, [0x59, 0x33, 0x62]),
RoomObject(0x03F26F, [0xFD, 0x66, 0x83]),
RoomObject(0x03F272, [0xFC, 0x89, 0x00]),
RoomObject(0x03F275, [0x22, 0xA1, 0x61]),
RoomObject(0x03F278, [0xFC, 0x8E, 0x81]),
RoomObject(0x03F27B, [0x90, 0xCA, 0x0E]),
RoomObject(0x03F27E, [0xD0, 0xE6, 0x10]),
RoomObject(0x03F281, [0xFE, 0x41, 0x00]),
RoomObject(0x03F284, [0x93, 0x23, 0x61]),
RoomObject(0x03F287, [0x92, 0x98, 0x61]),
RoomObject(0x03F28A, [0xFF, 0x81, 0x02]),
RoomObject(0x03F28D, [0xE3, 0x23, 0x62]),
RoomObject(0x03F290, [0xE2, 0x98, 0x62]),
RoomObject(0x03F293, [0xFE, 0x71, 0xC8]),
RoomObject(0x03F296, [0xAD, 0x1C, 0x03]),
RoomObject(0x03F299, [0xFF, 0x51, 0xCA]),
RoomObject(0x03F29C, [0x9D, 0x2D, 0x63]),
RoomObject(0x03F29F, [0xFE, 0x75, 0x89]),
RoomObject(0x03F2A2, [0xAD, 0x58, 0x04]),
RoomObject(0x03F2A5, [0xD5, 0x2D, 0x64]),
RoomObject(0x03F2A8, [0xFF, 0x55, 0x8B]),
RoomObject(0x03F2AB, [0xFE, 0x78, 0x08]),
RoomObject(0x03F2AE, [0xAD, 0x80, 0x03]),
RoomObject(0x03F2B1, [0xFF, 0x58, 0x0A]),
RoomObject(0x03F2B4, [0x9C, 0x91, 0x63]),
RoomObject(0x03F2B7, [0xFE, 0x7A, 0x09]),
RoomObject(0x03F2BA, [0xAD, 0xA0, 0x04]),
RoomObject(0x03F2BD, [0xFF, 0x5A, 0x0B]),
RoomObject(0x03F2C0, [0xD4, 0x91, 0x64]),
RoomObject(0x03F2C3, [0x2C, 0x2E, 0xDC]),
RoomObject(0x03F2C6, [0x3D, 0x3E, 0xF9]),
RoomObject(0x03F2C9, [0x32, 0xA9, 0xF9]),
RoomObject(0x03F2CC, [0x3A, 0xA9, 0xF9]),
RoomObject(0x03F2CF, [0x42, 0xA9, 0xF9]),
RoomObject(0x03F2D2, [0x4A, 0xA9, 0xF9]),
RoomObject(0x03F2D5, [0x57, 0x9E, 0x69]),
RoomObject(0x03F2D8, [0x54, 0xDC, 0x69]),
RoomObject(0x03F2DB, [0x38, 0xC8, 0x89]),
RoomObject(0x03F2DE, [0x58, 0x9C, 0x89]),
RoomObject(0x03F2E1, [0x58, 0xA8, 0x89]),
RoomObject(0x03F2E4, [0x58, 0xB4, 0x89]),
RoomObject(0x03F2E7, [0x58, 0xC0, 0x89]),
RoomObject(0x03F2EA, [0x58, 0xCC, 0x89]),
RoomObject(0x03F2ED, [0x58, 0xD8, 0x89]),
RoomObject(0x03F2F0, [0x58, 0xE4, 0x89]),
RoomObject(0x03F2F3, [0xAA, 0x2E, 0xC8]),
RoomObject(0x03F2F6, [0xAA, 0x29, 0x3F]),
RoomObject(0x03F2F9, [0xAA, 0x2E, 0x79]),
RoomObject(0x03F2FC, [0xAA, 0x59, 0x40]),
RoomObject(0x03F2FF, [0xD6, 0x2E, 0x7A]),
RoomObject(0x03F302, [0xAA, 0x90, 0xC8]),
RoomObject(0x03F305, [0xAA, 0x8D, 0x3F]),
RoomObject(0x03F308, [0xA8, 0x93, 0x79]),
RoomObject(0x03F30B, [0xAA, 0xA1, 0x40]),
RoomObject(0x03F30E, [0xD4, 0x93, 0x7A]),
RoomObject(0x03F311, [0xB9, 0x5B, 0xF9]),
RoomObject(0x03F314, [0xB9, 0xA3, 0xF9]),
RoomObject(0x03F317, [0x9C, 0x68, 0x22]),
RoomObject(0x03F31A, [0x9C, 0x6A, 0x69]),
RoomObject(0x03F31D, [0x9C, 0x7C, 0x22]),
RoomObject(0x03F320, [0xD4, 0x7C, 0x22]),
RoomObject(0x03F323, [0x9C, 0xB0, 0x22]),
RoomObject(0x03F326, [0xD4, 0xB0, 0x22]),
RoomObject(0x03F329, [0xB3, 0x73, 0xFA]),
RoomObject(0x03F32C, [0xCD, 0x68, 0xDD]),
RoomObject(0x03F32F, [0xB8, 0xC0, 0xDD]),
RoomObject(0x03F332, [0x08, 0x00, 0x60]),
RoomObject(0x03F335, [0x10, 0x00, 0x60]),
RoomObject(0x03F338, [0x1B, 0x10, 0xC0]),
RoomObject(0x03F33B, [0x59, 0x10, 0xC0]),
RoomObject(0x03F33E, [0x68, 0x23, 0xC0]),
RoomObject(0x03F341, [0x68, 0x61, 0xC0]),
RoomObject(0x03F344, [0x1B, 0x91, 0x60]),
RoomObject(0x03F347, [0x88, 0x10, 0x60]),
RoomObject(0x03F34A, [0xF0, 0x10, 0x60]),
RoomObject(0x03F34D, [0x90, 0xDF, 0xA1]),
RoomObject(0x03F350, [0xD4, 0xF7, 0xA3])], [],
[DoorObject(Position.InteriorW, DoorKind.TrapTriggerable),
DoorObject(Position.SouthW, DoorKind.CaveEntrance), DoorObject(Position.SouthE, DoorKind.CaveEntrance)]
)
Room0127 = Room([0xE1, 0x00],
[RoomObject(0x0AB600, [0xFE, 0x89, 0x00]),
RoomObject(0x0AB603, [0xA2, 0xA1, 0x61]),

View File

@@ -124,6 +124,7 @@ def boss_writes(world, player, rom):
eye_number = random.randint(0, 8) # randomize moldorm eyes (var + 1)
rom.write_byte(snes_to_pc(0x368102), eye_number) # enemizer flag
rom.write_byte(snes_to_pc(0x1DDBB3), eye_number) # loop variable
# todo: flag vitreous key fix (prize on the eyes)
data_tables = world.data_tables[player]
arrghus_can_swim = True
water_tiles_on = True

View File

@@ -1,7 +1,11 @@
import RaceRandom as random
from Utils import snes_to_pc
from source.dungeon.EnemyList import SpriteType
from source.enemizer.SpriteSheets import uw_sub_group_choices, setup_required_dungeon_groups
from source.dungeon.RoomList import Room010C
from source.enemizer.SpriteSheets import sub_group_choices, setup_required_dungeon_groups
from source.enemizer.SpriteSheets import randomize_underworld_sprite_sheets, randomize_overworld_sprite_sheets
from source.enemizer.TilePattern import tile_patterns
water_rooms = {
0x16, 0x28, 0x34, 0x36, 0x38, 0x46, 0x66
@@ -52,7 +56,7 @@ def setup_specific_requirements(data_tables):
if requirement.good_for_uw_water():
water_groups.update(requirement.groups)
for i in range(0, 4):
limited = [x for x in requirement.sub_groups[i] if x in uw_sub_group_choices[i]]
limited = [x for x in requirement.sub_groups[i] if x in sub_group_choices[i]]
water_sub_groups[i].update(limited)
if requirement.good_for_shutter():
killable_groups.update(requirement.groups)
@@ -76,7 +80,7 @@ def get_possible_sheets(room_id, data_tables, specific, uw_sheets):
killable_needed = room_id in shutter_sprites
water_needed = room_id in water_rooms
for sheet in data_tables.sprite_sheets.values():
for sheet in uw_sheets:
if room_id in sheet.room_set:
return [sheet]
@@ -156,7 +160,18 @@ def get_possible_sheets(room_id, data_tables, specific, uw_sheets):
return possible_sheets
def uw_candidate_sprites(data_tables):
def get_possible_ow_sheets(area_id, ow_sheets):
# requirements = data_tables.sprite_requirements
for sheet in ow_sheets:
if area_id in sheet.room_set:
return [sheet]
# not sure I need to match anything else at this point
return ow_sheets
def find_candidate_sprites(data_tables, sheet_range):
requirements = data_tables.sprite_requirements
uw_sprite_candidates = []
uw_sheet_candidates = []
@@ -173,7 +188,7 @@ def uw_candidate_sprites(data_tables):
candidate_sub_groups[i].update(r.sub_groups[i])
uw_sprite_candidates.append(k)
for num in range(65, 124):
for num in sheet_range:
sheet = data_tables.sprite_sheets[num]
if candidate_groups and sheet not in candidate_groups:
continue
@@ -196,6 +211,17 @@ def get_possible_enemy_sprites(room_id, sheet, uw_sprites, data_tables):
return ret
def get_possible_enemy_sprites_ow(sheet, sprites, data_tables):
ret = []
for sprite in sprites:
requirement = data_tables.sprite_requirements[sprite]
if isinstance(requirement, dict):
continue
if sheet.valid_sprite(requirement):
ret.append(requirement)
return ret
def get_randomize_able_sprites(room_id, data_tables):
sprite_table = {}
for idx, sprite in enumerate(data_tables.uw_enemy_table.room_map[room_id]):
@@ -211,13 +237,28 @@ def get_randomize_able_sprites(room_id, data_tables):
return sprite_table
def get_randomize_able_sprites_ow(area_id, data_tables):
sprite_table = {}
for idx, sprite in enumerate(data_tables.ow_enemy_table[area_id]):
sprite_secondary = 0 if sprite.sub_type != SpriteType.Overlord else sprite.sub_type
key = (sprite.kind, sprite_secondary)
if key not in data_tables.sprite_requirements:
continue
req = data_tables.sprite_requirements[key]
if isinstance(req, dict):
continue
if not req.static and req.can_randomize:
sprite_table[idx] = sprite
return sprite_table
# RandomizeRooms(optionFlags);
def randomize_underworld_rooms(data_tables):
# RoomCollection.RandomizeRoomSpriteGroups
# randomize room sprite sheets
specific = setup_specific_requirements(data_tables)
uw_candidates, uw_sheets = uw_candidate_sprites(data_tables)
uw_candidates, uw_sheets = find_candidate_sprites(data_tables, range(65, 124))
for room_id in range(0, 0x128):
if room_id in {0, 1, 3, 6, 7, 0xd, 0x14, 0x1c, 0x20, 0x29, 0x30, 0x33,
0x4d, 0x5a, 0x7F, 0x90, 0xa4, 0xac, 0xc8, 0xde}:
@@ -228,26 +269,93 @@ def randomize_underworld_rooms(data_tables):
randomizeable_sprites = get_randomize_able_sprites(room_id, data_tables)
if randomizeable_sprites:
candidate_sheets = get_possible_sheets(room_id, data_tables, specific, uw_sheets)
chosen_sheet = random.choice(candidate_sheets)
data_tables.room_headers[room_id].sprite_sheet = chosen_sheet.id - 0x40
candidate_sprites = get_possible_enemy_sprites(room_id, chosen_sheet, uw_candidates, data_tables)
if room_id in water_rooms:
water_sprites = [x for x in candidate_sprites if x.water_only]
for i, sprite in randomizeable_sprites.items():
chosen = random.choice(water_sprites)
sprite.kind = chosen.sprite
else:
# todo: stal sprites
for i, sprite in randomizeable_sprites.items():
if sprite.drops_item:
key_sprites = [x for x in candidate_sprites if x.good_for_key_drop() and not x.water_only]
chosen = random.choice(key_sprites)
elif room_id in shutter_sprites and i in shutter_sprites[room_id]:
killable_sprite = [x for x in candidate_sprites if x.good_for_shutter() and not x.water_only]
chosen = random.choice(killable_sprite)
done = False
while not done:
chosen_sheet = random.choice(candidate_sheets)
data_tables.room_headers[room_id].sprite_sheet = chosen_sheet.id - 0x40
candidate_sprites = get_possible_enemy_sprites(room_id, chosen_sheet, uw_candidates, data_tables)
randomized = True
if room_id in water_rooms:
water_sprites = [x for x in candidate_sprites if x.water_only]
if len(water_sprites) == 0:
randomized = False
else:
non_water = [x for x in candidate_sprites if not x.water_only]
chosen = random.choice(non_water)
sprite.kind = chosen.sprite
for i, sprite in randomizeable_sprites.items():
chosen = random.choice(water_sprites)
sprite.kind = chosen.sprite
else:
# todo: stal sprites
for i, sprite in randomizeable_sprites.items():
if sprite.drops_item:
choice_list = [x for x in candidate_sprites if x.good_for_key_drop() and not x.water_only]
elif room_id in shutter_sprites and i in shutter_sprites[room_id]:
choice_list = [x for x in candidate_sprites if x.good_for_shutter() and not x.water_only]
else:
choice_list = [x for x in candidate_sprites if not x.water_only]
if len(choice_list) == 0:
randomized = False
break
chosen = random.choice(choice_list)
sprite.kind = chosen.sprite
done = randomized
# done with sprites
# done with rooms
# done with rooms
def randomize_overworld_enemies(data_tables, randomize_bush_sprites):
# todo: decision on stump/bird
# original kodongo discovery?
# rom.write_byte(snes_to_pc(0x09CF4F), 0x10) //move bird from tree stump in lost woods
ow_candidates, ow_sheets = find_candidate_sprites(data_tables, range(1, 64))
areas_to_randomize = [0, 2, 3, 5, 7, 0xA, 0xF, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x1a, 0x1b, 0x1d, 0x1e, 0x22, 0x25, 0x28, 0x29, 0x2A, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x32, 0x33, 0x34, 0x35, 0x37, 0x3a, 0x3b, 0x3c, 0x3f]
area_list = areas_to_randomize + [x + 0x40 for x in areas_to_randomize] # light world + dark world
area_list += [0x80, 0x81] + [x + 0x90 for x in areas_to_randomize] # specials + post aga LW
for area_id in area_list:
randomizeable_sprites = get_randomize_able_sprites_ow(area_id, data_tables)
if randomizeable_sprites:
candidate_sheets = get_possible_ow_sheets(area_id, ow_sheets)
chosen_sheet = random.choice(candidate_sheets)
data_tables.overworld_sprite_sheets[area_id] = chosen_sheet
candidate_sprites = get_possible_enemy_sprites_ow(chosen_sheet, ow_candidates, data_tables)
for i, sprite in randomizeable_sprites.items():
chosen = random.choice(candidate_sprites)
sprite.kind = chosen
if randomize_bush_sprites:
pass
# todo: randomize the bush sprite
def randomize_enemies(world, player):
if world.enemy_shuffle[player] != 'none':
data_tables = world.data_tables[player]
randomize_underworld_sprite_sheets(data_tables.sprite_sheets)
randomize_underworld_rooms(data_tables)
randomize_overworld_sprite_sheets(data_tables.sprite_sheets)
randomize_overworld_enemies(data_tables, world.enemy_shuffle[player] == 'random')
# todo: health shuffle
# todo: damage shuffle
def write_enemy_shuffle_settings(world, player, rom):
if world.enemy_shuffle[player] != 'none':
# killable thief
rom.write_byte(snes_to_pc(0x368108), 0xc4)
rom.write_byte(snes_to_pc(0x0DB237), 4) # health value: # todo: thief health value
# mimic room barriers
data_tables = world.data_tables[player]
mimic_room = data_tables.room_list[0x10c] = Room010C
mimic_room.layer1[40].data[0] = 0x54 # rail adjust
mimic_room.layer1[40].data[1] = 0x9C
mimic_room.layer1[45].data[1] = 0xB0 # block adjust 1
mimic_room.layer1[47].data[1] = 0xD0 # block adjust 2
if world.enemy_shuffle[player] == 'random':
rom.write_byte(snes_to_pc(0x368100), 1) # randomize bushes
# random tile pattern
pattern_name, tile_pattern = random.choice(tile_patterns)
rom.write_byte(snes_to_pc(0x9BA1D), len(tile_pattern))
for idx, pair in enumerate(tile_pattern):
rom.write_byte(snes_to_pc(0x09BA2A + idx), (pair[0] + 3) * 16)
rom.write_byte(snes_to_pc(0x09BA40 + idx), (pair[1] + 4) * 16)

View File

@@ -1,4 +1,5 @@
from types import SimpleNamespace
from collections import Counter, defaultdict
from source.dungeon.EnemyList import enemy_names, SpriteType
from source.enemizer.Enemizer import randomize_underworld_rooms
@@ -8,15 +9,35 @@ import RaceRandom as random
if __name__ == '__main__':
random.seed(42)
world = SimpleNamespace(pottery={1: 'none'})
data_tables = init_data_tables(world, 1)
randomize_underworld_sprite_sheets(data_tables.sprite_sheets)
randomize_underworld_rooms(data_tables)
for room_id, enemy_list in data_tables.uw_enemy_table.room_map.items():
print(f'Room {hex(room_id)}:')
for i, sprite in enumerate(enemy_list):
if sprite.sub_type == SpriteType.Overlord:
print(f' Overlord #{i+1} {hex(sprite.kind)}:')
else:
print(f' Enemy #{i+1} {enemy_names[sprite.kind]}:')
stats = defaultdict(Counter)
column_headers = {}
for trial in range(0, 100):
world = SimpleNamespace(pottery={1: 'none'})
data_tables = init_data_tables(world, 1)
randomize_underworld_sprite_sheets(data_tables.sprite_sheets)
randomize_underworld_rooms(data_tables)
for room_id, enemy_list in data_tables.uw_enemy_table.room_map.items():
# print(f'Room {hex(room_id)}:')
for i, sprite in enumerate(enemy_list):
if sprite.sub_type == SpriteType.Overlord:
result = f'O{hex(sprite.kind)}'
else:
result = enemy_names[sprite.kind]
if result not in column_headers:
column_headers[result] = None
stats[(room_id, i)][result] += 1
with open('result.csv', 'w') as result_file:
result_file.write('room_id,slot,')
result_file.write(','.join(column_headers.keys()))
result_file.write('\n')
for key, counter in stats.items():
rid, slot = key
result_file.write(f'{rid},{slot}')
for result_item in column_headers.keys():
result_file.write(f',{counter[result_item]}')
result_file.write('\n')

File diff suppressed because it is too large Load Diff

View File

@@ -554,7 +554,7 @@ def setup_required_dungeon_groups(sheets):
# non-optional
([None, None, None, 82], [0x2, 0x58, 0x64, 0x8c, 0x10b]), # pull switches
([None, None, None, 82], [0x1a, 0x3d, 0x44, 0x56, 0x5e, 0x7c, 0x95, 0xc3]), # collasping bridges
([None, None, None, 82], [0x1a, 0x3d, 0x44, 0x56, 0x5e, 0x7c, 0x95, 0xc3]), # collapsing bridges
([None, None, None, 83], [0x4, 0x3f, 0xce]), # pull tongue
([None, None, None, 83], [0x35, 0x37, 0x76]), # swamp drains
([None, None, 34, None], [0x28]), # tektike forced? - spawn chest
@@ -593,7 +593,7 @@ def setup_required_dungeon_groups(sheets):
# roomCollection.LoadRooms()
# roomCollection.RandomizeRoomSpriteGroups(spriteGroupCollection, optionFlags);
# more stuff
uw_sub_group_choices = {
sub_group_choices = {
0: [22, 31, 47, 14], # 70, 72 for guards
1: [44, 30, 32], # 73, 13
2: [12, 18, 23, 24, 28, 46, 34, 35, 39, 40, 38, 41, 36, 37, 42],
@@ -611,7 +611,41 @@ def randomize_underworld_sprite_sheets(sheets):
sheet.sub_groups[1] = random.choice([13, 73])
for idx in range(0, 4):
if not sheet.locked[idx]:
sheet.sub_groups[idx] = random.choice(uw_sub_group_choices[idx])
sheet.sub_groups[idx] = random.choice(sub_group_choices[idx])
# lock the group?
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, 63], {0x1B, 0xAB}) # Hyrule Castle (pre/post-Aga)
sheets[6].add_sprite_to_sheet([None, None, None, None], {0x22, 0x28, 0xB2, 0xB8}) # Smithy/Race (pre/post-Aga)
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, None], {0x3A, 0xCA}) # M-rock (pre/post-Aga)
sheets[22].add_sprite_to_sheet([None, None, 24, None], {0x4F, 0xDF}) # Catfish (pre/post-Aga)
sheets[21].add_sprite_to_sheet([21, None, None, 21], {0x62, 0xF2}) # Smith DW (pre/post-Aga)
sheets[27].add_sprite_to_sheet([None, 42, None, None], {0x68, 0xF8}) # Dig Game (pre/post-Aga)
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, 0xF9}) # VoO South (pre/post-Aga)
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, 0xFA}) # Stumpy (pre/post-Aga)
sheets[12].add_sprite_to_sheet([None, None, 55, 54], {0x80, 0x110}) # Specials (pre/post-Aga)
sheets[14].add_sprite_to_sheet([None, None, 12, 68], {0x81, 0x111}) # Zora's Domain (pre/post-Aga)
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, 0xEE}) # PoD pre/post-Aga
def randomize_overworld_sprite_sheets(sheets):
setup_required_overworld_groups(sheets)
for num in range(1, 64): # sheets 0x1 to 0x3F inclusive
sheet = sheets[num]
for idx in range(0, 4):
if not sheet.locked[idx]:
sheet.sub_groups[idx] = random.choice(sub_group_choices[idx])
# lock the group?
@@ -627,4 +661,3 @@ def randomize_underworld_sprite_sheets(sheets):

View File

@@ -0,0 +1,93 @@
import os
import json
import codecs
if __name__ == '__main__':
directory = './EnemizerCLI.Core/tiles'
for filename in os.listdir(directory):
with codecs.open(directory+'/'+filename, 'r', 'utf-8-sig') as fin:
pattern = json.load(fin)
pairs = [f'({x["x"]}, {x["y"]})' for x in pattern["Items"]]
print(f'(\'{filename}\', [{", ".join(pairs)}]),')
tile_patterns = [
('heart soft', [(3, 1), (5, 2), (4, 7), (2, 5), (7, 1), (7, 5), (8, 4), (1, 2), (2, 1), (1, 4), (5, 7), (6, 1),
(6, 6), (4, 2), (8, 3), (1, 3), (3, 6), (8, 2)]),
('metroid', [(2, 7), (7, 7), (1, 3), (3, 1), (8, 3), (1, 6), (4, 5), (5, 3), (1, 4), (6, 1), (6, 4), (8, 6), (3, 4),
(7, 5), (4, 1), (5, 5), (2, 2), (2, 5), (7, 2), (5, 1), (4, 3), (8, 4)]),
('moldorm vertical', [(5, 1), (6, 0), (7, 2), (5, 4), (4, 4), (4, 1), (3, 5), (5, 6), (3, 2), (6, 6), (7, 5),
(6, 1), (4, 8), (3, 3), (5, 7), (3, 8), (2, 7), (6, 4), (4, 0), (3, 6), (7, 3), (4, 6)]),
('scream emoji', [(2, 2), (7, 2), (2, 3), (7, 3), (1, 7), (8, 7), (3, 2), (6, 2), (2, 6), (7, 6), (3, 3), (6, 3),
(2, 7), (7, 7), (4, 5), (5, 7), (5, 5), (4, 7), (4, 6), (5, 6), (2, 5), (7, 5)]),
('mario mushroom', [(3, 7), (4, 7), (5, 7), (3, 4), (4, 4), (5, 4), (3, 1), (4, 1), (5, 1), (2, 2), (6, 6), (6, 2),
(2, 6), (1, 3), (7, 5), (7, 3), (1, 5), (1, 4), (6, 5), (7, 4), (2, 5)]),
('moldorm', [(1, 3), (2, 5), (3, 6), (5, 5), (7, 5), (7, 2), (5, 3), (3, 2), (2, 4), (1, 5), (5, 4), (6, 2), (7, 4),
(8, 1), (8, 4), (4, 2), (9, 2), (2, 3), (4, 6), (9, 3), (7, 3), (6, 6)]),
('thinking emoji', [(5, 6), (6, 4), (4, 3), (3, 1), (2, 6), (5, 3), (6, 6), (6, 1), (3, 0), (4, 7), (2, 4), (3, 8),
(6, 0), (3, 7), (3, 3)]),
('triangle', [(1, 5), (7, 5), (4, 2), (3, 5), (5, 5), (4, 3), (2, 4), (6, 4), (5, 3), (2, 5), (6, 5), (3, 3),
(4, 5), (5, 4), (3, 4), (4, 4)]),
('heart', [(8, 3), (2, 3), (5, 7), (2, 4), (8, 4), (4, 6), (6, 6), (8, 2), (2, 2), (5, 2), (7, 1), (3, 1), (3, 5),
(6, 1), (7, 5), (4, 1)]),
('arrghus', [(4, 1), (6, 2), (2, 3), (4, 3), (3, 4), (4, 5), (5, 6), (3, 7), (1, 4), (2, 5), (5, 1), (6, 3), (2, 2),
(7, 4), (5, 4), (4, 4), (6, 5), (3, 6), (5, 7), (3, 1), (3, 5), (5, 5)]),
('cowboy smile', [(1, 2), (3, 3), (4, 1), (3, 5), (5, 8), (6, 7), (5, 2), (7, 2), (1, 3), (2, 7), (5, 5), (5, 3),
(3, 2), (4, 3), (7, 3), (2, 3), (4, 2), (3, 8), (4, 8), (6, 3)]),
('clown face happy', [(2, 2), (7, 6), (7, 2), (2, 6), (3, 3), (6, 7), (6, 3), (3, 7), (2, 3), (5, 6), (7, 3),
(4, 6), (2, 5), (6, 6), (7, 5), (3, 6), (4, 5), (5, 7), (5, 5), (4, 7), (3, 2), (6, 2)]),
('generic happy face', [(2, 1), (6, 3), (6, 5), (4, 6), (2, 2), (6, 6), (2, 3), (3, 5), (3, 2), (3, 3), (6, 2),
(5, 5), (1, 2), (3, 6), (7, 5), (7, 1), (4, 5), (8, 2), (5, 6), (2, 5), (7, 3), (7, 2)]),
('YMCA', [(1, 2), (2, 3), (5, 2), (7, 2), (6, 3), (7, 4), (2, 4), (5, 4), (6, 2), (3, 2), (5, 3), (7, 3), (7, 6),
(4, 8), (2, 7), (6, 7), (4, 6), (8, 8), (8, 7), (3, 8), (3, 6), (6, 8)]),
('ze', [(5, 7), (6, 7), (7, 7), (1, 3), (2, 3), (3, 3), (5, 5), (6, 5), (7, 5), (1, 7), (2, 7), (3, 7), (5, 3),
(6, 3), (7, 3), (3, 4), (2, 5), (1, 6), (5, 4), (5, 6)]),
('space invader metroid', [(4, 1), (2, 3), (3, 5), (5, 6), (7, 6), (7, 3), (2, 8), (3, 2), (1, 7), (1, 4), (6, 2),
(8, 5), (7, 8), (4, 6), (8, 7), (5, 1), (2, 6), (8, 4), (1, 5), (6, 5)]),
('screw attack', [(2, 7), (7, 4), (6, 1), (3, 7), (2, 4), (5, 6), (4, 2), (5, 4), (3, 6), (7, 1), (3, 3), (6, 4),
(4, 6), (4, 3), (6, 2), (3, 4), (6, 5), (5, 2), (4, 5), (4, 4), (5, 3), (5, 5)]),
('vanilla wrong order', [(7, 2), (7, 4), (7, 5), (7, 7), (6, 3), (6, 5), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6),
(4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (3, 3), (3, 5), (2, 2), (2, 4), (2, 5), (2, 7)]),
('tile shaped tiles', [(2, 7), (2, 6), (2, 5), (2, 4), (2, 3), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2), (7, 2),
(7, 3), (7, 4), (7, 5), (7, 6), (7, 7), (6, 7), (5, 7), (4, 7), (3, 7), (4, 5), (5, 4)]),
('panda shocked emoji', [(7, 3), (7, 4), (3, 3), (3, 4), (5, 5), (5, 6), (5, 7), (4, 6), (4, 7), (6, 7), (6, 6),
(8, 6), (8, 7), (8, 8), (2, 8), (2, 7), (2, 6), (2, 1), (3, 1), (7, 1), (8, 1), (5, 8)]),
('JK', [(1, 5), (3, 4), (5, 3), (7, 3), (3, 2), (7, 5), (6, 4), (2, 6), (5, 6), (5, 5), (8, 2), (3, 5), (8, 6),
(5, 2), (3, 3), (5, 4)]),
('dollar sign', [(6, 2), (5, 1), (4, 1), (3, 1), (2, 2), (2, 6), (3, 7), (4, 7), (5, 7), (6, 6), (2, 3), (6, 5),
(3, 4), (5, 4), (4, 4), (4, 0), (4, 2), (4, 3), (4, 5), (4, 6), (4, 8)]),
('rupee diagonal', [(1, 4), (1, 5), (1, 6), (1, 7), (2, 7), (3, 7), (4, 7), (5, 6), (6, 5), (7, 4), (7, 3), (7, 2),
(7, 1), (6, 1), (5, 1), (4, 1), (3, 2), (2, 3), (3, 5), (4, 4), (5, 3)]),
('sword', [(1, 8), (8, 1), (8, 2), (1, 4), (7, 1), (5, 8), (7, 3), (2, 4), (6, 2), (5, 7), (6, 4), (2, 5), (5, 3),
(4, 7), (5, 5), (3, 6), (4, 4), (2, 7), (4, 6), (3, 5)]),
('z1 dungeon1', [(4, 6), (6, 4), (2, 4), (3, 7), (4, 3), (5, 7), (3, 2), (5, 4), (3, 5), (5, 5), (4, 2), (6, 3),
(4, 7), (3, 4), (7, 3), (4, 5), (4, 4)]),
('z1 dungeon 4', [(4, 8), (3, 6), (4, 4), (5, 2), (3, 1), (4, 3), (6, 2), (4, 1), (3, 3), (5, 4), (3, 5), (4, 7),
(6, 1), (3, 8), (5, 7), (3, 2), (4, 6), (3, 4), (5, 1)]),
('LTTP', [(3, 4), (2, 3), (6, 2), (6, 4), (2, 2), (5, 2), (6, 3), (2, 4), (7, 2), (3, 6), (4, 8), (8, 7), (7, 6),
(7, 8), (5, 6), (7, 7), (4, 6), (4, 7), (8, 6)]),
('triple triforce', [(4, 2), (3, 3), (4, 3), (5, 3), (6, 5), (5, 6), (6, 6), (7, 6), (3, 6), (2, 6), (2, 5),
(1, 6)]),
('TILE', [(2, 2), (5, 4), (3, 2), (2, 4), (5, 2), (2, 3), (1, 2), (5, 3), (3, 6), (6, 6), (7, 8), (8, 6), (6, 7),
(3, 8), (4, 8), (3, 7), (6, 8), (7, 6), (7, 7), (8, 8)]),
('panda thinking emoji', [(2, 1), (3, 1), (6, 2), (7, 2), (6, 3), (3, 2), (3, 3), (3, 5), (4, 5), (5, 5), (2, 6),
(2, 7), (1, 7), (2, 8), (1, 8), (3, 7), (4, 7), (3, 8)]),
('pokata key', [(3, 1), (4, 2), (5, 3), (4, 4), (4, 6), (5, 7), (6, 8), (4, 8), (6, 6), (3, 3), (5, 1), (4, 5),
(3, 2), (4, 3), (5, 4), (5, 6), (4, 7), (5, 8), (3, 4), (5, 2), (4, 1)]),
('tile shaped tiles randomish', [(4, 2), (2, 2), (7, 5), (5, 4), (3, 2), (4, 5), (4, 7), (7, 7), (2, 7), (2, 4),
(7, 2), (2, 5), (5, 7), (7, 4), (5, 2), (6, 2), (3, 7), (2, 3), (7, 6), (6, 7),
(7, 3), (2, 6)]),
('NO', [(1, 5), (4, 4), (3, 4), (6, 2), (8, 4), (1, 2), (8, 5), (7, 5), (1, 3), (6, 5), (6, 4), (1, 4), (4, 5),
(4, 2), (2, 3), (7, 2), (8, 2), (8, 3), (4, 3), (6, 3)]),
('bomb', [(3, 3), (5, 7), (6, 4), (2, 6), (5, 3), (3, 7), (6, 6), (2, 4), (7, 2), (2, 5), (4, 7), (5, 1), (4, 2),
(6, 5), (6, 1), (4, 3), (8, 2)]),
('boot', [(6, 7), (8, 6), (5, 5), (5, 3), (8, 2), (3, 6), (4, 8), (7, 8), (8, 4), (6, 2), (2, 7), (3, 8), (4, 5),
(5, 4), (7, 2), (8, 7), (8, 8), (8, 3), (5, 8), (2, 8), (5, 2), (8, 5)]),
('javalogo', [(5, 8), (4, 8), (3, 8), (2, 8), (1, 7), (5, 6), (4, 6), (3, 6), (2, 6), (1, 5), (7, 7), (8, 6),
(7, 5), (3, 4), (3, 3), (2, 2), (3, 1), (5, 4), (5, 3), (6, 2)]),
('pokata and ender key', [(3, 1), (5, 3), (4, 4), (4, 6), (5, 7), (6, 8), (4, 8), (6, 6), (3, 3), (5, 1), (4, 5),
(3, 2), (4, 7), (4, 1), (5, 8), (5, 2), (3, 4), (5, 6), (5, 4)]),
('kitty', [(3, 1), (6, 4), (7, 6), (8, 3), (1, 4), (3, 7), (3, 4), (2, 2), (5, 2), (6, 1), (6, 5), (6, 7), (2, 6),
(7, 2), (8, 5), (1, 3), (1, 5), (4, 2), (3, 5), (4, 6), (8, 4), (5, 6)]),
('creeper face', [(3, 7), (4, 5), (5, 4), (5, 6), (3, 3), (2, 2), (3, 2), (7, 3), (6, 7), (4, 6), (6, 3), (7, 2),
(2, 3), (4, 4), (3, 6), (6, 2), (6, 6), (5, 5)])
]

View File

@@ -1,19 +1,33 @@
from collections import defaultdict
from Utils import snes_to_pc, int24_as_bytes, int16_as_bytes
from source.dungeon.EnemyList import EnemyTable, init_vanilla_sprites, vanilla_sprites, init_enemy_stats
from source.dungeon.RoomHeader import init_room_headers
from source.dungeon.RoomList import Room0127
from source.enemizer.OwEnemyList import init_vanilla_sprites_ow, vanilla_sprites_ow
from source.enemizer.SpriteSheets import init_sprite_sheets, init_sprite_requirements
def convert_area_id_to_offset(area_id):
if area_id < 0x40:
return area_id
if 0x40 <= area_id < 0x80:
return area_id + 0x40
if 0x90 <= area_id < 0xCF:
return area_id - 0x50
raise Exception(f'{hex(area_id)} is not a valid area id for offset math')
class DataTables:
def __init__(self):
self.room_headers = None
self.room_list = None
self.sprite_sheets = None
self.uw_enemy_table = None
self.ow_enemy_table = None # todo : data migration
self.ow_enemy_table = None
self.pot_secret_table = None
self.overworld_sprite_sheets = None
# associated data
self.sprite_requirements = None
@@ -33,7 +47,6 @@ class DataTables:
door_start, bytes_written = room.write_to_rom(snes_to_pc(room_start_address), rom)
rom.write_bytes(snes_to_pc(0x1F83C0 + room_id * 3), int24_as_bytes(room_start_address + door_start))
room_start_address += bytes_written
# todo: room data doors pointers at 1F83C0
if room_start_address > 0x380000:
raise Exception('Room list exceeded bank size')
# size notes: bank 03 uses 140E bytes
@@ -45,7 +58,19 @@ class DataTables:
if self.uw_enemy_table.size() > 0x2800:
raise Exception('Sprite table is too big for current area')
self.uw_enemy_table.write_sprite_data_to_rom(rom)
# todo: write ow enemy table
for area_id, sheet_number in self.overworld_sprite_sheets.items():
if area_id in [0x80, 0x81]:
offset = area_id - 0x80 # 02E575 for special areas?
rom.write_byte(snes_to_pc(0x02E576+offset), sheet_number)
else:
offset = convert_area_id_to_offset(area_id)
rom.write_byte(snes_to_pc(0x00FA81+offset), sheet_number)
# _00FA81 is LW normal
# _00FAC1 is LW post-aga
# _00FB01 is DW
for area, sprite_list in vanilla_sprites_ow.items():
for sprite in sprite_list:
rom.write_bytes(snes_to_pc(sprite.original_address), sprite.sprite_data_ow())
def init_data_tables(world, player):
@@ -62,4 +87,10 @@ def init_data_tables(world, player):
for room, sprite_list in vanilla_sprites.items():
for sprite in sprite_list:
uw_table.room_map[room].append(sprite.copy())
data_tables.overworld_sprite_sheets = {}
data_tables.ow_enemy_table = defaultdict(list)
init_vanilla_sprites_ow()
for area, sprite_list in vanilla_sprites_ow.items():
for sprite in sprite_list:
data_tables.ow_enemy_table[area].append(sprite.copy())
return data_tables