diff --git a/README.md b/README.md index 74a73e6..c055205 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ I have mostly solved parsing of SNES menus in a sister project, however there ar - [x] Character sprites - [ ] Weapon sprites (solved problem, soon™) - [ ] Weapon animations (will hardcode these) -- [ ] Enemy sprites (solved problem, soon™) +- [x] Enemy sprites +- [ ] Enemy sprite separate shadows - [ ] Backgrounds (solved problem, soon™) - [ ] Enemy AI (needs research) - [ ] Abilities (will hardcode these, with fixes and extensions where appropriate) diff --git a/data/SNES_PSX_addresses.tsv b/data/SNES_PSX_addresses.tsv index 6eb8654..1eb5e19 100644 --- a/data/SNES_PSX_addresses.tsv +++ b/data/SNES_PSX_addresses.tsv @@ -1,14 +1,19 @@ Label SNES PSX_file PSX_offset format Comment locations_bg_palettes 0x03BB00 /nar/ff5_binx.bin 0x03BF80 43 of Palette128Of555 +enemy_battle_sprite_palettes 0x0ED000 See enemy_battle_sprite_data for pointers. Some are 8 colours instead of 16. worldmap_blocks 0x0FF0C0 /nar/ff5_binx.bin 0x040300 3 of 4 of 192 of u8 # Top-left corners, top-right corners, bottom-left corners, bottom-right corners worldmap_tiles.bias 0x0FF9C0 /nar/ff5_bin3.bin 0x03FB00 3 of 256 of u8 Add to each pixel of the mode7c tiles worldmap_palettes 0x0FFCC0 /nar/ff5_binx.bin 0x040000 3 of Palette128Of555 +enemy_battle_sprite_layouts_small 0x10D004 102 of 8 of u8 length 0x330 +enemy_battle_sprite_layouts_large 0x10D334 72 of 16 of u16 length 0x900 worldmap_tiles.0 0x1B8000 /nar/ff5_bin3.bin 0x039B00 256 of TileSNESMode7c Add the biases worldmap_tiles.1 0x1BA000 /nar/ff5_bin3.bin 0x039B00 256 of TileSNESMode7c Add the biases worldmap_tiles.2 0x1BC000 /nar/ff5_bin3.bin 0x039B00 128 of TileSNESMode7c Add the biases character_battle_sprite_tiles 0x120000 /mnu/men_bin.eng 0x010200 5 of 22 of 48 of TileSNES4bpp character_battle_sprite_palettes 0x14A3C0 /btl/ff5_btl.bin 0x0273C0 5 of 22 of Palette16Of555 Also /mnu/men_bin.eng:0x03A5C0 +enemy_battle_sprite_data 0x14B180 384 of EnemySpriteData length 0x780 character_battle_sprite_layouts 0x14B997 /btl/ff5_btl.bin 0x028997 11 of 6 of u8 +enemy_battle_sprite_tiles 0x150000 See enemy_battle_sprite_data for pointers character_battle_sprite_disabled_palette 0x00F867 /mnu/memsave.bin 0x000034 Palette16Of555 character_battle_sprite_stone_palette 0x00F807 N/A N/A Palette16Of555 Also 0x199835 tiles_fist 0x11D710 /btl/ff5_btl.bin 0x021D10 TileSNES3bpp Also /mnu/men_bin.eng:0x00D910 diff --git a/data/SNES_other.tsv b/data/SNES_other.tsv index 3bb4381..eabf51c 100644 --- a/data/SNES_other.tsv +++ b/data/SNES_other.tsv @@ -115,3 +115,15 @@ u8 weak struct StatusEffect 4 of u8 initial 3 of u8 immune + + +struct EnemySpriteData +u7 tileset_offset_hi # combined with lo, << 3, + 0x150000 +u1 is_3bpp +u8 tileset_offset_lo +u2 palette_offset_hi # combined with lo, << 4, + 0x0ED000 +u4 unk +u1 is_separate_shadow +u1 is_large_layout +u8 palette_offset_lo +u8 layout_id # Small? <<3, + 0x10D004, take 8 bytes. Large? <<5, + 0x10D334, take 32 bytes. diff --git a/scripts/loaders/RomLoader.gd b/scripts/loaders/RomLoader.gd index b3c24b0..5457585 100644 --- a/scripts/loaders/RomLoader.gd +++ b/scripts/loaders/RomLoader.gd @@ -70,6 +70,7 @@ func load_snes_rom(filename: String): snes_data.job_levels.append(ability_list) print(snes_data.job_levels) SpriteLoader.load_from_structs(snes_data) + SpriteLoader.load_enemy_battle_sprites(snes_data, buffer) MapLoader.load_snes_rom(rom_snes) func load_psx_folder(_dirname: String): diff --git a/scripts/loaders/SpriteLoader.gd b/scripts/loaders/SpriteLoader.gd index c0f9006..f4c1bfe 100644 --- a/scripts/loaders/SpriteLoader.gd +++ b/scripts/loaders/SpriteLoader.gd @@ -5,6 +5,7 @@ const INDEX_FORMAT := globals.INDEX_FORMAT const snes_graphics := preload('res://scripts/loaders/snes/graphics.gd') const gba_graphics := preload('res://scripts/loaders/gba/graphics.gd') var shader_material = load('res://palette_mat.tres') +const TILE_RECT := Rect2(0, 0, 8, 8) # Offsets @@ -135,13 +136,12 @@ static func generate_palette_from_colorarray(colors: PoolColorArray, format:=Ima return img static func make_tile_atlas(tile_images) -> Image: - var r := Rect2(0, 0, 8, 8) var image = Image.new() image.create(128, 128, false, INDEX_FORMAT) var tile = 0 for y in tile_images.size()/16: for x in 16: - image.blit_rect(tile_images[tile], r, Vector2(x*8, y*8)) + image.blit_rect(tile_images[tile], TILE_RECT, Vector2(x*8, y*8)) tile += 1 return image @@ -273,6 +273,102 @@ static func make_tile_atlas(tile_images) -> Image: # print_debug('Invalid bpp "%s" in sprite blocks json' % bpp) # sprite_blocks[k] = tiles +# func load_snes_rom(rom: File): +# snes_load_battle_sprites(rom) +# snes_load_worldmap(rom) +# # snes_load_map_sprites(rom) + +# Cache these as they are shared between monsters +var monster_battle_sprite_palettes := {} # offset -> 16 colour palette image +var monster_battle_sprite_palette_textures := {} # offset -> 16 colour palette texture +var monster_battle_sprite_images := {} # "tiles_offset:layout_id:large:bpp" -> tiles laid out in image +var monster_battle_sprite_textures := {} # "tiles_offset:layout_id:large:bpp" -> tiles laid out in texture +func load_enemy_battle_sprites(data: Dictionary, buffer: StreamPeerBuffer): + var PAL: int = Common.SNES_PSX_addresses.enemy_battle_sprite_palettes.SNES + var TILES: int = Common.SNES_PSX_addresses.enemy_battle_sprite_tiles.SNES + var Palette16Of555 = RomLoader.structdefs.Palette16Of555 + data.monster_battle_sprites = [] + for monster in data.enemy_battle_sprite_data: + var entry = {} + # Fetch or generate+cache palette + var palette_offset = (monster.palette_offset_hi << 12) + (monster.palette_offset_lo << 4) + if not (palette_offset in monster_battle_sprite_palettes): + buffer.seek(palette_offset + PAL) + var colors = PoolColorArray() + for i in 16: + colors.append(snes_graphics.bgr555_to_color(buffer.get_u16())) + var image := generate_palette_from_colorarray(colors) + monster_battle_sprite_palettes[palette_offset] = image + monster_battle_sprite_palette_textures[palette_offset] = texture_from_image(image) + entry['palette'] = monster_battle_sprite_palette_textures[palette_offset] + # Fetch or generate+cache tiles and layout + var bpp := 3 if monster.is_3bpp else 4 + var tiles_offset = (monster.tileset_offset_hi << 11) + (monster.tileset_offset_lo << 3) + var key: String + if monster.is_large_layout: + var layout = data.enemy_battle_sprite_layouts_large[monster.layout_id] + key = '%04X:%02X:1:%d' % [tiles_offset, monster.layout_id, bpp] + if not (key in monster_battle_sprite_images): + buffer.seek(tiles_offset + TILES) + var image := Image.new() + image.create(128, 128, false, INDEX_FORMAT) + var max_col := 0 + var max_row := 0 + if bpp == 3: + for y in 16: + for x in 16: + if (layout[y] & (0x8000 >> x)): + image.blit_rect(snes_graphics._3plane_to_tile(buffer.get_data(24)[1]), TILE_RECT, Vector2(x, y)*8) + if max_row < y: + max_row = y + if max_col < x: + max_col = x + else: + for y in 16: + for x in 16: + if (layout[y] & (0x8000 >> x)): + image.blit_rect(snes_graphics._4plane_to_tile(buffer.get_data(32)[1]), TILE_RECT, Vector2(x, y)*8) + if max_row < y: + max_row = y + if max_col < x: + max_col = x + image.crop(8*(max_col+1), 8*(max_row+1)) + monster_battle_sprite_images[key] = image + monster_battle_sprite_textures[key] = texture_from_image(image) + else: + var layout = data.enemy_battle_sprite_layouts_small[monster.layout_id] + key = '%04X:%02X:0:%d' % [tiles_offset, monster.layout_id, bpp] + if not (key in monster_battle_sprite_images): + buffer.seek(tiles_offset + TILES) + var image := Image.new() + image.create(64, 64, false, INDEX_FORMAT) + var max_col := 0 + var max_row := 0 + if bpp == 3: + for y in 8: + for x in 8: + if (layout[y] & (0x80 >> x)): + image.blit_rect(snes_graphics._3plane_to_tile(buffer.get_data(24)[1]), TILE_RECT, Vector2(x, y)*8) + if max_row < y: + max_row = y + if max_col < x: + max_col = x + else: + for y in 8: + for x in 8: + if (layout[y] & (0x80 >> x)): + image.blit_rect(snes_graphics._4plane_to_tile(buffer.get_data(32)[1]), TILE_RECT, Vector2(x, y)*8) + if max_row < y: + max_row = y + if max_col < x: + max_col = x + image.crop(8*(max_col+1), 8*(max_row+1)) + monster_battle_sprite_images[key] = image + monster_battle_sprite_textures[key] = texture_from_image(image) + entry['sprite'] = monster_battle_sprite_textures[key] + # Save monster + data.monster_battle_sprites.append(entry) + static func bias_tile(unbiased: PoolByteArray, bias: int) -> Image: var image := Image.new() var biased = ByteArray(64) @@ -281,11 +377,6 @@ static func bias_tile(unbiased: PoolByteArray, bias: int) -> Image: image.create_from_data(8, 8, false, INDEX_FORMAT, biased) return image -# func load_snes_rom(rom: File): -# snes_load_battle_sprites(rom) -# snes_load_worldmap(rom) -# # snes_load_map_sprites(rom) - func load_from_structs(data: Dictionary): # Load Battle sprites for character_tiles in data.character_battle_sprite_tiles: @@ -296,7 +387,7 @@ func load_from_structs(data: Dictionary): for sprite in num_Character_Battle_Sprite_Layouts: var sprite_tiles: PoolByteArray = data.character_battle_sprite_layouts[sprite] for i in 6: - strip_image.blit_rect(job_tiles[sprite_tiles[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, ((i/2) * 8) + (sprite * 24))) + strip_image.blit_rect(job_tiles[sprite_tiles[i]], TILE_RECT, Vector2((i%2) * 8, ((i/2) * 8) + (sprite * 24))) strip_images.append(strip_image) strip_textures.append(texture_from_image(strip_image)) # Character Battle Sprite Palettes @@ -335,7 +426,7 @@ func load_from_structs(data: Dictionary): var src_idx: int = data.worldmap_blocks[world_ts][i][block] block_tile_ids.append(src_idx) if src_idx < tile_count: - image.blit_rect(tile_images[src_idx], Rect2(0, 0, 8, 8), Vector2((i%2)*8, (i/2)*8)) + image.blit_rect(tile_images[src_idx], TILE_RECT, Vector2((i%2)*8, (i/2)*8)) block_images.append(image) block_textures.append(texture_from_image(image)) worldmap_block_individual_imgs.append(block_images) @@ -353,7 +444,7 @@ func load_from_structs(data: Dictionary): # image = Image.new() # image.create(16*8, (tile_count/16)*8, false, INDEX_FORMAT) # for tile in tile_count: - # image.blit_rect(tile_images[tile], Rect2(0, 0, 8, 8), Vector2((tile%16)*8, (tile/16)*8)) + # image.blit_rect(tile_images[tile], TILE_RECT, Vector2((tile%16)*8, (tile/16)*8)) # worldmap_tile_atlas_textures.append(texture_from_image(image)) MapLoader.update_worldmap_block_tile_ids(worldmap_block_tile_ids) @@ -397,7 +488,7 @@ func load_gba_rom(filename: String): var strip_image = Image.new() strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, INDEX_FORMAT) for i in range(6 * num_Character_Battle_Sprite_Layouts): - strip_image.blit_rect(tiles[battle_strip_layouts[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, (i/2) * 8)) + strip_image.blit_rect(tiles[battle_strip_layouts[i]], TILE_RECT, Vector2((i%2) * 8, (i/2) * 8)) strip_images.append(strip_image) strip_textures.append(texture_from_image(strip_image)) diff --git a/test_scene.gd b/test_scene.gd index 9d98f20..5e99579 100644 --- a/test_scene.gd +++ b/test_scene.gd @@ -1,5 +1,5 @@ extends Control - +const palette_mat := preload('res://palette_mat.tres') var save_slots = [] var save_slot_dicts = [] @@ -25,6 +25,16 @@ func _ready(): $PartyMenu.update_labels(data) ThemeManager.set_menu_color_555(data.config.menu_color_r, data.config.menu_color_g, data.config.menu_color_b) + var monster_box := GridContainer.new() + monster_box.columns = 8 + add_child(monster_box) + for mon in RomLoader.snes_data.monster_battle_sprites: + var t := TextureRect.new() + t.material = palette_mat.duplicate() + t.texture = mon.sprite + t.material.set_shader_param('palette', mon.palette) + monster_box.add_child(t) + # var lbl = Label.new() # for i in 22: # lbl.text = lbl.text + '%s - %s\n' % [StringLoader.get_job_name(i), StringLoader.get_job_desc(i)]