summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client.gd5
-rw-r--r--constants.gd11
-rw-r--r--data_models/login_character_list.gd8
-rw-r--r--data_models/player_data.gd6
-rw-r--r--entities/player.gd40
-rw-r--r--entities/player.tscn24
-rw-r--r--extractor/action_format.gd182
-rw-r--r--extractor/grf.gd225
-rw-r--r--extractor/map.gd34
-rw-r--r--extractor/rsm_format.gd521
-rw-r--r--extractor/rsw_format.gd34
-rw-r--r--extractor/version.gd20
-rw-r--r--network/server.gd8
-rw-r--r--packets/character_information.gd4
-rw-r--r--project.godot2
-rw-r--r--rathena-icon.pngbin0 -> 15143 bytes
-rw-r--r--rathena-icon.png.import34
-rw-r--r--ui/chat_window.gd17
-rw-r--r--ui/chat_window.tscn22
-rw-r--r--ui/login.gd24
-rw-r--r--ui/login.tscn3
-rw-r--r--ui/login/login_character_selection_list.gd34
-rw-r--r--ui/login/login_character_selection_list.tscn2
23 files changed, 1011 insertions, 249 deletions
diff --git a/client.gd b/client.gd
index 3a69e9d..d553d3e 100644
--- a/client.gd
+++ b/client.gd
@@ -7,6 +7,7 @@ var account: Dictionary = {
var character: Dictionary = {
"name": "",
+ "info": null,
}
@@ -33,8 +34,8 @@ func _ready() -> void:
#)
#RSWFormat.from_bytes(
#ByteStream.from_bytes(
- ##FileAccess.get_file_as_bytes("res://client_data/data/int_land02.rsw")
- #FileAccess.get_file_as_bytes("res://client_data/data/pay_dun00.rsw")
+ #FileAccess.get_file_as_bytes("res://client_data/data/int_land02.rsw")
+ ##FileAccess.get_file_as_bytes("res://client_data/data/pay_dun00.rsw")
#)
#)
RSMFormat.from_bytes(
diff --git a/constants.gd b/constants.gd
index 5bb518a..322c85a 100644
--- a/constants.gd
+++ b/constants.gd
@@ -66,6 +66,17 @@ enum Job {
Acolyte = 4,
}
+enum Direction {
+ South,
+ SouthWest,
+ West,
+ NorthWest,
+ North,
+ NorthEast,
+ East,
+ SouthEast,
+}
+
static var PacketDB = {
#LoginServerLoginPacket.HEADER: LoginServerLoginPacket,
diff --git a/data_models/login_character_list.gd b/data_models/login_character_list.gd
index f34d61e..e638877 100644
--- a/data_models/login_character_list.gd
+++ b/data_models/login_character_list.gd
@@ -14,3 +14,11 @@ static func from_character_list_packet(packet: CharacterServerLoginSuccessCharac
resource.character_information = packet.character_information
return resource
+
+
+func get_info_for_slot(slot_idx: int):
+ for info in character_information:
+ if info.character_number == slot_idx:
+ return info
+
+ return null
diff --git a/data_models/player_data.gd b/data_models/player_data.gd
new file mode 100644
index 0000000..c4a7c73
--- /dev/null
+++ b/data_models/player_data.gd
@@ -0,0 +1,6 @@
+class_name PlayerData
+extends Resource
+
+
+var head_direction: Constants.Direction
+var body_direction: Constants.Direction
diff --git a/entities/player.gd b/entities/player.gd
new file mode 100644
index 0000000..ea5e333
--- /dev/null
+++ b/entities/player.gd
@@ -0,0 +1,40 @@
+class_name Player
+extends CharacterBody3D
+
+
+@export var data: PlayerData
+
+
+func _ready() -> void:
+ %Head.texture = load(
+ "%s/%s/000.png" % [
+ "res://client_data/data/sprite",
+ Constants.FilePaths.get_player_head(
+ Client.character.info.gender,
+ Client.character.info.head
+ ),
+ ]
+ )
+ %Head.visible = true
+
+ %Body.texture = load(
+ "%s/%s/000.png" % [
+ "res://client_data/data/sprite",
+ Constants.FilePaths.get_player_body(
+ Client.character.info.gender,
+ Client.character.info.job
+ ),
+ ]
+ )
+
+
+func set_head(direction: Constants.Direction):
+ %Head.texture = load(
+ "%s/%s/000.png" % [
+ "res://client_data/data/sprite",
+ Constants.FilePaths.get_player_head(
+ Client.character.info.gender,
+ Client.character.info.head
+ ),
+ ]
+ )
diff --git a/entities/player.tscn b/entities/player.tscn
index 4df1bc8..d0fef19 100644
--- a/entities/player.tscn
+++ b/entities/player.tscn
@@ -1,3 +1,23 @@
-[gd_scene format=3 uid="uid://b2c5mpkafk8q6"]
+[gd_scene load_steps=5 format=3 uid="uid://b2c5mpkafk8q6"]
-[node name="Player" type="CharacterBody2D"]
+[ext_resource type="Script" uid="uid://caid7hva3kg2i" path="res://entities/player.gd" id="1_merdl"]
+[ext_resource type="Texture2D" uid="uid://danymuvfjf4o1" path="res://client_data/data/sprite/Àΰ£Á·/¸Ó¸®Åë/³²/16_³²/000.png" id="2_b0kkn"]
+[ext_resource type="Texture2D" uid="uid://cwqgdd00sf7pu" path="res://client_data/data/sprite/Àΰ£Á·/¸öÅë/³²/Ãʺ¸ÀÚ_³²/000.png" id="3_e4p34"]
+[ext_resource type="PackedScene" uid="uid://kp7ru23t0olg" path="res://client_data/data/sprite/¸ó½ºÅÍ/bombporing/actions.tscn" id="4_rfe5m"]
+
+[node name="Player" type="CharacterBody3D"]
+script = ExtResource("1_merdl")
+
+[node name="Camera3D" type="Camera3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.704684, 0.709522, 0, -0.709522, 0.704684, 0, 4.6641, 4.76031)
+
+[node name="Head" type="Sprite3D" parent="."]
+unique_name_in_owner = true
+offset = Vector2(0, 43)
+texture = ExtResource("2_b0kkn")
+
+[node name="Body" type="Sprite3D" parent="."]
+unique_name_in_owner = true
+texture = ExtResource("3_e4p34")
+
+[node name="AnimationPlayer" parent="." instance=ExtResource("4_rfe5m")]
diff --git a/extractor/action_format.gd b/extractor/action_format.gd
index d61c590..25a794e 100644
--- a/extractor/action_format.gd
+++ b/extractor/action_format.gd
@@ -82,6 +82,188 @@ static func from_bytes(bytes: ByteStream) -> ActionFormat:
return action_format
+func convert(name: String, sprites_path: String) -> Node2D:
+ var node := Node2D.new()
+ node.name = name
+ node.set_script(load("res://extractor/actions.gd"))
+
+ var animation_player := AnimationPlayer.new()
+ animation_player.name = "AnimationPlayer"
+ animation_player.unique_name_in_owner = true
+ node.add_child(animation_player)
+ animation_player.owner = node
+
+ var sprite_layers := CanvasGroup.new()
+ sprite_layers.name = "SpriteLayers"
+ sprite_layers.unique_name_in_owner = true
+
+ node.add_child(sprite_layers)
+ sprite_layers.owner = node
+
+ var track_properties = [
+ "animation",
+ "frame",
+ "speed_scale",
+ "position",
+ "self_modulate",
+ "scale",
+ "rotation_degrees",
+ "flip_h",
+ "visible",
+ ]
+
+ var sprite_frames := SpriteFrames.new()
+ #sprite_frames.add_animation("default")
+ for img_file_path in DirAccess.get_files_at(sprites_path):
+ if img_file_path.ends_with(".png"):
+ sprite_frames.add_frame("default", load("%s/%s" % [sprites_path, img_file_path]))
+
+ var animation_library := AnimationLibrary.new()
+
+ # get max number of sprite layers for all actions
+ var action_sprite_layers_max_count = actions.reduce(func(accum, action: ActionData):
+ return max(accum, action.motions.reduce(func(accum2, motion: Motion):
+ return max(accum2, motion.sprite_layer_count)
+ , 0))
+ , 0)
+
+ # add Nodes for each sprite layer
+ for sprite_layer_idx in action_sprite_layers_max_count:
+ var sprite = AnimatedSprite2D.new()
+ sprite.centered = false # 必要!!
+ sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
+ sprite.sprite_frames = sprite_frames
+ sprite.name = str(sprite_layer_idx).pad_zeros(3)
+ sprite_layers.add_child(sprite)
+ sprite.owner = node
+
+ for action_idx in actions.size():
+ var action: ActionData = actions[action_idx]
+ var frame_timing_base := ((frame_times[action_idx] * 24) / 1000)
+
+ # TODO: check why this is necessary
+ if sprites_path.contains("cursors") and action_idx == 0:
+ frame_timing_base = ((frame_times[action_idx] * 24 * 2) / 1000)
+
+ # add animation for each action
+ var animation := Animation.new()
+ animation.loop_mode = Animation.LOOP_LINEAR
+ animation.length = frame_timing_base * action.motion_count
+ animation_library.add_animation(str(action_idx).pad_zeros(3), animation)
+
+ # get max number of sprite layers for current action motions
+ var motion_sprite_layers_max_count = action.motions.reduce(func(accum, motion: Motion):
+ return max(accum, motion.sprite_layer_count)
+ , 0)
+
+ # add animation tracks for each sprite layer
+ for sprite_layer_idx in motion_sprite_layers_max_count:
+ var sprite := sprite_layers.get_child(sprite_layer_idx)
+ for property_idx in track_properties.size():
+ var track_idx = (sprite_layer_idx * track_properties.size()) + property_idx
+ animation.add_track(Animation.TYPE_VALUE, track_idx)
+ animation.value_track_set_update_mode(track_idx, Animation.UPDATE_DISCRETE)
+ animation.track_set_path(
+ track_idx,
+ "%s:%s" % ["SpriteLayers/" + sprite.name, track_properties[property_idx]]
+ )
+
+ for i in range(motion_sprite_layers_max_count, action_sprite_layers_max_count):
+ var sprite := sprite_layers.get_child(i)
+ var track_idx = animation.add_track(Animation.TYPE_VALUE)
+ animation.track_set_path(
+ track_idx,
+ "%s:visible" % ["SpriteLayers/" + sprite.name]
+ )
+ animation.track_insert_key(track_idx, 0.0, false)
+
+ # add animation tracks
+ for motion_idx in action.motions.size():
+ var motion: Motion = action.motions[motion_idx]
+
+ # TODO: no animations to speak of available ? sprite_index = -1
+ #if motion.event_id == -1:
+ #continue
+
+ var timing = motion_idx * frame_timing_base
+ var visible_key = 0
+
+ # add visible = false animation tracks to other sprite_layers
+ for i in motion_sprite_layers_max_count:
+ var track_idx = i * track_properties.size() + track_properties.find("visible")
+ visible_key = animation.track_insert_key(track_idx, timing, false)
+
+ for sprite_layer_idx in motion.sprite_layers.size():
+ var layer: SpriteLayer = motion.sprite_layers[sprite_layer_idx]
+
+ if layer.sprite_index == -1:
+ continue
+
+ var track_base_idx = sprite_layer_idx * track_properties.size()
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("animation"),
+ timing,
+ "default"
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("frame"),
+ timing,
+ layer.sprite_index
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("speed_scale"),
+ timing,
+ 1.0
+ )
+
+ var layer_image := sprite_frames.get_frame_texture("default", layer.sprite_index)
+ var position: Vector2 = layer.get_position() - ceil(layer_image.get_size() / 2) # for fixing half pixel drawing
+ var rotated = layer_image.get_size().rotated(deg_to_rad(layer.rotation_degrees))
+ var distance = layer_image.get_size() - rotated
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("position"),
+ timing,
+ position + (distance / 2)
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("self_modulate"),
+ timing,
+ layer.get_color()
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("scale"),
+ timing,
+ layer.get_scale()
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("rotation_degrees"),
+ timing,
+ layer.rotation_degrees
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("flip_h"),
+ timing,
+ layer.flip_h
+ )
+
+ animation.track_set_key_value(
+ track_base_idx + track_properties.find("visible"),
+ visible_key,
+ true
+ )
+
+ animation_player.add_animation_library("", animation_library)
+
+ return node
+
+
class ActionData:
## Byte Type: u32 [br]
## Byte Length: 4
diff --git a/extractor/grf.gd b/extractor/grf.gd
index 1d19046..a723fdc 100644
--- a/extractor/grf.gd
+++ b/extractor/grf.gd
@@ -230,186 +230,15 @@ func convert(destination: String = "res://client_data"):
var sprite = SpriteFormat.from_bytes(file_entry.get_contents(file_access))
sprite.save_to_file(base_file_directory_path)
- elif file_path.ends_with(".act") and file_path.contains(player_head_path_part):
- continue
+ elif file_path.ends_with(".act") and file_path.contains(player_head_path_part): #or file_path.contains(player_body_path_part):
+ #continue
if not FileAccess.file_exists("%s/000.png.import" % base_file_directory_path):
continue
- var scene := PackedScene.new()
- var scene_root := Node2D.new()
- scene_root.name = "Actions"
- scene_root.set_script(load("res://extractor/actions.gd"))
-
- var animation_player := AnimationPlayer.new()
- animation_player.name = "AnimationPlayer"
- animation_player.unique_name_in_owner = true
- scene_root.add_child(animation_player)
- animation_player.owner = scene_root
-
- var sprite_layers := CanvasGroup.new()
- sprite_layers.name = "SpriteLayers"
- sprite_layers.unique_name_in_owner = true
-
- scene_root.add_child(sprite_layers)
- sprite_layers.owner = scene_root
-
- var track_properties = [
- "animation",
- "frame",
- "speed_scale",
- "position",
- "self_modulate",
- "scale",
- "rotation_degrees",
- "flip_h",
- "visible",
- ]
-
- var sprite_frames := SpriteFrames.new()
- #sprite_frames.add_animation("default")
- for img_file_path in DirAccess.get_files_at(base_file_directory_path):
- if img_file_path.ends_with(".png"):
- sprite_frames.add_frame("default", load("%s/%s" % [base_file_directory_path, img_file_path]))
-
- var animation_library := AnimationLibrary.new()
- var action_data := ActionFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access)))
-
- # get max number of sprite layers for all actions
- var action_sprite_layers_max_count = action_data.actions.reduce(func(accum, action: ActionFormat.ActionData):
- return max(accum, action.motions.reduce(func(accum2, motion: ActionFormat.Motion):
- return max(accum2, motion.sprite_layer_count)
- , 0))
- , 0)
+ var action := ActionFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access)))
+ var scene_root := action.convert(file_name, base_file_directory_path)
- # add Nodes for each sprite layer
- for sprite_layer_idx in action_sprite_layers_max_count:
- var sprite = AnimatedSprite2D.new()
- sprite.centered = false # 必要!!
- sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
- sprite.sprite_frames = sprite_frames
- sprite.name = str(sprite_layer_idx).pad_zeros(3)
- sprite_layers.add_child(sprite)
- sprite.owner = scene_root
-
- for action_idx in action_data.actions.size():
- var action: ActionFormat.ActionData = action_data.actions[action_idx]
- var frame_timing_base := ((action_data.frame_times[action_idx] * 24) / 1000)
-
- if file_path.contains("cursors") and action_idx == 0:
- frame_timing_base = ((action_data.frame_times[action_idx] * 24 * 2) / 1000)
-
- # add animation for each action
- var animation := Animation.new()
- animation.loop_mode = Animation.LOOP_LINEAR
- animation.length = frame_timing_base * action.motion_count
- animation_library.add_animation(str(action_idx).pad_zeros(3), animation)
-
- # get max number of sprite layers for current action motions
- var motion_sprite_layers_max_count = action.motions.reduce(func(accum, motion: ActionFormat.Motion):
- return max(accum, motion.sprite_layer_count)
- , 0)
-
- # add animation tracks for each sprite layer
- for sprite_layer_idx in motion_sprite_layers_max_count:
- var sprite := sprite_layers.get_child(sprite_layer_idx)
- for property_idx in track_properties.size():
- var track_idx = (sprite_layer_idx * track_properties.size()) + property_idx
- animation.add_track(Animation.TYPE_VALUE, track_idx)
- animation.value_track_set_update_mode(track_idx, Animation.UPDATE_DISCRETE)
- animation.track_set_path(
- track_idx,
- "%s:%s" % ["SpriteLayers/" + sprite.name, track_properties[property_idx]]
- )
-
- for i in range(motion_sprite_layers_max_count, action_sprite_layers_max_count):
- var sprite := sprite_layers.get_child(i)
- var track_idx = animation.add_track(Animation.TYPE_VALUE)
- animation.track_set_path(
- track_idx,
- "%s:visible" % ["SpriteLayers/" + sprite.name]
- )
- animation.track_insert_key(track_idx, 0.0, false)
-
- # add animation tracks
- for motion_idx in action.motions.size():
- var motion: ActionFormat.Motion = action.motions[motion_idx]
-
- # TODO: no animations to speak of available ?
- if motion.event_id == -1:
- continue
-
- var timing = motion_idx * frame_timing_base
- var visible_key = 0
-
- # add visible = false animation tracks to other sprite_layers
- for i in motion_sprite_layers_max_count:
- var track_idx = i * track_properties.size() + track_properties.find("visible")
- visible_key = animation.track_insert_key(track_idx, timing, false)
-
- for sprite_layer_idx in motion.sprite_layers.size():
- var layer: ActionFormat.SpriteLayer = motion.sprite_layers[sprite_layer_idx]
-
- var track_base_idx = sprite_layer_idx * track_properties.size()
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("animation"),
- timing,
- "default"
- )
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("frame"),
- timing,
- layer.sprite_index
- )
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("speed_scale"),
- timing,
- 1.0
- )
-
- var layer_image := sprite_frames.get_frame_texture("default", layer.sprite_index)
- var position: Vector2 = layer.get_position() - ceil(layer_image.get_size() / 2) # for fixing half pixel drawing
- var rotated = layer_image.get_size().rotated(deg_to_rad(layer.rotation_degrees))
- var distance = layer_image.get_size() - rotated
- animation.track_insert_key(
- track_base_idx + track_properties.find("position"),
- timing,
- position + (distance / 2)
- )
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("self_modulate"),
- timing,
- layer.get_color()
- )
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("scale"),
- timing,
- layer.get_scale()
- )
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("rotation_degrees"),
- timing,
- layer.rotation_degrees
- )
-
- animation.track_insert_key(
- track_base_idx + track_properties.find("flip_h"),
- timing,
- layer.flip_h
- )
-
- animation.track_set_key_value(
- track_base_idx + track_properties.find("visible"),
- visible_key,
- true
- )
-
- animation_player.add_animation_library("", animation_library)
+ var scene := PackedScene.new()
scene.pack(scene_root)
# TODO: doesn't work if png is not imported via editor focus => run game twice
@@ -418,53 +247,15 @@ func convert(destination: String = "res://client_data"):
# Map.rsw and .gnd and .gat
- if file_path.ends_with(".rsw") and file_path.contains("pay_dun"):
+ if file_path.ends_with(".rsw") and (file_path.contains("pay_dun") or file_path.contains("iz_int") or file_path.contains("int_land")):
var rsw = RSWFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access)))
-
- var gnd_file_path = "res://client_data/data/%s" % rsw.gnd_file
- var gnd = GNDFormat.from_bytes(ByteStream.from_bytes(FileAccess.get_file_as_bytes(gnd_file_path)))
-
- var gat_file_path = "res://client_data/data/%s" % rsw.gat_file
- var gat = GATFormat.from_bytes(ByteStream.from_bytes(FileAccess.get_file_as_bytes(gat_file_path)))
+ var scene_root := rsw.convert(file_name, "res://client_data")
var scene := PackedScene.new()
- var scene_root := Node3D.new()
- scene_root.name = file_name
-
- for resource in rsw.map_resources:
- if resource is RSWFormat.SpatialAudioSource:
- var audio_file_path := "res://client_data/data/wav/%s" % resource.audio_file
- if not FileAccess.file_exists(audio_file_path):
- continue
-
- var audio = AudioStreamPlayer3D.new()
- audio.stream = load(audio_file_path)
- audio.name = resource.audio_file
- audio.position = resource.get_position()
- audio.volume_linear = resource.volume_gain
- audio.max_distance = resource.audio_range
- scene_root.add_child(audio, true)
- audio.owner = scene_root
-
- var surfrace_tool := SurfaceTool.new()
- for surface: GNDFormat.Surface in gnd.surfaces:
- pass
- #surfrace_tool.add_vertex()
-
scene.pack(scene_root)
ResourceSaver.save(scene, "%s/%s/%s.tscn" % [destination, base_directory_path, file_name])
static func decode_string(bytes: PackedByteArray):
+ # TODO: use iconv to decode EUC-KR
return bytes.get_string_from_ascii()
- @warning_ignore("unreachable_code")
- # 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/map.gd b/extractor/map.gd
new file mode 100644
index 0000000..c3a9713
--- /dev/null
+++ b/extractor/map.gd
@@ -0,0 +1,34 @@
+extends Node3D
+
+
+func _ready() -> void:
+ # add player
+ var map_server_login_success_packet: MapServerLoginSuccessPacket = Network.map_server.received_packets[MapServerLoginSuccessPacket.HEADER][0]
+ var initial_player_position: Vector2 = map_server_login_success_packet.get_position()
+
+ var player = preload("res://entities/player.tscn").instantiate()
+ player.position = Vector3(initial_player_position.x, 0, initial_player_position.y)
+ add_child(player)
+
+ # listen to packets
+ Network.map_server.received_packet.connect(func(packet: Packet):
+ if packet is ChangeMapPacket:
+ player.position.x = packet.get_position().x
+ player.position.z = packet.get_position().y
+ )
+
+ # play audio
+ for node: AudioStreamPlayer3D in find_children("se_*"):
+ node.play()
+
+ # add HUD TODO: add all HUD as HUD scene
+ var chat_window = preload("res://ui/chat_window.tscn").instantiate()
+ add_child(chat_window)
+
+ # TODO: load map.
+ # TODO: whatever else needs to be loaded after converting from rsw
+
+ var map_loaded_packet := MapLoadedPacket.new()
+ Network.map_server.send(map_loaded_packet)
+
+ # TODO: check which map server packets to send next
diff --git a/extractor/rsm_format.gd b/extractor/rsm_format.gd
index d3dabb0..cf83af9 100644
--- a/extractor/rsm_format.gd
+++ b/extractor/rsm_format.gd
@@ -73,10 +73,56 @@ static func from_bytes(bytes: ByteStream) -> RSMFormat:
version.minor = bytes.decode_u8()
rsm_format.version = version
+ rsm_format.animation_length = bytes.decode_u32()
+ rsm_format.shade_type = bytes.decode_u32()
+
+ if version.higher_than(1, 3): # >= 1.4
+ rsm_format.alpha = bytes.decode_u8()
+
+ if version.lower_than(2, 2): # < 2.2
+ rsm_format.reserved = bytes.get_buffer(16).bytes
+
+ if version.higher_than(2, 1): # >= 2.2
+ rsm_format.frames_per_second = bytes.decode_float()
+
+ if version.lower_than(2, 3): # < 2.3
+ rsm_format.texture_count = bytes.decode_u32()
+
+ rsm_format.texture_names = [] as Array[String]
+ for _n in rsm_format.texture_count:
+ rsm_format.texture_names.append(bytes.get_string_from_utf8(40))
+
+ rsm_format.root_node_name = bytes.get_string_from_utf8(40)
+
+ if version.higher_than(2, 1): # >= 2.2
+ rsm_format.root_node_count = bytes.decode_u32()
+
+ rsm_format.root_node_names = [] as Array[String]
+ for _n in rsm_format.root_node_count:
+ rsm_format.root_node_names.append(bytes.get_string_from_utf8(40))
+
+ rsm_format.node_count = bytes.decode_u32()
+ rsm_format.nodes = [] as Array[ModelNode]
+ for _n in rsm_format.node_count:
+ rsm_format.nodes.append(ModelNode.from_bytes(bytes, version))
+
print(inst_to_dict(rsm_format))
+ #print(inst_to_dict(rsm_format.nodes[0].texture_coordinates[0]))
+ #rsm_format.nodes[0].texture_coordinates.clear()
+ #print(inst_to_dict(rsm_format.nodes[0].faces[0]))
+ #rsm_format.nodes[0].faces.clear()
+ #print(inst_to_dict(rsm_format.nodes[0]))
return rsm_format
+func convert() -> Node3D:
+ var node := Node3D.new()
+ node.name = root_node_name
+ #node.set_script(load("res://extractor/model.gd"))
+
+ return node
+
+
class ModelNode:
## Byte Type: u8 [br]
## Byte Length: 40
@@ -85,3 +131,478 @@ class ModelNode:
## Byte Type: u8 [br]
## Byte Length: 40
var parent_node_name: String
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ ## Versions: [<2.3]
+ var texture_count: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ ## Length: [member texture_count] [br]
+ ## Versions: [<2.3]
+ var texture_indices: Array[int]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ ## Versions: [>=2.3]
+ var texture_name_count: int
+
+ ## Byte Length: 40 [br]
+ ## Length: [member texture_name_count] [br]
+ ## Versions: [>=2.3]
+ var texture_names: Array[String]
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 9 [br]
+ ## Length: 9 [br]
+ ## 3 x 3 Matrix. Each element represents a column.
+ var offset_matrix: Array[Vector3]
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ ## Versions: [<2.2]
+ var translation_1: Vector3
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ var translation_2: Vector3
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 [br]
+ ## Versions: [<2.2]
+ var rotation_angle: float
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ ## Versions: [<2.2]
+ var rotation_axis: Vector3
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ ## Versions: [<2.2]
+ var scale: Vector3
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var vertex_position_count: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 * [member vertex_position_count] [br]
+ ## Length: [member vertex_position_count]
+ var vertex_positions: Array[Vector3]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var texture_coordinate_count: int
+
+ ## Length: [member texture_coordinate_count]
+ var texture_coordinates: Array[TextureCoordinate]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var face_count: int
+
+ ## Length: [member face_count]
+ var faces: Array[Face]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ ## Versions: [>=1.6]
+ var scale_keyframe_count: int
+
+ ## Length: [member scale_keyframe_count]
+ ## Versions: [>=1.6]
+ var scale_keyframes: Array[ScaleKeyframe]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var rotation_keyframe_count: int
+
+ ## Length: [member scale_keyframe_count]
+ var rotation_keyframes: Array[RotationKeyframe]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ ## Versions: [>=2.2]
+ var translation_keyframe_count: int
+
+ ## Length: [member scale_keyframe_count]
+ ## Versions: [>=2.2]
+ var translation_keyframes: Array[TranslationKeyframe]
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ ## Versions: [>=2.3]
+ var textures_keyframe_count: int
+
+ ## Length: [member scale_keyframe_count]
+ ## Versions: [>=2.3]
+ var textures_keyframes: Array[TexturesKeyframe]
+
+
+ static func from_bytes(bytes: ByteStream, version: Version) -> ModelNode:
+ var node = ModelNode.new()
+
+ node.node_name = bytes.get_string_from_utf8(40)
+ node.parent_node_name = bytes.get_string_from_utf8(40)
+
+ if version.lower_than(2, 3): # < 2.3
+ node.texture_count = bytes.decode_u32()
+
+ node.texture_indices = [] as Array[int]
+ for _n in node.texture_count:
+ node.texture_indices.append(bytes.decode_u32())
+
+ if version.higher_than(2, 2): # >= 2.3
+ node.texture_name_count = bytes.decode_u32()
+
+ node.texture_names = [] as Array[String]
+ for _n in node.texture_name_count:
+ node.texture_names.append(bytes.get_string_from_utf8(40))
+
+ node.offset_matrix = [] as Array[Vector3]
+ for _in in 3:
+ node.offset_matrix.append(Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ ))
+
+ if version.lower_than(2, 2):
+ node.translation_1 = Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+
+ node.translation_2 = Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+
+ if version.lower_than(2, 2):
+ node.rotation_angle = bytes.decode_float()
+ node.rotation_axis = Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+ node.scale = Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+
+ node.vertex_position_count = bytes.decode_u32()
+ node.vertex_positions = [] as Array[Vector3]
+
+ for _n in node.vertex_position_count:
+ node.vertex_positions.append(Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ ))
+
+ node.texture_coordinate_count = bytes.decode_u32()
+ node.texture_coordinates = [] as Array[TextureCoordinate]
+
+ for _n in node.texture_coordinate_count:
+ node.texture_coordinates.append(TextureCoordinate.from_bytes(bytes, version))
+
+ node.face_count = bytes.decode_u32()
+ node.faces = [] as Array[Face]
+
+ for _n in node.face_count:
+ node.faces.append(Face.from_bytes(bytes, version))
+
+ if version.higher_than(1, 5): # >= 1.6
+ node.scale_keyframe_count = bytes.decode_u32()
+ node.scale_keyframes = [] as Array[ScaleKeyframe]
+
+ for _n in node.scale_keyframe_count:
+ node.scale_keyframes.append(ScaleKeyframe.from_bytes(bytes))
+
+ node.rotation_keyframe_count = bytes.decode_u32()
+ node.rotation_keyframes = [] as Array[RotationKeyframe]
+
+ for _n in node.rotation_keyframe_count:
+ node.rotation_keyframes.append(RotationKeyframe.from_bytes(bytes))
+
+ if version.higher_than(2, 1): # >= 2.2
+ node.translation_keyframe_count = bytes.decode_u32()
+ node.translation_keyframes = [] as Array[ScaleKeyframe]
+
+ for _n in node.translation_keyframe_count:
+ node.translation_keyframes.append(TranslationKeyframe.from_bytes(bytes))
+
+ if version.higher_than(2, 2): # >= 2.3
+ node.textures_keyframe_count = bytes.decode_u32()
+ node.textures_keyframes = [] as Array[TexturesKeyframe]
+
+ for _n in node.textures_keyframe_count:
+ node.textures_keyframes.append(TexturesKeyframe.from_bytes(bytes))
+
+ return node
+
+
+class TextureCoordinate:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ ## Versions: [>=1.2]
+ var color: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 2 [br]
+ ## Note: possibly wrong if version < 1.2
+ var coordinates: Vector2
+
+
+ static func from_bytes(bytes: ByteStream, version: Version) -> TextureCoordinate:
+ var data = TextureCoordinate.new()
+
+ if version.higher_than(1, 1):
+ data.color = bytes.decode_u32()
+
+ data.coordinates = Vector2(
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+
+ return data
+
+
+class Face:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ ## Versions: [>=2.2]
+ var length: int
+
+ ## Byte Type: u16 [br]
+ ## Byte Length: 2 * 3
+ ## Length: 3
+ var vertex_position_indices: Array[int]
+
+ ## Byte Type: u16 [br]
+ ## Byte Length: 2 * 3
+ ## Length: 3
+ var texture_coordinate_indices: Array[int]
+
+ ## Byte Type: u16 [br]
+ ## Byte Length: 2
+ var texture_index: int
+
+ ## Byte Type: u16 [br]
+ ## Byte Length: 2
+ var padding: int
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var two_sided: int
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var smooth_group: int
+
+ ## Byte Type: i32 [br]
+ ## Length: ([member length] - 24) / 4 [br]
+ ## Versions: [>=2.2]
+ # TODO: saturating_sub?
+ var smooth_group_extra: Array[int]
+
+
+ static func from_bytes(bytes: ByteStream, version: Version) -> Face:
+ var data = Face.new()
+
+ if version.higher_than(2, 1):
+ data.length = bytes.decode_u32()
+
+ data.vertex_position_indices = [] as Array[int]
+ for _in in 3:
+ data.vertex_position_indices.append(bytes.decode_u16())
+
+ data.texture_coordinate_indices = [] as Array[int]
+ for _in in 3:
+ data.texture_coordinate_indices.append(bytes.decode_u16())
+
+ data.texture_index = bytes.decode_u16()
+ data.padding = bytes.decode_u16()
+ data.two_sided = bytes.decode_s32()
+ data.smooth_group = bytes.decode_s32()
+
+ if version.higher_than(2, 1):
+ data.smooth_group_extra = [] as Array[int]
+
+ for _n in ((data.length - 24) / 4):
+ data.smooth_group_extra.append(bytes.decode_s32())
+
+ return data
+
+
+class ScaleKeyframe:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ var frame: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ var scale: Vector3
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 [br]
+ var reserved: float
+
+
+ static func from_bytes(bytes: ByteStream) -> ScaleKeyframe:
+ var data = ScaleKeyframe.new()
+
+ data.frame = bytes.decode_u32()
+ data.scale = Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+ data.reserved = bytes.decode_float()
+
+ return data
+
+
+class RotationKeyframe:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ var frame: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ var rotation: Vector4
+
+
+ static func from_bytes(bytes: ByteStream) -> RotationKeyframe:
+ var data = RotationKeyframe.new()
+
+ data.frame = bytes.decode_u32()
+ data.rotation = Vector4(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+
+ return data
+
+
+class TranslationKeyframe:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ var frame: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 * 3 [br]
+ var translation: Vector3
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 [br]
+ var reserved: float
+
+
+ static func from_bytes(bytes: ByteStream) -> ScaleKeyframe:
+ var data = ScaleKeyframe.new()
+
+ data.frame = bytes.decode_u32()
+ data.translation = Vector3(
+ bytes.decode_float(),
+ bytes.decode_float(),
+ bytes.decode_float()
+ )
+ data.reserved = bytes.decode_float()
+
+ return data
+
+
+enum TextureOperation {
+ ## Texture translation on the X axis. The texture is tiled.
+ Translation_X,
+
+ ## Texture translation on the Y axis. The texture is tiled.
+ Translation_Y,
+
+ ## Texture multiplication on the X axis. The texture is tiled.
+ Scale_X,
+
+ ## Texture multiplication on the Y axis. The texture is tiled.
+ Scale_Y,
+
+ ## Texture rotation around (0, 0). The texture is not tiled.
+ Rotation,
+}
+
+
+class TexturesKeyframe:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var texture_index: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var texture_keyframe_count: int
+
+ ## Length: [member texture_keyframe_count]
+ var texture_keyframes: Array[TextureKeyframe]
+
+
+ static func from_bytes(bytes: ByteStream) -> TexturesKeyframe:
+ var data = TexturesKeyframe.new()
+
+ data.texture_index = bytes.decode_u32()
+ data.texture_keyframe_count = bytes.decode_u32()
+
+ data.texture_keyframes = [] as Array[TextureKeyframe]
+ for _n in data.texture_keyframe_count:
+ data.texture_keyframes.append(TextureKeyframe.from_bytes(bytes))
+
+ return data
+
+
+class TextureKeyframe:
+ ## Byte Type: u8
+ var operation_type: TextureOperation
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var frame_count: int
+
+ ## Length: [member frame_count]
+ var texture_frames: Array[TextureFrame]
+
+
+ static func from_bytes(bytes: ByteStream) -> TextureKeyframe:
+ var data = TextureKeyframe.new()
+
+ data.operation_type = bytes.decode_u8()
+ data.frame_count = bytes.decode_u32()
+
+ data.texture_frames = [] as Array[TextureFrame]
+ for _n in data.frame_count:
+ data.texture_frames.append(TextureFrame.from_bytes(bytes))
+
+ return data
+
+
+class TextureFrame:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4 [br]
+ var frame: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4 [br]
+ var translation: float
+
+
+ static func from_bytes(bytes: ByteStream) -> TextureFrame:
+ var data = TextureFrame.new()
+
+ data.frame = bytes.decode_u32()
+ data.translation = bytes.decode_float()
+
+ return data
diff --git a/extractor/rsw_format.gd b/extractor/rsw_format.gd
index ea1bf26..831a1cb 100644
--- a/extractor/rsw_format.gd
+++ b/extractor/rsw_format.gd
@@ -105,6 +105,40 @@ static func from_bytes(bytes: ByteStream) -> RSWFormat:
return rsw_format
+func convert(name: String, data_path: String) -> Node3D:
+ var gnd_file_path = "%s/data/%s" % [data_path, gnd_file]
+ var gnd = GNDFormat.from_bytes(ByteStream.from_bytes(FileAccess.get_file_as_bytes(gnd_file_path)))
+
+ var gat_file_path = "%s/data/%s" % [data_path, gat_file]
+ var gat = GATFormat.from_bytes(ByteStream.from_bytes(FileAccess.get_file_as_bytes(gat_file_path)))
+
+ var node := Node3D.new()
+ node.name = name
+ node.set_script(load("res://extractor/map.gd"))
+
+ for resource in map_resources:
+ if resource is RSWFormat.SpatialAudioSource:
+ var audio_file_path := "%s/data/wav/%s" % [data_path, resource.audio_file]
+ if not FileAccess.file_exists(audio_file_path):
+ continue
+
+ var audio = AudioStreamPlayer3D.new()
+ audio.stream = load(audio_file_path)
+ audio.name = resource.audio_file
+ audio.position = resource.get_position()
+ audio.volume_linear = resource.volume_gain
+ audio.max_distance = resource.audio_range
+ node.add_child(audio, true)
+ audio.owner = node
+
+ var surface_tool := SurfaceTool.new()
+ for surface: GNDFormat.Surface in gnd.surfaces:
+ pass
+ #surface_tool.add_vertex()
+
+ return node
+
+
class WaterConfiguration:
## Byte Type: f32 [br]
## Byte Length: 4
diff --git a/extractor/version.gd b/extractor/version.gd
index 679a8f0..ae97a10 100644
--- a/extractor/version.gd
+++ b/extractor/version.gd
@@ -11,5 +11,25 @@ var major: int
var minor: int
+func lower_than(compare_major: int, compare_minor: int) -> bool:
+ if (major > compare_major):
+ return false
+
+ if (major == compare_major):
+ return minor < compare_minor
+
+ return true
+
+
+func higher_than(compare_major: int, compare_minor: int) -> bool:
+ if (major > compare_major):
+ return true
+
+ if (major == compare_major):
+ return minor > compare_minor
+
+ return false
+
+
func _to_string() -> String:
return "%s.%s" % [major, minor]
diff --git a/network/server.gd b/network/server.gd
index 8b41b4f..3b3b9cd 100644
--- a/network/server.gd
+++ b/network/server.gd
@@ -3,6 +3,8 @@ class_name Server
signal received_packet(packet: Packet)
+var received_packets: Dictionary # [int, Array[Packet]]
+
var host: String
var port: int
var peer: StreamPeerTCP = StreamPeerTCP.new()
@@ -38,7 +40,11 @@ func listen() -> void:
prints("Upcoming Length:", packet_length.decode_u16(0), "for =>")
var packet = packet_type.from_bytes(raw_packet)
-
+
+ if not received_packets.has(header):
+ received_packets[header] = []
+ received_packets[header].append(packet)
+
received_packet.emit(packet)
var display_header = raw_packet.slice(0, 2)
diff --git a/packets/character_information.gd b/packets/character_information.gd
index a2ac0ab..9c940c5 100644
--- a/packets/character_information.gd
+++ b/packets/character_information.gd
@@ -182,6 +182,10 @@ var character_name_change: int
var gender: Constants.Gender
+func get_map_name() -> String:
+ return map_name.substr(0, map_name.length() - 4)
+
+
static func from_bytes(bytes: PackedByteArray):
var info = CharacterInformation.new()
diff --git a/project.godot b/project.godot
index 1501b51..ff70e52 100644
--- a/project.godot
+++ b/project.godot
@@ -13,7 +13,7 @@ config_version=5
config/name="Minerva"
run/main_scene="res://ui/login.tscn"
config/features=PackedStringArray("4.4", "Forward Plus")
-config/icon="res://icon.svg"
+config/icon="uid://du8c0ll5pq5ci"
[autoload]
diff --git a/rathena-icon.png b/rathena-icon.png
new file mode 100644
index 0000000..9fab873
--- /dev/null
+++ b/rathena-icon.png
Binary files differ
diff --git a/rathena-icon.png.import b/rathena-icon.png.import
new file mode 100644
index 0000000..7b02fa5
--- /dev/null
+++ b/rathena-icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://du8c0ll5pq5ci"
+path="res://.godot/imported/rathena-icon.png-a6f5753273990ca7c83a6018bb123585.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://rathena-icon.png"
+dest_files=["res://.godot/imported/rathena-icon.png-a6f5753273990ca7c83a6018bb123585.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/ui/chat_window.gd b/ui/chat_window.gd
index c88a3ee..7841d57 100644
--- a/ui/chat_window.gd
+++ b/ui/chat_window.gd
@@ -3,6 +3,9 @@ extends PanelContainer
@export var player_color: Color = Color8(255, 255, 255)
+var is_dragging := false
+var drag_anchor := Vector2.ZERO
+
func _ready() -> void:
# clear test label
@@ -57,3 +60,17 @@ func _on_broadcast_formatted_message_packet_received(packet: BroadcastFormattedM
format.size = packet.font_size
add_message(packet.message, format)
+
+
+func _process(_delta: float) -> void:
+ if is_dragging:
+ global_position += get_global_mouse_position() - drag_anchor
+ drag_anchor = get_global_mouse_position()
+
+
+func _on_handle_gui_input(event: InputEvent) -> void:
+ if event.is_action_pressed("primary_click"):
+ is_dragging = true
+ drag_anchor = get_global_mouse_position()
+ elif event.is_action_released("primary_click"):
+ is_dragging = false
diff --git a/ui/chat_window.tscn b/ui/chat_window.tscn
index e67d360..b430fb8 100644
--- a/ui/chat_window.tscn
+++ b/ui/chat_window.tscn
@@ -1,6 +1,8 @@
-[gd_scene load_steps=3 format=3 uid="uid://c8uqw08hxfqlu"]
+[gd_scene load_steps=5 format=3 uid="uid://c8uqw08hxfqlu"]
[ext_resource type="Script" uid="uid://cy5bwkc4gokw1" path="res://ui/chat_window.gd" id="1_vovuq"]
+[ext_resource type="PackedScene" uid="uid://cjcm2mai50thr" path="res://ui/bmp_texture_button.tscn" id="2_2x3wj"]
+[ext_resource type="Texture2D" uid="uid://dqq3mtjcrwqnp" path="res://client_data/data/texture/À¯ÀúÀÎÅÍÆäÀ̽º/basic_interface/sys_base_off.bmp" id="3_smgio"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ku06j"]
bg_color = Color(0.133333, 0.133333, 0.133333, 0.784314)
@@ -37,9 +39,23 @@ layout_mode = 2
layout_mode = 2
text = "Label"
-[node name="LineEdit" type="LineEdit" parent="VBoxContainer"]
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="ToPlayer" type="LineEdit" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
+
+[node name="LineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
placeholder_text = "Send Messages here"
-[connection signal="text_submitted" from="VBoxContainer/LineEdit" to="." method="_on_line_edit_text_submitted"]
+[node name="Handle" parent="VBoxContainer/HBoxContainer" instance=ExtResource("2_2x3wj")]
+layout_mode = 2
+texture_normal = ExtResource("3_smgio")
+stretch_mode = 5
+
+[connection signal="text_submitted" from="VBoxContainer/HBoxContainer/LineEdit" to="." method="_on_line_edit_text_submitted"]
+[connection signal="gui_input" from="VBoxContainer/HBoxContainer/Handle" to="." method="_on_handle_gui_input"]
diff --git a/ui/login.gd b/ui/login.gd
index 8cabec8..fb3c80b 100644
--- a/ui/login.gd
+++ b/ui/login.gd
@@ -15,8 +15,6 @@ var current_character_information: CharacterInformation
func _ready() -> void:
switch_screen(%Login)
#$BackgroundMusic.play()
-
- %ChatWindow.visible = false
func switch_screen(screen: Node):
@@ -94,11 +92,15 @@ func _on_character_server_login_pressed(character_server_info: CharacterServerIn
current_character_slot_idx = slot_idx
%CharacterSelectionSlotLabel.text = "%s/%s" % [str(slot_idx + 1), login_character_list.slot_count]
- if slot_idx < character_list.character_information.size():
- current_character_information = character_list.character_information[slot_idx]
+ var info = login_character_list.get_info_for_slot(slot_idx)
+ if info:
+ current_character_information = info
else:
current_character_information = null
)
+ %CharacterSelectionList.requested_login.connect(func(slot_idx: int):
+ _on_character_selected_pressed(slot_idx)
+ )
# pre-select first character
%CharacterSelectionList.select(0)
@@ -117,6 +119,7 @@ func _on_character_selected_pressed(slot_idx: int):
return
Client.character.name = current_character_information.name
+ Client.character.info = current_character_information
Network.map_server = MapServer.new(
selected_character.get_map_server_ip(),
@@ -131,16 +134,9 @@ func _on_character_selected_pressed(slot_idx: int):
)
var _logged_in = await Network.map_server.logged_in
- # TODO: switch to game :)
-
- %ChatWindow.visible = true
- %ChatWindow.initialize()
-
- # TODO: load map
- var map_loaded_packet := MapLoadedPacket.new()
- Network.map_server.send(map_loaded_packet)
-
- # TODO: check which map server packets to send next
+ get_tree().change_scene_to_file(
+ "res://client_data/data/%s.tscn" % current_character_information.get_map_name()
+ )
func _on_character_server_back_button_pressed() -> void:
diff --git a/ui/login.tscn b/ui/login.tscn
index 4842c26..172b00e 100644
--- a/ui/login.tscn
+++ b/ui/login.tscn
@@ -46,7 +46,6 @@ stretch_mode = 6
[node name="Login" type="CenterContainer" parent="."]
unique_name_in_owner = true
-visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
@@ -174,6 +173,7 @@ texture_hover = ExtResource("16_hqeko")
[node name="CharacterSelection" type="CenterContainer" parent="."]
unique_name_in_owner = true
+visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
@@ -305,6 +305,7 @@ stream = ExtResource("3_2nukd")
[node name="ChatWindow" parent="." instance=ExtResource("4_ah2a1")]
unique_name_in_owner = true
+visible = false
layout_mode = 1
anchors_preset = -1
anchor_top = 0.829
diff --git a/ui/login/login_character_selection_list.gd b/ui/login/login_character_selection_list.gd
index 221c47f..bd19fa1 100644
--- a/ui/login/login_character_selection_list.gd
+++ b/ui/login/login_character_selection_list.gd
@@ -3,6 +3,7 @@ extends Control
signal selected(slot_idx: int)
+signal requested_login(slot_idx: int)
var item_scene := preload("res://ui/login/character_selection_item.tscn")
@@ -40,7 +41,6 @@ func set_selected_slot_index(value: int) -> void:
if selected_slot_index == value:
return
- var character_list := login_character_list.character_information
var maximum_slot_count := login_character_list.slot_count
%CharacterList.get_child(selected_slot_index - slot_offset).is_selected = false
@@ -57,13 +57,14 @@ func set_selected_slot_index(value: int) -> void:
slot_offset = min(selected_slot_index, maximum_slot_count - displayed_slots_count)
elif selected_slot_index < slot_offset:
#slot_offset = selected_slot_index
- slot_offset = max(0, selected_slot_index - displayed_slots_count)
+ slot_offset = max(0, selected_slot_index - (displayed_slots_count - 1))
draw()
%CharacterList.get_child(selected_slot_index - slot_offset).is_selected = true
- if selected_slot_index < character_list.size():
- %CharacterSelectionStatus.set_info(character_list[selected_slot_index])
+ var info = login_character_list.get_info_for_slot(selected_slot_index)
+ if info:
+ %CharacterSelectionStatus.set_info(info)
else:
$CharacterSelectionStatus.clear()
@@ -79,14 +80,12 @@ func draw():
if not login_character_list:
return
- var character_information: Array[CharacterInformation] = login_character_list.character_information
-
for display_slot_idx in displayed_slots_count:
var item: CharacterSelectionItem = %CharacterList.get_child(display_slot_idx)
var slot_idx = slot_offset + display_slot_idx
- if slot_idx < character_information.size():
- var info: CharacterInformation = character_information[slot_idx]
+ var info = login_character_list.get_info_for_slot(slot_idx)
+ if info:
item.initialize_with_info(info)
else:
item.clear()
@@ -94,6 +93,10 @@ func draw():
if item.selected.is_connected(_on_item_selected):
item.selected.disconnect(_on_item_selected)
item.selected.connect(_on_item_selected.bind(slot_idx))
+
+ if item.gui_input.is_connected(_on_item_gui_input):
+ item.gui_input.disconnect(_on_item_gui_input)
+ item.gui_input.connect(_on_item_gui_input.bind(slot_idx))
func select(slot_idx: int):
@@ -104,6 +107,21 @@ func _on_item_selected(slot_idx: int):
selected_slot_index = slot_idx
+func _on_item_gui_input(event: InputEvent, slot_idx: int) -> void:
+ if event is InputEventMouseButton and event.double_click:
+ requested_login.emit(slot_idx)
+
+
+func _input(event: InputEvent) -> void:
+ if event.is_action_pressed("ui_accept"):
+ requested_login.emit(selected_slot_index)
+
+ if event.is_action_pressed("ui_left"):
+ %ButtonLeft.pressed.emit()
+ if event.is_action_pressed("ui_right"):
+ %ButtonRight.pressed.emit()
+
+
func _on_button_left_pressed() -> void:
selected_slot_index -= 1
diff --git a/ui/login/login_character_selection_list.tscn b/ui/login/login_character_selection_list.tscn
index 1a5aba7..391ed63 100644
--- a/ui/login/login_character_selection_list.tscn
+++ b/ui/login/login_character_selection_list.tscn
@@ -27,6 +27,7 @@ theme_override_constants/separation = 16
alignment = 1
[node name="ButtonLeft" parent="MarginContainer/HBoxContainer" instance=ExtResource("2_s7n6r")]
+unique_name_in_owner = true
texture_filter = 0
layout_mode = 2
texture_normal = ExtResource("3_c5a25")
@@ -46,6 +47,7 @@ layout_mode = 2
layout_mode = 2
[node name="ButtonRight" parent="MarginContainer/HBoxContainer" instance=ExtResource("2_s7n6r")]
+unique_name_in_owner = true
texture_filter = 0
layout_mode = 2
texture_normal = ExtResource("7_c8nb3")