diff options
author | Daniel Weipert <git@mail.dweipert.de> | 2025-01-05 16:54:36 +0100 |
---|---|---|
committer | Daniel Weipert <git@mail.dweipert.de> | 2025-01-05 16:54:36 +0100 |
commit | 35b0f811f23f029110373798b19d9d0895d907f0 (patch) | |
tree | 2e4906a992c2569fa0f89dbe0f079010a734cc41 | |
parent | e08a29e73ea4f7e6d78e8e7f5a6e7033dbc1f542 (diff) |
next commit
-rw-r--r-- | client.gd | 5 | ||||
-rw-r--r-- | constants.gd | 11 | ||||
-rw-r--r-- | data_models/login_character_list.gd | 8 | ||||
-rw-r--r-- | data_models/player_data.gd | 6 | ||||
-rw-r--r-- | entities/player.gd | 40 | ||||
-rw-r--r-- | entities/player.tscn | 24 | ||||
-rw-r--r-- | extractor/action_format.gd | 182 | ||||
-rw-r--r-- | extractor/grf.gd | 225 | ||||
-rw-r--r-- | extractor/map.gd | 34 | ||||
-rw-r--r-- | extractor/rsm_format.gd | 521 | ||||
-rw-r--r-- | extractor/rsw_format.gd | 34 | ||||
-rw-r--r-- | extractor/version.gd | 20 | ||||
-rw-r--r-- | network/server.gd | 8 | ||||
-rw-r--r-- | packets/character_information.gd | 4 | ||||
-rw-r--r-- | project.godot | 2 | ||||
-rw-r--r-- | rathena-icon.png | bin | 0 -> 15143 bytes | |||
-rw-r--r-- | rathena-icon.png.import | 34 | ||||
-rw-r--r-- | ui/chat_window.gd | 17 | ||||
-rw-r--r-- | ui/chat_window.tscn | 22 | ||||
-rw-r--r-- | ui/login.gd | 24 | ||||
-rw-r--r-- | ui/login.tscn | 3 | ||||
-rw-r--r-- | ui/login/login_character_selection_list.gd | 34 | ||||
-rw-r--r-- | ui/login/login_character_selection_list.tscn | 2 |
23 files changed, 1011 insertions, 249 deletions
@@ -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 Binary files differnew file mode 100644 index 0000000..9fab873 --- /dev/null +++ b/rathena-icon.png 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") |