diff --git a/scripts/loaders/map_loader.gd b/scripts/loaders/map_loader.gd index bb63f19..a0627f0 100644 --- a/scripts/loaders/map_loader.gd +++ b/scripts/loaders/map_loader.gd @@ -45,11 +45,118 @@ extends Node # Probably going to shader this effect instead of storing hundreds of frames # No palette cycling +enum FlagsBlockPathing { + PATHABLE_FOOT = 0x001, + PATHABLE_CHOCOBO = 0x002, + PATHABLE_BLACK_CHOCOBO = 0x004, + PATHABLE_HIRYUU = 0x008, + PATHABLE_SUBMARINE = 0x010, + PATHABLE_SHIP = 0x020, + PATHABLE_AIRSHIP = 0x040, + SURFACEABLE = 0x080, + LANDABLE_CHOCOBO = 0x100, + LANDABLE_BLACK_CHOCOBO = 0x200, + LANDABLE_HIRYUU = 0x400, + LANDABLE_AIRSHIP = 0x800, +} + +class WorldMap: + const block_width := 256 + const block_height := 256 + const tile_width := block_width * 2 + const tile_height := block_height * 2 + var blockmap: PoolByteArray + var block_tile_ids: PoolByteArray + var block_pathing: PoolIntArray + + func get_block_tiles(id: int) -> PoolByteArray: + var i = id * 4 + return self.block_tile_ids.subarray(i, i+4) + + func get_block_pathing_flags(id: int) -> int: + return block_pathing[id] + + func make_tile_atlas() -> Image: + var image := Image.new() + var data := PoolByteArray() + data.resize(tile_width*tile_height) + var block_tile := 0 + for y_off in range(0, tile_height*tile_width, tile_width): + for x_off in range(0, tile_width, 2): + data[y_off + x_off] = self.block_tile_ids[block_tile] + block_tile += 1 + data[y_off + x_off + 1] = self.block_tile_ids[block_tile] + block_tile += 1 + data[y_off + tile_width + x_off] = self.block_tile_ids[block_tile] + block_tile += 1 + data[y_off + tile_width + x_off + 1] = self.block_tile_ids[block_tile] + block_tile += 1 + return image + +var worldmaps = [WorldMap.new(), WorldMap.new(), WorldMap.new(), WorldMap.new(), WorldMap.new()] + var worldmap_block_properties = [] +var worldmap_block_pathings = [] func load_worldmap_block_properties(rom: File): rom.seek(0x0FEA00) for world_ts in 3: var ts_properties = PoolIntArray() + var ts_pathings = PoolIntArray() for block in 0xC0: - ts_properties.append(rom.get_16() + (rom.get_8() << 16)) + var properties := rom.get_16() + (rom.get_8() << 16) + ts_properties.append(properties) + var pathings := properties >> 16 # First 8 pathable flags map directly + pathings |= (((properties >> 12) & 0xF) ^ 0xF) << 8 # Next 4 flags (can land) are taken from high bits of second byte and inverted + ts_pathings.append(pathings) worldmap_block_properties.append(ts_properties) + worldmap_block_pathings.append(ts_pathings) + worldmaps[0].block_pathing = worldmap_block_pathings[0] + worldmaps[1].block_pathing = worldmap_block_pathings[1] + worldmaps[2].block_pathing = worldmap_block_pathings[0] + worldmaps[3].block_pathing = worldmap_block_pathings[2] + worldmaps[4].block_pathing = worldmap_block_pathings[2] + + +func load_worldmaps(rom: File): + var chunk_addresses = PoolIntArray() + chunk_addresses.resize(0x500) # 5 worldmaps * 256 chunks + rom.seek(0x0FE000) + for id in range(0, 0x434): + chunk_addresses[id] = rom.get_16() + 0x070000 + for id in range(0x434, 0x500): + chunk_addresses[id] = rom.get_16() + 0x080000 + + for worldmap_id in 5: # Bartz World, Galuf World, Combined World, Underwater Galuf World, Underwater Combined World + # Worldmap chunks have a basic compression. + # Repeated blocks along a row are run-length-encoded (RLE) + # Certain blocks (mountains) expand to 1x3 + var blockmap = PoolByteArray() + blockmap.resize(WorldMap.block_height * WorldMap.block_width) + for chunk_id in range(worldmap_id*0x100, (worldmap_id+1)*0x100): + rom.seek(chunk_addresses[chunk_id]) + while blockmap.size() < 256: + var b := rom.get_8() + if b >= 0xC0: # RLE + var count := b-0xBF + var block = rom.get_8() + for i in count: + blockmap.append(block) + else: + blockmap.append(b) + if b == 0x0C or b == 0x1C or b == 0x2C: + # Mountain blocks expand to a 1x3 + blockmap.append(b+1) + blockmap.append(b+2) + worldmaps[worldmap_id].blockmap = blockmap + +func update_worldmap_block_tile_ids(worldmap_block_tile_ids: Array): + # Called by SpriteLoader + worldmaps[0].block_tile_ids = worldmap_block_tile_ids[0] + worldmaps[1].block_tile_ids = worldmap_block_tile_ids[1] + worldmaps[2].block_tile_ids = worldmap_block_tile_ids[0] + worldmaps[3].block_tile_ids = worldmap_block_tile_ids[2] + worldmaps[4].block_tile_ids = worldmap_block_tile_ids[2] + +func load_snes_rom(rom: File): + load_worldmap_block_properties(rom) + load_worldmaps(rom) diff --git a/scripts/loaders/rom_loader.gd b/scripts/loaders/rom_loader.gd index 2512da4..1ea4e17 100644 --- a/scripts/loaders/rom_loader.gd +++ b/scripts/loaders/rom_loader.gd @@ -10,7 +10,7 @@ func load_snes_rom(filename: String): var error := rom_snes.open(filename, File.READ) if error == OK: SpriteLoader.load_snes_rom(rom_snes) - MapLoader.load_worldmap_block_properties(rom_snes) + MapLoader.load_snes_rom(rom_snes) var _thread_error = thread.start(SoundLoader, 'parse_rom', rom_snes) func _ready(): diff --git a/scripts/loaders/sprite_loader.gd b/scripts/loaders/sprite_loader.gd index e30d439..f058952 100644 --- a/scripts/loaders/sprite_loader.gd +++ b/scripts/loaders/sprite_loader.gd @@ -33,7 +33,9 @@ const offset_Tiles_Fist := 0x11D710 # 3bpp tile # Arrays to store sprites in var worldmap_palette_imgs = [] var worldmap_palette_textures = [] -var worldmap_tile_imgs = [] +var worldmap_tile_individual_imgs = [] +var worldmap_tile_atlas_textures = [] +var worldmap_block_tile_ids = [] var worldmap_block_individual_imgs = [] var worldmap_block_individual_textures = [] var worldmap_block_atlas_imgs = [] @@ -193,6 +195,18 @@ func snes_get_tile(rom: File, offset: int, length: int) -> Image: return snes_1plane_to_tile(data) +func make_tile_atlas(tile_images) -> Image: + var r := Rect2(0, 0, 8, 8) + var image = Image.new() + image.create(256, 256, false, Image.FORMAT_R8) + var tile = 0 + for y in tile_images.size()/16: + for x in 16: + image.blit_rect(tile_images[tile], r, Vector2(x, y)*8) + tile += 1 + return image + + func snes_load_worldmap(rom: File): # Load Worldmap Graphics var worldmap_tile_counts = [256, 256, 128] # Only 128 underwater tiles @@ -215,24 +229,29 @@ func snes_load_worldmap(rom: File): image = snes_mode7_compressed_to_tile(tiledata, tile_palettes[tile]) tile_images.append(image) # tile_textures.append(texture_from_image(image)) - worldmap_tile_imgs.append(tile_images) + worldmap_tile_individual_imgs.append(tile_images) + worldmap_tile_atlas_textures.append(texture_from_image(make_tile_atlas(tile_images))) + # Block definitions var block_images = [] var block_textures = [] var block_bank_start: int = offset_worldmap_blocks + (world_ts*0x300) + var block_tile_ids := PoolByteArray() for block in 0xC0: # 192 blocks per world tileset image = Image.new() image.create(16, 16, false, Image.FORMAT_R8) for tile in 4: rom.seek(block_bank_start + block + (tile * 0xC0)) # Horrible interleaving scheme var src_idx := rom.get_8() + block_tile_ids.append(src_idx) if src_idx < tile_count: image.blit_rect(tile_images[src_idx], Rect2(0, 0, 8, 8), Vector2((tile%2)*8, (tile/2)*8)) block_images.append(image) block_textures.append(texture_from_image(image)) worldmap_block_individual_imgs.append(block_images) worldmap_block_individual_textures.append(block_textures) + worldmap_block_tile_ids.append(block_tile_ids) # Make block atlas image = Image.new() image.create(16*16, 16*12, false, Image.FORMAT_R8) @@ -248,8 +267,8 @@ func snes_load_worldmap(rom: File): # image.blit_rect(tile_images[tile], Rect2(0, 0, 8, 8), Vector2((tile%16)*8, (tile/16)*8)) # worldmap_tile_atlas_textures.append(texture_from_image(image)) - for world_map in 5: # Bartz World, Galuf World, Combined World, Underwater Galuf World, Underwater Combined World - pass + MapLoader.update_worldmap_block_tile_ids(worldmap_block_tile_ids) + func load_snes_rom(rom: File): # Load Battle sprites diff --git a/shaders/worldmap_shader.gdshader b/shaders/worldmap_shader.gdshader new file mode 100644 index 0000000..b9eaa59 --- /dev/null +++ b/shaders/worldmap_shader.gdshader @@ -0,0 +1,28 @@ +shader_type canvas_item; +uniform sampler2D tiles; +uniform sampler2D palette; +// uniform float tile_width = 8.0; +uniform float tilemap_width = 256.0; // Require square tilemap for now +const float index_scale = 255.0 / 16.0; + + +// This shader maps from tileID texels to Tiles, and then applies palette. +// tiles hardcoded to 16x16 tiles for now +// palette hardcoded to 16x16 colors for now + +void fragment() { + // GLES2 + float tile_idx = texture(TEXTURE, UV).a * index_scale; + vec2 tile_uv = vec2(fract(tile_idx), trunc(tile_idx) / 16.0); + vec2 sub_tile_uv = fract(UV / tilemap_width); + + // TODO: add sea HScroll, waterfall VScroll tile UV modulation + // TODO: move cycling palette to a sampler2DArray or sampler3D rather than rebinding + + float color_idx = texture(tiles, tile_uv + (sub_tile_uv/16.0)).a * index_scale; + float palette_uv = vec2(fract(color_idx), trunc(color_idx) / 16.0); + COLOR = texture(palette, palette_uv); + + if (color_idx == 0.0) + COLOR.a = 0.0; +}