2023-07-27 01:34:25 +09:30
|
|
|
extends Node
|
|
|
|
|
|
|
|
# World Map Block Properties
|
|
|
|
# 3 bytes
|
|
|
|
# Byte0: movement properties
|
|
|
|
# 0x01 = passable on foot
|
2023-07-27 16:41:49 +09:30
|
|
|
# 0x02 = passable on chocobo?
|
2023-07-27 01:34:25 +09:30
|
|
|
# 0x04 = passable on black chocobo
|
2023-07-27 16:41:49 +09:30
|
|
|
# 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?
|
2023-07-27 01:34:25 +09:30
|
|
|
# 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
|
2023-07-27 16:41:49 +09:30
|
|
|
# 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.
|
2023-07-27 01:34:25 +09:30
|
|
|
# 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
|
2023-07-27 16:41:49 +09:30
|
|
|
# 0x20 = Set on first two rows of forests, also waterfall, but not lower bounds of forests
|
2023-07-27 01:34:25 +09:30
|
|
|
# 0x40 = Shallow water.
|
|
|
|
# 0x80 =
|
|
|
|
|
2023-07-27 16:41:49 +09:30
|
|
|
# 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
|
|
|
|
|
2023-07-27 19:38:53 +09:30
|
|
|
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]
|
|
|
|
|
2023-07-27 20:25:26 +09:30
|
|
|
func make_tile_map() -> Image:
|
2023-07-27 19:38:53 +09:30
|
|
|
var image := Image.new()
|
|
|
|
var data := PoolByteArray()
|
|
|
|
data.resize(tile_width*tile_height)
|
2023-07-27 20:25:26 +09:30
|
|
|
var block_idx := 0
|
|
|
|
for y_off in range(0, tile_height*tile_width, tile_width*2):
|
2023-07-27 19:38:53 +09:30
|
|
|
for x_off in range(0, tile_width, 2):
|
2023-07-27 20:25:26 +09:30
|
|
|
var tile_offset = self.blockmap[block_idx] * 4
|
|
|
|
block_idx += 1
|
|
|
|
data[y_off + x_off] = self.block_tile_ids[tile_offset]
|
|
|
|
data[y_off + x_off + 1] = self.block_tile_ids[tile_offset+1]
|
|
|
|
data[y_off + tile_width + x_off] = self.block_tile_ids[tile_offset+2]
|
|
|
|
data[y_off + tile_width + x_off + 1] = self.block_tile_ids[tile_offset+3]
|
2023-07-28 00:06:46 +09:30
|
|
|
image.create_from_data(tile_width, tile_height, false, SpriteLoader.INDEX_FORMAT, data)
|
2023-07-27 19:38:53 +09:30
|
|
|
return image
|
|
|
|
|
|
|
|
var worldmaps = [WorldMap.new(), WorldMap.new(), WorldMap.new(), WorldMap.new(), WorldMap.new()]
|
|
|
|
|
2023-07-27 01:34:25 +09:30
|
|
|
var worldmap_block_properties = []
|
2023-07-27 19:38:53 +09:30
|
|
|
var worldmap_block_pathings = []
|
2024-06-30 14:44:32 +09:30
|
|
|
func load_worldmap_block_properties(data: Dictionary): # TODO: replace this with a struct definition
|
|
|
|
for world_ts in data.worldmap_block_properties: # 3
|
2023-07-27 01:34:25 +09:30
|
|
|
var ts_properties = PoolIntArray()
|
2023-07-27 19:38:53 +09:30
|
|
|
var ts_pathings = PoolIntArray()
|
2024-06-30 14:44:32 +09:30
|
|
|
for properties in world_ts:
|
2023-07-27 19:38:53 +09:30
|
|
|
ts_properties.append(properties)
|
2024-06-30 14:44:32 +09:30
|
|
|
var pathings: int = properties >> 16 # First 8 pathable flags map directly
|
2023-07-27 19:38:53 +09:30
|
|
|
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)
|
2023-07-27 01:34:25 +09:30
|
|
|
worldmap_block_properties.append(ts_properties)
|
2023-07-27 19:38:53 +09:30
|
|
|
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]
|
|
|
|
|
|
|
|
|
2024-06-30 14:44:32 +09:30
|
|
|
func load_worldmaps(data: Dictionary, buffer: StreamPeerBuffer):
|
|
|
|
var offset1: int = Common.SNES_PSX_addresses.worldmap_compressed_tilesets.SNES
|
|
|
|
var offset2: int = Common.SNES_PSX_addresses.worldmap_compressed_tilesets2.SNES
|
2023-07-27 19:38:53 +09:30
|
|
|
|
|
|
|
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
|
2024-06-30 14:44:32 +09:30
|
|
|
var chunk_addresses: Array = data.ptrs_worldmap_tilesets[worldmap_id]
|
2023-07-27 19:38:53 +09:30
|
|
|
var blockmap = PoolByteArray()
|
2023-07-27 20:25:26 +09:30
|
|
|
# blockmap.resize(WorldMap.block_height * WorldMap.block_width) # Try this later if performance is a problem
|
2024-06-30 14:44:32 +09:30
|
|
|
for chunk_id in 0x100:
|
|
|
|
var bank = offset1
|
|
|
|
if worldmap_id >= 0x4 and chunk_id >= 0x34: # Chunks 0x434 up to 0x500 are in the next bank
|
|
|
|
bank = offset2
|
|
|
|
|
|
|
|
buffer.seek(bank + chunk_addresses[chunk_id])
|
2023-07-27 20:25:26 +09:30
|
|
|
var chunk_size := 0
|
|
|
|
while chunk_size < 256:
|
2023-07-28 00:06:46 +09:30
|
|
|
# var b: int = (blockmap.size() % 16) + (16 * (chunk_id % 12)); # For debugging the map shader against blocks
|
2023-08-23 19:58:20 +09:30
|
|
|
var b := buffer.get_u8()
|
2023-07-27 19:38:53 +09:30
|
|
|
if b >= 0xC0: # RLE
|
|
|
|
var count := b-0xBF
|
2023-08-23 19:58:20 +09:30
|
|
|
var block = buffer.get_u8()
|
2023-07-27 19:38:53 +09:30
|
|
|
for i in count:
|
|
|
|
blockmap.append(block)
|
2023-07-27 20:25:26 +09:30
|
|
|
chunk_size += count
|
2023-07-27 19:38:53 +09:30
|
|
|
else:
|
|
|
|
blockmap.append(b)
|
2023-07-27 20:25:26 +09:30
|
|
|
chunk_size += 1
|
2023-07-27 19:38:53 +09:30
|
|
|
if b == 0x0C or b == 0x1C or b == 0x2C:
|
|
|
|
# Mountain blocks expand to a 1x3
|
|
|
|
blockmap.append(b+1)
|
|
|
|
blockmap.append(b+2)
|
2023-07-27 20:25:26 +09:30
|
|
|
chunk_size += 2
|
2023-07-27 19:38:53 +09:30
|
|
|
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]
|
|
|
|
|
2024-06-30 14:44:32 +09:30
|
|
|
func load_snes_rom(data: Dictionary, buffer: StreamPeerBuffer):
|
|
|
|
load_worldmap_block_properties(data)
|
|
|
|
load_worldmaps(data, buffer)
|