From 688790b5dc0ea8f51a99e42a00c7510b9bd87aa6 Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Wed, 4 Dec 2024 14:23:42 +0100 Subject: next commit --- .gitignore | 3 + byte_stream.gd | 72 +++++++++++++++++ client.gd | 9 +++ client.tscn | 19 +++++ cursor.gd | 37 +++++++++ cursor.tscn | 6 ++ extractor/action.gd | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++ extractor/grf.gd | 12 ++- extractor/sprite.gd | 204 ++++++++++++++++++++++++++++++++++++++++++++++ extractor/version.gd | 15 ++++ login.gd | 9 ++- login.tscn | 13 ++- network/network.gd | 2 - project.godot | 1 + 14 files changed, 615 insertions(+), 9 deletions(-) create mode 100644 byte_stream.gd create mode 100644 client.gd create mode 100644 client.tscn create mode 100644 cursor.gd create mode 100644 cursor.tscn create mode 100644 extractor/action.gd create mode 100644 extractor/sprite.gd create mode 100644 extractor/version.gd diff --git a/.gitignore b/.gitignore index 82eea3b..669284d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ /data/*.grf /data/extracted/ +/data/BGM/ + +/extractor/test/ diff --git a/byte_stream.gd b/byte_stream.gd new file mode 100644 index 0000000..df55a7d --- /dev/null +++ b/byte_stream.gd @@ -0,0 +1,72 @@ +class_name ByteStream + + +var bytes: PackedByteArray + +var position: int = 0 + + +@warning_ignore("shadowed_variable") +static func from_bytes(bytes: PackedByteArray): + var byte_stream = ByteStream.new() + + byte_stream.bytes = bytes + + return byte_stream + + +@warning_ignore("shadowed_variable") +func seek(position: int): + if position > 0: + assert(position < bytes.size()) + + self.position = position + + +func decode_u8(): + var result = bytes.decode_u8(position) + seek(position + 1) + + return result + +func decode_u16(): + var result = bytes.decode_u16(position) + seek(position + 2) + + return result + +func decode_u32(): + var result = bytes.decode_u32(position) + seek(position + 4) + + return result + +func decode_u64(): + var result = bytes.decode_u64(position) + seek(position + 8) + + return result + +func decode_s8(): + var result = bytes.decode_s8(position) + seek(position + 1) + + return result + +func decode_s16(): + var result = bytes.decode_s16(position) + seek(position + 2) + + return result + +func decode_s32(): + var result = bytes.decode_s32(position) + seek(position + 4) + + return result + +func decode_s64(): + var result = bytes.decode_s64(position) + seek(position + 8) + + return result diff --git a/client.gd b/client.gd new file mode 100644 index 0000000..a66cfaf --- /dev/null +++ b/client.gd @@ -0,0 +1,9 @@ +extends Node + + +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")) diff --git a/client.tscn b/client.tscn new file mode 100644 index 0000000..fdc2b75 --- /dev/null +++ b/client.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=11 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 new file mode 100644 index 0000000..851441d --- /dev/null +++ b/cursor.gd @@ -0,0 +1,37 @@ +extends Node + + +@export var arrow_images: Array[Texture2D] +@export_range(1.0, 10.0, 0.1) var arrow_speed := 7.0 +var arrow_idx := 0.0 + +@export var click_images: Array[Texture2D] +@export_range(1.0, 10.0, 0.1) var click_speed := 4.0 +var click_idx := 0.0 + +class CurrentCursor: + var images: Array[Texture2D] + var speed: float + var idx: float + + +func _process(delta: float) -> void: + 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) + 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) + else: + Input.set_custom_mouse_cursor(null, Input.get_current_cursor_shape()) + + +func _input(event: InputEvent) -> void: + if event is InputEventMouseButton: + pass diff --git a/cursor.tscn b/cursor.tscn new file mode 100644 index 0000000..05f6703 --- /dev/null +++ b/cursor.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://n0y3fpb8j820"] + +[ext_resource type="Script" path="res://cursor.gd" id="1_gyqds"] + +[node name="Cursor" type="Node"] +script = ExtResource("1_gyqds") diff --git a/extractor/action.gd b/extractor/action.gd new file mode 100644 index 0000000..1865786 --- /dev/null +++ b/extractor/action.gd @@ -0,0 +1,222 @@ +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/grf.gd b/extractor/grf.gd index 1e17264..3739c8c 100644 --- a/extractor/grf.gd +++ b/extractor/grf.gd @@ -9,7 +9,7 @@ class Header: ## Byte Length: 15 ## Master of Magic - var signature: String + var signature: String = "Master of Magic" ## Byte Length: 15 var encryption: PackedByteArray @@ -138,6 +138,7 @@ class FileEntry: return contents + @warning_ignore("shadowed_variable") static func from_bytes_with_filename(bytes: PackedByteArray, file_name: String): var file_entry = FileEntry.new() @@ -188,12 +189,15 @@ static func open(path: String): return grf -func write(): +func extract(destination: String = "res://data"): for file_entry in file_entries: var file_path: String = file_entry.get_file_path() - var base_directory = DirAccess.open("res://data") + var base_directory = DirAccess.open(destination) base_directory.make_dir_recursive("extracted/" + file_path.get_base_dir()) - var file = FileAccess.open("res://data/extracted/%s" % [file_path], FileAccess.WRITE_READ) + var file = FileAccess.open("%s/extracted/%s" % [destination, file_path], FileAccess.WRITE_READ) file.store_buffer(file_entry.get_contents(file_access)) + + # TODO: write pngs for sprites (and .tres files maybe if necessary) + # TODO: (also maybe write .tres files for action data files(whatever they are)) diff --git a/extractor/sprite.gd b/extractor/sprite.gd new file mode 100644 index 0000000..410ce07 --- /dev/null +++ b/extractor/sprite.gd @@ -0,0 +1,204 @@ +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/version.gd b/extractor/version.gd new file mode 100644 index 0000000..679a8f0 --- /dev/null +++ b/extractor/version.gd @@ -0,0 +1,15 @@ +class_name Version + + +## Byte Type: u8 +## Byte Length: 1 +var major: int + + +## Byte Type: u8 +## Byte Length: 1 +var minor: int + + +func _to_string() -> String: + return "%s.%s" % [major, minor] diff --git a/login.gd b/login.gd index 8d8e945..c00e3cf 100644 --- a/login.gd +++ b/login.gd @@ -9,16 +9,19 @@ var current_character_information: CharacterInformation func _ready() -> void: switch_screen(%Login) + $AudioStreamPlayer2.play() func switch_screen(screen: Node): for node in get_children(): - node.visible = false + if node is Control: + node.visible = false screen.visible = true func _on_login_pressed() -> void: + $AudioStreamPlayer.play() Network.login_server.login(%Username.text, %Password.text) account_information = await Network.login_server.logged_in @@ -29,6 +32,7 @@ func _on_login_pressed() -> void: for info: CharacterServerInformation in character_server_information: var select_character_server = Button.new() + select_character_server.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND select_character_server.text = info.server_name select_character_server.pressed.connect(_on_character_server_login_pressed.bind(info)) %CharacterServerList.add_child(select_character_server) @@ -37,6 +41,7 @@ func _on_login_pressed() -> void: func _on_character_server_login_pressed(character_server_info: CharacterServerInformation) -> void: + $AudioStreamPlayer.play() Network.character_server = CharacterServer.new( character_server_info.get_server_ip(), character_server_info.server_port @@ -55,6 +60,7 @@ func _on_character_server_login_pressed(character_server_info: CharacterServerIn for slot_idx in response.character_information.size(): var info: CharacterInformation = response.character_information[slot_idx] var character = Button.new() + character.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND character.text = info.name character.pressed.connect(func(): current_character_information = info @@ -66,6 +72,7 @@ func _on_character_server_login_pressed(character_server_info: CharacterServerIn func _on_character_selected_pressed(slot_idx: int): + $AudioStreamPlayer.play() Network.character_server.select_character(slot_idx) var packet = await Network.character_server.selected_character diff --git a/login.tscn b/login.tscn index 25b8a6d..165d3b0 100644 --- a/login.tscn +++ b/login.tscn @@ -1,6 +1,8 @@ -[gd_scene load_steps=2 format=3 uid="uid://dser74lcd3a4g"] +[gd_scene load_steps=4 format=3 uid="uid://dser74lcd3a4g"] [ext_resource type="Script" path="res://login.gd" id="1_1m5cv"] +[ext_resource type="AudioStream" uid="uid://c2xge60t573wc" path="res://data/extracted/data/wav/¹öÆ°¼Ò¸®.wav" id="2_aeqi0"] +[ext_resource type="AudioStream" uid="uid://br8ujl4uxv14a" path="res://data/BGM/01.mp3" id="3_2nukd"] [node name="Login" type="Control"] layout_mode = 3 @@ -13,7 +15,6 @@ script = ExtResource("1_1m5cv") [node name="Login" type="CenterContainer" parent="."] unique_name_in_owner = true -visible = false layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -40,6 +41,7 @@ placeholder_text = "Password" [node name="Login" type="Button" parent="Login/VBoxContainer"] layout_mode = 2 +mouse_default_cursor_shape = 2 text = "Login" [node name="CharacterServer" type="CenterContainer" parent="."] @@ -58,6 +60,7 @@ layout_mode = 2 [node name="CharacterSelection" type="CenterContainer" parent="."] unique_name_in_owner = true +visible = false layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -70,4 +73,10 @@ unique_name_in_owner = true layout_mode = 2 columns = 5 +[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] +stream = ExtResource("2_aeqi0") + +[node name="AudioStreamPlayer2" type="AudioStreamPlayer" parent="."] +stream = ExtResource("3_2nukd") + [connection signal="pressed" from="Login/VBoxContainer/Login" to="." method="_on_login_pressed"] diff --git a/network/network.gd b/network/network.gd index 1db3c50..a0b3d1e 100644 --- a/network/network.gd +++ b/network/network.gd @@ -8,8 +8,6 @@ static var map_server: MapServer func _ready() -> void: login_server = LoginServer.new("127.0.0.1") - - var grf = GRF.open("res://data/data.grf") func _process(_delta: float) -> void: diff --git a/project.godot b/project.godot index 6157940..74a299a 100644 --- a/project.godot +++ b/project.godot @@ -18,3 +18,4 @@ config/icon="res://icon.svg" [autoload] Network="*res://network/network.tscn" +Client="*res://client.tscn" -- cgit v1.2.3