From e3c185e05823e30eccd7728ceda2ee57cc66fd4d Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Wed, 11 Dec 2024 12:55:08 +0100 Subject: next commit --- byte_stream.gd | 42 +++++-- client.gd | 8 +- client.tscn | 12 +- cursor.gd | 59 +++++---- cursor.tscn | 2 +- extractor/action.gd | 222 --------------------------------- extractor/action_format.gd | 305 +++++++++++++++++++++++++++++++++++++++++++++ extractor/grf.gd | 20 ++- extractor/sprite.gd | 204 ------------------------------ extractor/sprite_format.gd | 233 ++++++++++++++++++++++++++++++++++ login.gd | 2 +- sprite.gd | 96 ++++++++++++++ sprite.tscn | 11 ++ sprite_resource.gd | 5 + 14 files changed, 745 insertions(+), 476 deletions(-) delete mode 100644 extractor/action.gd create mode 100644 extractor/action_format.gd delete mode 100644 extractor/sprite.gd create mode 100644 extractor/sprite_format.gd create mode 100644 sprite.gd create mode 100644 sprite.tscn create mode 100644 sprite_resource.gd diff --git a/byte_stream.gd b/byte_stream.gd index df55a7d..fe150fc 100644 --- a/byte_stream.gd +++ b/byte_stream.gd @@ -7,7 +7,7 @@ var position: int = 0 @warning_ignore("shadowed_variable") -static func from_bytes(bytes: PackedByteArray): +static func from_bytes(bytes: PackedByteArray) -> ByteStream: var byte_stream = ByteStream.new() byte_stream.bytes = bytes @@ -18,55 +18,77 @@ static func from_bytes(bytes: PackedByteArray): @warning_ignore("shadowed_variable") func seek(position: int): if position > 0: - assert(position < bytes.size()) + assert(position <= bytes.size()) self.position = position -func decode_u8(): +func advance(jumps: int): + position += jumps + + +func get_buffer(length: int) -> ByteStream: + var byte_stream = ByteStream.new() + + byte_stream.bytes = bytes.slice(position, position + length) + seek(position + length) + + return byte_stream + + +func decode_u8() -> int: var result = bytes.decode_u8(position) seek(position + 1) return result -func decode_u16(): +func decode_u16() -> int: var result = bytes.decode_u16(position) seek(position + 2) return result -func decode_u32(): +func decode_u32() -> int: var result = bytes.decode_u32(position) seek(position + 4) return result -func decode_u64(): +func decode_u64() -> int: var result = bytes.decode_u64(position) seek(position + 8) return result -func decode_s8(): +func decode_s8() -> int: var result = bytes.decode_s8(position) seek(position + 1) return result -func decode_s16(): +func decode_s16() -> int: var result = bytes.decode_s16(position) seek(position + 2) return result -func decode_s32(): +func decode_s32() -> int: var result = bytes.decode_s32(position) seek(position + 4) return result -func decode_s64(): +func decode_s64() -> int: var result = bytes.decode_s64(position) seek(position + 8) return result + +func decode_float() -> float: + var result = bytes.decode_float(position) + seek(position + 4) + + return result + +func get_string_from_utf8(length: int) -> String: + return get_buffer(length).bytes.get_string_from_utf8() diff --git a/client.gd b/client.gd index a66cfaf..5c3e9a8 100644 --- a/client.gd +++ b/client.gd @@ -1,9 +1,13 @@ extends Node -func _ready() -> void: +#func _ready() -> void: #var grf = GRF.open("res://data/data.grf") #grf.extract("user://data") #Sprite.from_bytes(FileAccess.get_file_as_bytes("res://data/extracted/data/sprite/cursors.spr")) - ActionFormat.from_bytes(FileAccess.get_file_as_bytes("res://data/extracted/data/sprite/cursors.act")) + #ActionFormat.from_bytes( + #ByteStream.from_bytes( + #FileAccess.get_file_as_bytes("res://data/extracted/data/sprite/cursors.act") + #) + #) diff --git a/client.tscn b/client.tscn index fdc2b75..7d17661 100644 --- a/client.tscn +++ b/client.tscn @@ -1,19 +1,9 @@ -[gd_scene load_steps=11 format=3 uid="uid://cuylx656oarpy"] +[gd_scene load_steps=3 format=3 uid="uid://cuylx656oarpy"] [ext_resource type="Script" path="res://client.gd" id="1_e4txq"] [ext_resource type="PackedScene" uid="uid://n0y3fpb8j820" path="res://cursor.tscn" id="2_m3abr"] -[ext_resource type="Texture2D" uid="uid://dkc1awf1xpl2o" path="res://extractor/test/test-000.png" id="3_0vwpd"] -[ext_resource type="Texture2D" uid="uid://bx25ptahbg1ve" path="res://extractor/test/test-001.png" id="4_qgfg3"] -[ext_resource type="Texture2D" uid="uid://cj32t45v42v3a" path="res://extractor/test/test-002.png" id="5_h0rr4"] -[ext_resource type="Texture2D" uid="uid://boeeuqg0sephn" path="res://extractor/test/test-003.png" id="6_k7dkb"] -[ext_resource type="Texture2D" uid="uid://m5yp6e4y4dl2" path="res://extractor/test/test-004.png" id="7_lr2su"] -[ext_resource type="Texture2D" uid="uid://p5h4en0yc8i5" path="res://extractor/test/test-005.png" id="8_vfr4i"] -[ext_resource type="Texture2D" uid="uid://c5r5lpebvbcmp" path="res://extractor/test/test-007.png" id="9_im4or"] -[ext_resource type="Texture2D" uid="uid://bw0ad3gkk1api" path="res://extractor/test/test-008.png" id="10_b8x2v"] [node name="Client" type="Node"] script = ExtResource("1_e4txq") [node name="Cursor" parent="." instance=ExtResource("2_m3abr")] -arrow_images = Array[Texture2D]([ExtResource("3_0vwpd"), ExtResource("4_qgfg3"), ExtResource("5_h0rr4"), ExtResource("6_k7dkb"), ExtResource("7_lr2su"), ExtResource("8_vfr4i")]) -click_images = Array[Texture2D]([ExtResource("9_im4or"), ExtResource("10_b8x2v")]) diff --git a/cursor.gd b/cursor.gd index 851441d..b8aac57 100644 --- a/cursor.gd +++ b/cursor.gd @@ -1,35 +1,48 @@ -extends Node +extends Node2D -@export var arrow_images: Array[Texture2D] -@export_range(1.0, 10.0, 0.1) var arrow_speed := 7.0 -var arrow_idx := 0.0 +var sprite: Sprite -@export var click_images: Array[Texture2D] -@export_range(1.0, 10.0, 0.1) var click_speed := 4.0 -var click_idx := 0.0 +var current_action_idx := 0 +var last_action_idx := 0 -class CurrentCursor: - var images: Array[Texture2D] - var speed: float - var idx: float +func _ready() -> void: + sprite = preload("res://sprite.tscn").instantiate() + sprite.load_file("res://data/extracted/data/sprite/cursors.spr") + add_child(sprite) + + sprite.action_data.frame_times[0] *= 2.0 + for idx in sprite.action_data.frame_times.size(): + sprite.action_data.frame_times[idx] *= 1.0 + + Input.mouse_mode = Input.MOUSE_MODE_HIDDEN -func _process(delta: float) -> void: + +func _process(_delta: float) -> void: + sprite.global_position = get_global_mouse_position() + if Input.get_current_cursor_shape() == Input.CURSOR_ARROW: - arrow_idx += (delta * arrow_speed) - if arrow_idx > arrow_images.size(): - arrow_idx = 0 - - Input.set_custom_mouse_cursor(arrow_images[floor(arrow_idx)], Input.CURSOR_ARROW) + current_action_idx = 0 elif Input.get_current_cursor_shape() == Input.CURSOR_POINTING_HAND: - click_idx += (delta * click_speed) - if click_idx > click_images.size(): - click_idx = 0 - - Input.set_custom_mouse_cursor(click_images[floor(click_idx)], Input.CURSOR_POINTING_HAND) + current_action_idx = 2 else: - Input.set_custom_mouse_cursor(null, Input.get_current_cursor_shape()) + current_action_idx = -1 + + if current_action_idx == -1: + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + sprite.visible = false + elif current_action_idx != last_action_idx: + sprite.set_current_action(current_action_idx) + Input.mouse_mode = Input.MOUSE_MODE_HIDDEN + sprite.visible = true + + if current_action_idx == 0 and sprite.get_node("%SpriteLayers").get_child_count() == 1: + Input.set_custom_mouse_cursor(sprite.get_node("%SpriteLayers").get_child(0).texture.get_image()) + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + sprite.visible = false + + last_action_idx = current_action_idx func _input(event: InputEvent) -> void: diff --git a/cursor.tscn b/cursor.tscn index 05f6703..96efc47 100644 --- a/cursor.tscn +++ b/cursor.tscn @@ -2,5 +2,5 @@ [ext_resource type="Script" path="res://cursor.gd" id="1_gyqds"] -[node name="Cursor" type="Node"] +[node name="Cursor" type="Node2D"] script = ExtResource("1_gyqds") diff --git a/extractor/action.gd b/extractor/action.gd deleted file mode 100644 index 1865786..0000000 --- a/extractor/action.gd +++ /dev/null @@ -1,222 +0,0 @@ -class_name ActionFormat - - -## Byte Length: 2 [br] -## SP -var signature: String = "AC" - -## Byte Type: u8 [br] -## Byte Length: 2 -var version: Version - -## Byte Type: u16 [br] -## Byte Length: 2 -var action_count: int - -## Byte Type: u8 [br] -## Byte Length: 10 -var reserved: PackedByteArray - -## Length: [member action_count] -var actions: Array[Action] - -## Byte Type: u16 [br] -## Byte Length: 2 -var event_count: int - -## Length: [member event_count] -var events: Array[Event] - -## Byte Type: f32 [br] -## Byte Length: 4 -var frame_times: float - - -func get_byte_length() -> int: - var length := 22 - - for action in actions: - length += action.get_byte_length() - - length += Event.BYTE_LENGTH * event_count - - return length - - -static func from_bytes(bytes: PackedByteArray): - var action = ActionFormat.new() - - @warning_ignore("shadowed_variable") - var version = Version.new() - version.minor = bytes.decode_u8(2) - version.major = bytes.decode_u8(3) - action.version = version - - print(version) - - action.action_count = bytes.decode_u16(4) - action.reserved = bytes.slice(6, 6 + 10) - - # TODO - action.actions = [] as Array[Action] - - action.event_count = 0 - - action.frame_times = 0.0 - - print(inst_to_dict(action)) - - return action - - -class Action: - ## Byte Type: u32 [br] - ## Byte Length: 4 - var motion_count: int - - ## Length: [member motion_count] - var motions: Array[Motion] - - - func get_byte_length() -> int: - var length := 4 - for motion in motions: - length += motion.get_byte_length() - - return length - - -class Motion: - ## Byte Type: u8 [br] - ## Byte Length: 32 - var unused: PackedByteArray - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var sprite_layer_count: int - - ## Length: [member sprite_layer_count] - var sprite_layers: Array[SpriteLayer] - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var event_id: int - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var sprite_anchor_count: int - - ## Length: [member sprite_anchor_count] - var sprite_anchors: Array[SpriteAnchor] - - - func get_byte_length() -> int: - return 44 + SpriteLayer.BYTE_LENGTH * sprite_layer_count + SpriteAnchor.BYTE_LENGTH * sprite_anchor_count - - -class SpriteLayer: - const BYTE_LENGTH := 48 - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var position_u: int - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var position_v: int - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var sprite_index: int - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var is_flipped_vertical: int - - ## Byte Type: u8 [br] - ## Byte Length: 1 - var color_r: int - - ## Byte Type: u8 [br] - ## Byte Length: 1 - var color_g: int - - ## Byte Type: u8 [br] - ## Byte Length: 1 - var color_b: int - - ## Byte Type: u8 [br] - ## Byte Length: 1 - var color_a: int - - ## Byte Type: f32 [br] - ## Byte Length: 4 - var scale_u: float - - ## Byte Type: f32 [br] - ## Byte Length: 4 - var scale_v: float - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var rotation_degrees: int - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var sprite_type: int - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var sprite_width: int - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var sprite_height: int - - - func get_position() -> Vector2: - return Vector2(position_u, position_v) - - - func get_color() -> Color: - return Color8(color_r, color_g, color_b, color_a) - - - func get_scale() -> Vector2: - return Vector2(scale_u, scale_v) - - - func get_size() -> Vector2: - return Vector2(sprite_width, sprite_height) - - -class SpriteAnchor: - const BYTE_LENGTH := 16 - - ## Byte Type: u8 [br] - ## Byte Length: 4 - var unused: PackedByteArray - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var position_u: int - - ## Byte Type: i32 [br] - ## Byte Length: 4 - var position_v: int - - ## Byte Type: u32 [br] - ## Byte Length: 4 - var flag: int - - - func get_position() -> Vector2: - return Vector2(position_u, position_v) - - -class Event: - const BYTE_LENGTH := 40 - - ## Byte Type: u8 [br] - ## Byte Length: 40 - var name: String diff --git a/extractor/action_format.gd b/extractor/action_format.gd new file mode 100644 index 0000000..ff2ba64 --- /dev/null +++ b/extractor/action_format.gd @@ -0,0 +1,305 @@ +class_name ActionFormat + + +## Byte Length: 2 [br] +## AC +var signature: String = "AC" + +## Byte Type: u8 [br] +## Byte Length: 2 +var version: Version + +## Byte Type: u16 [br] +## Byte Length: 2 +var action_count: int + +## Byte Type: u8 [br] +## Byte Length: 10 +var reserved: PackedByteArray + +## Length: [member action_count] +var actions: Array[ActionData] + +## Byte Type: u32 [br] +## Byte Length: 4 +var event_count: int + +## Length: [member event_count] +var events: Array[Event] + +## Byte Type: f32 [br] +## Byte Length: [member action_count] [br] +## The times are given in the unit of "ticks per displayed frame". [br] +## Multiply by 24 to get the time in milliseconds. +var frame_times: Array[float] + + +func get_byte_length() -> int: + var length := 20 + + for action in actions: + length += action.get_byte_length() + + length += Event.BYTE_LENGTH * event_count + length += 4 * action_count + + return length + + +static func from_bytes(bytes: ByteStream) -> ActionFormat: + var action_format = ActionFormat.new() + + bytes.seek(2) + + @warning_ignore("shadowed_variable") + var version = Version.new() + version.minor = bytes.decode_u8() + version.major = bytes.decode_u8() + action_format.version = version + + action_format.action_count = bytes.decode_u16() + action_format.reserved = bytes.get_buffer(10).bytes + + action_format.actions = [] as Array[ActionData] + for idx in action_format.action_count: + var action = ActionData.from_bytes(bytes) + action_format.actions.append(action) + + action_format.event_count = bytes.decode_u32() + action_format.events = [] as Array[Event] + for idx in action_format.event_count: + var event = Event.from_bytes(bytes) + action_format.events.append(event) + + action_format.frame_times = [] as Array[float] + for idx in action_format.action_count: + action_format.frame_times.append(bytes.decode_float()) + + return action_format + + +class ActionData: + ## Byte Type: u32 [br] + ## Byte Length: 4 + var motion_count: int + + ## Length: [member motion_count] + var motions: Array[Motion] + + + func get_byte_length() -> int: + var length := 4 + for motion in motions: + length += motion.get_byte_length() + + return length + + + static func from_bytes(bytes: ByteStream) -> ActionData: + var action = ActionData.new() + + action.motion_count = bytes.decode_u32() + action.motions = [] as Array[Motion] + for idx in action.motion_count: + var motion = Motion.from_bytes(bytes) + action.motions.append(motion) + + return action + + +class Motion: + ## Byte Type: u8 [br] + ## Byte Length: 32 + var unused: PackedByteArray + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var sprite_layer_count: int + + ## Length: [member sprite_layer_count] + var sprite_layers: Array[SpriteLayer] + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var event_id: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var sprite_anchor_count: int + + ## Length: [member sprite_anchor_count] + var sprite_anchors: Array[SpriteAnchor] + + + func get_byte_length() -> int: + return 44 + SpriteLayer.BYTE_LENGTH * sprite_layer_count + SpriteAnchor.BYTE_LENGTH * sprite_anchor_count + + + static func from_bytes(bytes: ByteStream): + var motion = Motion.new() + + motion.unused = bytes.get_buffer(32).bytes + + motion.sprite_layer_count = bytes.decode_u32() + motion.sprite_layers = [] as Array[SpriteLayer] + for idx in motion.sprite_layer_count: + var sprite_layer = SpriteLayer.from_bytes(bytes) + motion.sprite_layers.append(sprite_layer) + + motion.event_id = bytes.decode_s32() + + motion.sprite_anchor_count = bytes.decode_u32() + motion.sprite_anchors = [] as Array[SpriteAnchor] + for idx in motion.sprite_anchor_count: + var sprite_anchor = SpriteAnchor.from_bytes(bytes) + motion.sprite_anchor_count.append(sprite_anchor) + + return motion + + +class SpriteLayer: + const BYTE_LENGTH := 44 + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var position_u: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var position_v: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var sprite_index: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var flip_h: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var color_r: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var color_g: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var color_b: int + + ## Byte Type: u8 [br] + ## Byte Length: 1 + var color_a: int + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var scale_u: float + + ## Byte Type: f32 [br] + ## Byte Length: 4 + var scale_v: float + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var rotation_degrees: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var type: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var width: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var height: int + + + func get_position() -> Vector2: + return Vector2(position_u, position_v) + + + func get_color() -> Color: + return Color8(color_r, color_g, color_b, color_a) + + + func get_scale() -> Vector2: + return Vector2(scale_u, scale_v) + + + func get_size() -> Vector2: + return Vector2(width, height) + + + static func from_bytes(bytes: ByteStream): + var sprite_layer = SpriteLayer.new() + + sprite_layer.position_u = bytes.decode_s32() + sprite_layer.position_v = bytes.decode_s32() + sprite_layer.sprite_index = bytes.decode_s32() + sprite_layer.flip_h = bytes.decode_u32() + sprite_layer.color_r = bytes.decode_u8() + sprite_layer.color_g = bytes.decode_u8() + sprite_layer.color_b = bytes.decode_u8() + sprite_layer.color_a = bytes.decode_u8() + sprite_layer.scale_u = bytes.decode_float() + sprite_layer.scale_v = bytes.decode_float() + sprite_layer.rotation_degrees = bytes.decode_s32() + sprite_layer.type = bytes.decode_u32() + sprite_layer.width = bytes.decode_u32() + sprite_layer.height = bytes.decode_u32() + + return sprite_layer + + +class SpriteAnchor: + const BYTE_LENGTH := 16 + + ## Byte Type: u8 [br] + ## Byte Length: 4 + var unused: PackedByteArray + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var position_u: int + + ## Byte Type: i32 [br] + ## Byte Length: 4 + var position_v: int + + ## Byte Type: u32 [br] + ## Byte Length: 4 + var flag: int + + + func get_position() -> Vector2: + return Vector2(position_u, position_v) + + + static func from_bytes(bytes: ByteStream): + var sprite_anchor = SpriteAnchor.new() + + sprite_anchor.unused = bytes.get_buffer(4).bytes + sprite_anchor.position_u = bytes.decode_s32() + sprite_anchor.position_v = bytes.decode_s32() + sprite_anchor.flag = bytes.decode_u32() + + return sprite_anchor + + +class Event: + const BYTE_LENGTH := 40 + + ## Byte Type: u8 [br] + ## Byte Length: 40 + var name: String + + + static func from_bytes(bytes: ByteStream): + var event = Event.new() + + event.name = bytes.get_string_from_utf8(BYTE_LENGTH) + + return event diff --git a/extractor/grf.gd b/extractor/grf.gd index 3739c8c..7e7e93c 100644 --- a/extractor/grf.gd +++ b/extractor/grf.gd @@ -140,6 +140,7 @@ class FileEntry: @warning_ignore("shadowed_variable") static func from_bytes_with_filename(bytes: PackedByteArray, file_name: String): + print(file_name) var file_entry = FileEntry.new() file_entry.file_name = file_name @@ -177,10 +178,10 @@ static func open(path: String): file_entry_offset + file_name_size, file_entry_offset + file_name_size + FileEntry.BYTE_LENGTH ), - grf.file_table.decompressed_record_headers.slice( + GRF.decode_string(grf.file_table.decompressed_record_headers.slice( file_entry_offset, file_entry_offset + file_name_size - ).get_string_from_ascii() + )) ) grf.file_entries.append(file_entry) @@ -201,3 +202,18 @@ func extract(destination: String = "res://data"): # TODO: write pngs for sprites (and .tres files maybe if necessary) # TODO: (also maybe write .tres files for action data files(whatever they are)) + + + +static func decode_string(bytes: PackedByteArray): + return bytes.get_string_from_ascii() + # TODO: check unicode codepoints and parse accordingly + var string = bytes.get_string_from_utf32() + if string == "": + string = bytes.get_string_from_utf16() + if string == "": + string = bytes.get_string_from_utf8() + if string == "": + string = bytes.get_string_from_ascii() + + return string diff --git a/extractor/sprite.gd b/extractor/sprite.gd deleted file mode 100644 index 410ce07..0000000 --- a/extractor/sprite.gd +++ /dev/null @@ -1,204 +0,0 @@ -class_name Sprite - - -## Byte Length: 2 [br] -## SP -var signature: String = "SP" - -## Byte Type: u8 [br] -## Byte Length: 2 -var version: Version - -## Byte Type: u16 [br] -## Byte Length: 2 -var palette_image_count: int - -## Byte Type: u16 [br] -## Byte Length: 2 -var rgba_image_count: int - -## Length: [member palette_image_count] -var palette_image_data: Array[PaletteImageData] - -## Byte Type: u8 [br] -## Byte Length: [member rgba_image_count] -var rgba_image_data: Array[RGBAImageData] - -## Byte Type: u16 [br] -## Byte Length: 256 * 4 (rgba u8) [br] -## The color with palette index 0 can be considered the "background color". [br] -## It must be cleared manually on load. -var palette: Array[PaletteColor] - - -static func from_bytes(bytes: PackedByteArray): - var sprite = Sprite.new() - - @warning_ignore("shadowed_variable") - var version = Version.new() - version.minor = bytes.decode_u8(2) - version.major = bytes.decode_u8(3) - sprite.version = version - - sprite.palette_image_count = bytes.decode_u16(4) - sprite.rgba_image_count = bytes.decode_u16(6) - - sprite.palette_image_data = [] as Array[PaletteImageData] - var palette_image_offset = 8 - var processed_palette_images = 0 - while processed_palette_images < sprite.palette_image_count: - var data = PaletteImageData.new() - data.width = bytes.decode_u16(palette_image_offset) - data.height = bytes.decode_u16(palette_image_offset + 2) - - data.data_size = bytes.decode_u16(palette_image_offset + 4) - data.encoded_data = bytes.slice( - palette_image_offset + 6, - palette_image_offset + 6 + data.data_size - ) - - data.decode() - - sprite.palette_image_data.append(data) - processed_palette_images += 1 - palette_image_offset += data.get_byte_length() - - sprite.rgba_image_data = [] as Array[RGBAImageData] - var rgba_image_offset = palette_image_offset - var processed_rgba_images = 0 - while processed_rgba_images < sprite.rgba_image_count: - var data = RGBAImageData.new() - data.width = bytes.decode_u16(rgba_image_offset) - data.height = bytes.decode_u16(rgba_image_offset + 2) - - data.data = bytes.slice( - rgba_image_offset + 4, - rgba_image_offset + 4 + (data.width * data.height * 4) - ) - - sprite.rgba_image_data.append(data) - processed_rgba_images += 1 - rgba_image_offset += data.get_byte_length() - - sprite.palette = [] as Array[PaletteColor] - var palette_offset = rgba_image_offset - while palette_offset < bytes.size(): - var color = PaletteColor.new() - color.r = bytes.decode_u8(palette_offset) - color.g = bytes.decode_u8(palette_offset + 1) - color.b = bytes.decode_u8(palette_offset + 2) - color.a = bytes.decode_u8(palette_offset + 3) - sprite.palette.append(color) - palette_offset += 4 - - #print(sprite.palette_image_data[0].get_rgba_data(sprite.palette)) - - for idx in sprite.palette_image_data.size(): - var d: PaletteImageData = sprite.palette_image_data[idx] - var i = Image.create_from_data( - d.width, - d.height, - false, - Image.FORMAT_RGBA8, - d.get_rgba_data(sprite.palette) - ) - i.save_png("res://extractor/test/test-" + str(idx).pad_zeros(3) + ".png") - - return sprite - - -class PaletteImageData: - ## Byte Type: u16 - ## Byte Length: 2 - var width: int - - ## Byte Type: u16 - ## Byte Length: 2 - var height: int - - ## Byte Type: u16 - ## Byte Length: 2 - var data_size: int - - ## Byte Type: u8 - ## Byte Length: [member data_size] - var encoded_data: PackedByteArray - - var decoded_data: PackedByteArray - - - func get_byte_length() -> int: - return 6 + data_size - - - func decode(): - decoded_data = PackedByteArray([]) - - var offset = 0 - while offset < encoded_data.size(): - var byte = encoded_data.decode_u8(offset) - if byte == 0: - var length = encoded_data.decode_u8(offset + 1) - var padding = PackedByteArray([]) - padding.resize(length) - padding.fill(0) - decoded_data.append_array(padding) - - offset += 2 - else: - decoded_data.append(byte) - offset += 1 - - - func get_rgba_data(palette: Array[PaletteColor]) -> PackedByteArray: - var rgba := PackedByteArray([]) - var background_color := palette[0] - - for idx in decoded_data: - var color := palette[idx] - if color == background_color: - rgba.append_array(PackedByteArray([0, 0, 0, 0])) - else: - rgba.append_array(color.to_bytes()) - - return rgba - - -class PaletteColor: - const BYTE_LENGTH := 4 - - var r: int - var g: int - var b: int - var a: int - - - func to_bytes(): - return PackedByteArray([r, g, b, 255]) - - -class RGBAImageData: - ## Byte Type: u16 - ## Byte Length: 2 - var width: int - - ## Byte Type: u16 - ## Byte Length: 2 - var height: int - - ## Byte Type: u8 - ## Byte Length: width * height * RGBAPixel.byte_length - var data: Array[Pixel] - - - class Pixel: - const BYTE_LENGTH := 4 - - var r: int - var g: int - var b: int - var a: int - - - func get_byte_length() -> int: - return width * height * data.size() diff --git a/extractor/sprite_format.gd b/extractor/sprite_format.gd new file mode 100644 index 0000000..fab0873 --- /dev/null +++ b/extractor/sprite_format.gd @@ -0,0 +1,233 @@ +class_name SpriteFormat + + +## Byte Length: 2 [br] +## SP +var signature: String = "SP" + +## Byte Type: u8 [br] +## Byte Length: 2 +var version: Version + +## Byte Type: u16 [br] +## Byte Length: 2 +var palette_image_count: int + +## Byte Type: u16 [br] +## Byte Length: 2 +var rgba_image_count: int + +## Length: [member palette_image_count] +var palette_image_data: Array[PaletteImageData] + +## Byte Type: u8 [br] +## Byte Length: [member rgba_image_count] +var rgba_image_data: Array[RGBAImageData] + +## Byte Type: u16 [br] +## Byte Length: 256 * 4 (rgba u8) [br] +## The color with palette index 0 can be considered the "background color". [br] +## It must be cleared manually on load. +var palette: Array[PaletteColor] + +var filepath: String + + +static func from_file(path: String) -> SpriteFormat: + var sprite_format = from_bytes(FileAccess.get_file_as_bytes(path)) + + sprite_format.filepath = path + + return sprite_format + + +static func from_bytes(bytes: PackedByteArray) -> SpriteFormat: + var sprite_format = SpriteFormat.new() + + @warning_ignore("shadowed_variable") + var version = Version.new() + version.minor = bytes.decode_u8(2) + version.major = bytes.decode_u8(3) + sprite_format.version = version + + sprite_format.palette_image_count = bytes.decode_u16(4) + sprite_format.rgba_image_count = bytes.decode_u16(6) + + sprite_format.palette_image_data = [] as Array[PaletteImageData] + var palette_image_offset = 8 + var processed_palette_images = 0 + while processed_palette_images < sprite_format.palette_image_count: + var data = PaletteImageData.new() + data.width = bytes.decode_u16(palette_image_offset) + data.height = bytes.decode_u16(palette_image_offset + 2) + + data.data_size = bytes.decode_u16(palette_image_offset + 4) + data.encoded_data = bytes.slice( + palette_image_offset + 6, + palette_image_offset + 6 + data.data_size + ) + + data.decode() + + sprite_format.palette_image_data.append(data) + processed_palette_images += 1 + palette_image_offset += data.get_byte_length() + + sprite_format.rgba_image_data = [] as Array[RGBAImageData] + var rgba_image_offset = palette_image_offset + var processed_rgba_images = 0 + while processed_rgba_images < sprite_format.rgba_image_count: + var data = RGBAImageData.new() + data.width = bytes.decode_u16(rgba_image_offset) + data.height = bytes.decode_u16(rgba_image_offset + 2) + + data.data = [] as Array[RGBAImageData.Pixel] + for _idx in (data.width * data.height): + var pixel = RGBAImageData.Pixel.new() + pixel.r = bytes.decode_u8(rgba_image_offset + 4) + pixel.g = bytes.decode_u8(rgba_image_offset + 5) + pixel.b = bytes.decode_u8(rgba_image_offset + 6) + pixel.a = bytes.decode_u8(rgba_image_offset + 7) + data.data.append(pixel) + + sprite_format.rgba_image_data.append(data) + processed_rgba_images += 1 + rgba_image_offset += data.get_byte_length() + + sprite_format.palette = [] as Array[PaletteColor] + var palette_offset = rgba_image_offset + while palette_offset < bytes.size(): + var color = PaletteColor.new() + color.r = bytes.decode_u8(palette_offset) + color.g = bytes.decode_u8(palette_offset + 1) + color.b = bytes.decode_u8(palette_offset + 2) + color.a = bytes.decode_u8(palette_offset + 3) + sprite_format.palette.append(color) + palette_offset += 4 + + return sprite_format + + +func save_to_file(): + assert(filepath != "") + + var base_dir = filepath.substr(0, filepath.length() - 4) # cut off .spr + DirAccess.make_dir_recursive_absolute(base_dir) + + for idx in palette_image_data.size(): + var data: PaletteImageData = palette_image_data[idx] + var image = Image.create_from_data( + data.width, + data.height, + false, + Image.FORMAT_RGBA8, + data.get_rgba_data(palette) + ) + + image.save_png("%s/%s.png" % [base_dir, str(idx).pad_zeros(3)]) + + +func files_exist() -> bool: + assert(filepath != "") + + var base_dir = filepath.substr(0, filepath.length() - 4) # cut off .spr + + return DirAccess.dir_exists_absolute(base_dir) + + + +class PaletteImageData: + ## Byte Type: u16 + ## Byte Length: 2 + var width: int + + ## Byte Type: u16 + ## Byte Length: 2 + var height: int + + ## Byte Type: u16 + ## Byte Length: 2 + var data_size: int + + ## Byte Type: u8 + ## Byte Length: [member data_size] + var encoded_data: PackedByteArray + + var decoded_data: PackedByteArray + + + func get_byte_length() -> int: + return 6 + data_size + + + func decode(): + decoded_data = PackedByteArray([]) + + var offset = 0 + while offset < encoded_data.size(): + var byte = encoded_data.decode_u8(offset) + if byte == 0: + var length = encoded_data.decode_u8(offset + 1) + var padding = PackedByteArray([]) + padding.resize(length) + padding.fill(0) + decoded_data.append_array(padding) + + offset += 2 + else: + decoded_data.append(byte) + offset += 1 + + + func get_rgba_data(palette: Array[PaletteColor]) -> PackedByteArray: + var rgba := PackedByteArray([]) + var background_color := palette[0] + + for idx in decoded_data: + var color := palette[idx] + if color == background_color: + rgba.append_array(PackedByteArray([0, 0, 0, 0])) + else: + rgba.append_array(color.to_bytes()) + + return rgba + + +class PaletteColor: + const BYTE_LENGTH := 4 + + var r: int + var g: int + var b: int + var a: int + + + func to_bytes(): + return PackedByteArray([r, g, b, 255]) + + +class RGBAImageData: + ## Byte Type: u16 + ## Byte Length: 2 + var width: int + + ## Byte Type: u16 + ## Byte Length: 2 + var height: int + + ## Byte Type: u8 + ## Byte Length: width * height * RGBAPixel.byte_length + var data: Array[Pixel] + + + class Pixel: + const BYTE_LENGTH := 4 + + var r: int + var g: int + var b: int + var a: int + + + func get_byte_length() -> int: + return 4 + width * height * Pixel.BYTE_LENGTH diff --git a/login.gd b/login.gd index c00e3cf..193533e 100644 --- a/login.gd +++ b/login.gd @@ -9,7 +9,7 @@ var current_character_information: CharacterInformation func _ready() -> void: switch_screen(%Login) - $AudioStreamPlayer2.play() + #$AudioStreamPlayer2.play() func switch_screen(screen: Node): diff --git a/sprite.gd b/sprite.gd new file mode 100644 index 0000000..3bfc961 --- /dev/null +++ b/sprite.gd @@ -0,0 +1,96 @@ +class_name Sprite +extends Node2D + + +var sprite_data: SpriteResource +var action_data: ActionFormat + +var current_action_idx := 0 +var current_frame := 0 +var current_start_time := 0.0 +var accumulator := 0.0 + +var cache: Dictionary + + +func _ready() -> void: + if not sprite_data or not action_data: + set_process(false) + + +func load_file(path: String): + var basename = path.get_basename() + + sprite_data = load("%s.tres" % basename) + + var act = FileAccess.open("%s.act" % basename, FileAccess.READ) + action_data = ActionFormat.from_bytes(ByteStream.from_bytes( + act.get_buffer(act.get_length()) + )) + + if not sprite_data.files_exist(): + sprite_data.save_to_file() # generate at "runtime" + + set_process(true) + set_current_action(0) + + +func set_current_action(idx: int): + current_action_idx = idx + current_frame = 0 + current_start_time = 0 + accumulator = 0 + + update(action_data.actions[current_action_idx].motions[current_frame].sprite_layers) + + +func _process(delta: float) -> void: + var action = action_data.actions[current_action_idx] + var frame_time = ((action_data.frame_times[current_action_idx] * 24) / 1000) + + accumulator += delta + if accumulator > current_start_time + frame_time: + var motion: ActionFormat.Motion = action.motions[current_frame] + update(motion.sprite_layers) + + current_start_time = accumulator + current_frame += 1 + if current_frame >= action.motions.size(): + set_current_action(current_action_idx) # reset current action + + +func update(sprite_layers: Array[ActionFormat.SpriteLayer]): + if not is_processing(): + return + + var has_different_layer_count = %SpriteLayers.get_child_count() != sprite_layers.size() + + if has_different_layer_count: + for node in %SpriteLayers.get_children(): + node.queue_free() + + for idx in sprite_layers.size(): + var sprite_layer = sprite_layers[idx] + + var base_dir = sprite_data.filepath.substr(0, sprite_data.filepath.length() - 4) # cut off .spr + var image = load("%s/%s.png" % [base_dir, str(sprite_layer.sprite_index).pad_zeros(3)]) + + var sprite: Sprite2D + if has_different_layer_count: + sprite = Sprite2D.new() + sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + sprite = %SpriteLayers.get_child(idx) + + sprite.texture = image + sprite.position = sprite_layer.get_position() + sprite.self_modulate = sprite_layer.get_color() + sprite.scale = sprite_layer.get_scale() + sprite.rotation_degrees = sprite_layer.rotation_degrees + sprite.flip_h = sprite_layer.flip_h + + # TODO: use sprite and action together to generate AnimatedSprite2D with SpriteFrame Resources? + # TODO: no. needs AnimationPlayer with config for different sprite positions in the animation + + if has_different_layer_count: + %SpriteLayers.add_child(sprite) diff --git a/sprite.tscn b/sprite.tscn new file mode 100644 index 0000000..d88f18e --- /dev/null +++ b/sprite.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://b8etnr5ebvrp3"] + +[ext_resource type="Script" path="res://sprite.gd" id="1_dohbv"] + +[node name="Sprite" type="Node2D"] +top_level = true +z_index = 1337 +script = ExtResource("1_dohbv") + +[node name="SpriteLayers" type="CanvasGroup" parent="."] +unique_name_in_owner = true diff --git a/sprite_resource.gd b/sprite_resource.gd new file mode 100644 index 0000000..380fac4 --- /dev/null +++ b/sprite_resource.gd @@ -0,0 +1,5 @@ +class_name SpriteResource +extends Resource + + +@export var images: Array[Texture2D] -- cgit v1.2.3