extends Node # World Map Block Properties # 3 bytes # Byte0: movement properties # 0x01 = passable on foot # 0x02 = passable on chocobo? # 0x04 = passable on black chocobo # 0x08 = passable on hiryuu? # 0x10 = passable in submarine? set on deep water tiles, and all undersea tiles that aren't cliffs # 0x20 = passable in ship? set on deep water tiles only (not undersea) - can submerge? # 0x40 = passable in airship? Pretty much every tile aboveground has this. No undersea. # 0x80 = only set on clear sea floor. Submarine pathable/can surface? # Byte1: movement properties # 0x01 = (water flips) can move from this block rightwards # 0x02 = (water flips) can move from this block leftwards # 0x04 = (water flips) can move from this block downwards # 0x08 = (water flips) can move from this block upwards # 0x10 = Chocobo can't land/dismount. most aboveground water tiles # 0x20 = Black Chocobo can't land. # 0x40 = Hiryuu can't land. # 0x80 = Airship can't land. # Byte2: movement properties # 0x01 = Set on forests, deep water, void. # 0x02 = Set on deep water, void, desert. # 0x04 = Only set on diagonal land corners and Galuf World swamp # 0x08 = No hits. # 0x10 = Mountains and Exdeath's Castle # 0x20 = Set on first two rows of forests, also waterfall, but not lower bounds of forests # 0x40 = Shallow water. # 0x80 = # Vehicle landing bit masks: [00 10 20 40 00 00 80] - & with Byte1, if 1, can't land # Vehicle IDs: [None, Chocobo, BlkChocobo, Hiryuu, Submarine, Ship, Airship] # Worldmap animations # World 1 (and probably 3) # Sea tiles and waterfall tiles have a scrolling effect in tile data # This may require setting up a proper tile indirect lookup shader # Shifting sands and the portal have cycling palettes: $6C and $6D swap every frame, $51 through $55 scroll left (i.e. $55->$54, $51->$55) # This will be best hardcoded as a 10 palette cycle # World 2: # Sea tiles have a horizontal scrolling effect in tile data (addresses $1880, $18C0, $1C80, $1CC0) # ASM at C09660 BF 21 86 7F LDA $7F8621,X # 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: 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)