Compare commits

...

2 Commits

Author SHA1 Message Date
copygirl a3e8841667 Remove type information from own types 1 month ago
copygirl 2290e058b6 Add player stats tab 1 month ago
  1. 2
      Scenes/copy_multiplayer_settings.gd
  2. 49
      Scenes/copy_multiplayer_settings.tscn
  3. 2
      Scenes/player_settings.gd
  4. 10
      Scenes/player_settings.tscn
  5. 33
      Scenes/player_stats.gd
  6. 32
      Scenes/player_stats.tscn
  7. 71
      Scenes/sync_controller.gd
  8. 10
      Utility/stream_buffer.gd
  9. 100
      copyMultiplayer.gd

@ -1,4 +1,4 @@
class_name copyMultiplayerSettings #class_name copyMultiplayerSettings
extends TabContainer extends TabContainer
@export var visible_icon : Texture2D @export var visible_icon : Texture2D

@ -4,7 +4,7 @@
[ext_resource type="Texture2D" uid="uid://qbho5oyu1kfa" path="res://Mods/copyMultiplayer/Resources/hidden.png" id="2_1u5pu"] [ext_resource type="Texture2D" uid="uid://qbho5oyu1kfa" path="res://Mods/copyMultiplayer/Resources/hidden.png" id="2_1u5pu"]
[ext_resource type="Texture2D" uid="uid://dcmljlb2v6p16" path="res://Mods/copyMultiplayer/Resources/visible.png" id="2_ibe7i"] [ext_resource type="Texture2D" uid="uid://dcmljlb2v6p16" path="res://Mods/copyMultiplayer/Resources/visible.png" id="2_ibe7i"]
[node name="copyMultiplayerSettings" type="TabContainer" node_paths=PackedStringArray("connect_address", "connect_reveal", "connect_port", "connect_join", "connect_host", "connect_disconnect", "settings_cache", "settings_file_dialog", "settings_nickname", "stats_ping", "stats_average", "stats_maximum")] [node name="copyMultiplayerSettings" type="TabContainer" node_paths=PackedStringArray("connect_address", "connect_reveal", "connect_port", "connect_join", "connect_host", "connect_disconnect", "settings_cache", "settings_file_dialog", "settings_nickname")]
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
@ -164,5 +164,52 @@ metadata/_tab_index = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Players"] [node name="VBoxContainer" type="VBoxContainer" parent="Players"]
layout_mode = 2 layout_mode = 2
[node name="Stats" type="MarginContainer" parent="."]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 4
metadata/_tab_index = 3
[node name="VBoxContainer" type="VBoxContainer" parent="Stats"]
layout_mode = 2
[node name="Header" type="HBoxContainer" parent="Stats/VBoxContainer"]
layout_mode = 2
[node name="Player" type="Label" parent="Stats/VBoxContainer/Header"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.75, 0.75, 0.75, 1)
text = "Player"
horizontal_alignment = 1
[node name="Ping" type="Label" parent="Stats/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.75, 0.75, 0.75, 1)
text = "Ping"
horizontal_alignment = 1
[node name="Average" type="Label" parent="Stats/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.75, 0.75, 0.75, 1)
text = "Avg."
horizontal_alignment = 1
[node name="Maximum" type="Label" parent="Stats/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.75, 0.75, 0.75, 1)
text = "Max."
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Stats/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 1
[connection signal="toggled" from="Connect/VBoxContainer/Join/ShowHide" to="." method="_on_show_hide_address_toggled"] [connection signal="toggled" from="Connect/VBoxContainer/Join/ShowHide" to="." method="_on_show_hide_address_toggled"]
[connection signal="pressed" from="Settings/VBoxContainer/Cache/Button" to="." method="_on_cache_dir_dialog_pressed"] [connection signal="pressed" from="Settings/VBoxContainer/Cache/Button" to="." method="_on_cache_dir_dialog_pressed"]

@ -1,4 +1,4 @@
class_name PlayerSettings #class_name PlayerSettings
extends Container extends Container
signal value_changed(Transform3D) signal value_changed(Transform3D)

@ -104,7 +104,7 @@ horizontal_alignment = 2
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
max_value = 359.0 max_value = 359.0
step = 0.1 step = 0.01
allow_greater = true allow_greater = true
allow_lesser = true allow_lesser = true
alignment = 2 alignment = 2
@ -121,7 +121,7 @@ horizontal_alignment = 2
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
max_value = 359.0 max_value = 359.0
step = 0.1 step = 0.01
allow_greater = true allow_greater = true
allow_lesser = true allow_lesser = true
alignment = 2 alignment = 2
@ -138,7 +138,7 @@ horizontal_alignment = 2
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
max_value = 359.0 max_value = 359.0
step = 0.1 step = 0.01
allow_greater = true allow_greater = true
allow_lesser = true allow_lesser = true
alignment = 2 alignment = 2
@ -164,8 +164,8 @@ horizontal_alignment = 2
[node name="SpinBox" type="SpinBox" parent="PanelContainer/MarginContainer/VBoxContainer/Scale"] [node name="SpinBox" type="SpinBox" parent="PanelContainer/MarginContainer/VBoxContainer/Scale"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
min_value = 0.5 min_value = 0.1
max_value = 2.0 max_value = 10.0
step = 0.01 step = 0.01
value = 1.0 value = 1.0
alignment = 2 alignment = 2

@ -0,0 +1,33 @@
#class_name PlayerStats
extends Container
var history: Array[Dictionary] = []
var ping := 0
func set_nickname(value: String) -> void:
$Nickname.text = value
func set_ping(value: int) -> void:
$Ping.text = str(value) + " ms"
ping = value
# Called when model animation is sent / received.
func push(buffer_size: int) -> void:
history.append({
time = Time.get_ticks_msec(),
size = buffer_size,
})
func _process(_delta: float) -> void:
var now := Time.get_ticks_msec()
while history.size() > 0 && now > history[0].time + 1000:
history.pop_front()
var total := 0
var maximum := 0
for entry in history:
total += entry.size
maximum = maxi(maximum, entry.size)
$Average.text = "%.1f kB/s" % (float(total) / 1000)
$Maximum.text = "%d bytes" % maximum

@ -0,0 +1,32 @@
[gd_scene load_steps=2 format=3 uid="uid://dlodft3egwy0p"]
[ext_resource type="Script" path="res://Mods/copyMultiplayer/Scenes/player_stats.gd" id="1_rd8lb"]
[node name="Stats" type="HBoxContainer"]
script = ExtResource("1_rd8lb")
[node name="Nickname" type="Label" parent="."]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Nickname"
horizontal_alignment = 1
clip_text = true
text_overrun_behavior = 3
[node name="Ping" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = "0 ms"
horizontal_alignment = 2
[node name="Average" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = "0 kB/s"
horizontal_alignment = 2
[node name="Maximum" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
text = "0 B"
horizontal_alignment = 2

@ -1,10 +1,12 @@
class_name SyncController #class_name SyncController
extends Area3D extends Area3D
static var StreamBuffer = load("res://Mods/copyMultiplayer/Utility/stream_buffer.gd")
@export var shape: CollisionShape3D @export var shape: CollisionShape3D
var peer_id: int var peer_id: int
var module: copyMultiplayer var module #: copyMultiplayer
var model_controller: ModelController var model_controller: ModelController
var nickname: String var nickname: String
@ -18,13 +20,16 @@ var model : Node
var skeleton : Skeleton3D var skeleton : Skeleton3D
var anim_player : AnimationPlayer var anim_player : AnimationPlayer
var anim_root : Node var anim_root : Node
var settings : PlayerSettings
var settings #: PlayerSettings
var stats #: PlayerStats
var pings : Dictionary
var is_dragging := false var is_dragging := false
var drag_current: Vector3 var drag_current: Vector3
# Reusable buffer to write data for synchronizing models. # Reusable buffer to write data for synchronizing models.
static var write_stream: StreamBuffer = StreamBuffer.with_capacity(2048) static var write_stream = StreamBuffer.with_capacity(2048)
func _ready() -> void: func _ready() -> void:
peer_id = name.to_int() peer_id = name.to_int()
@ -34,16 +39,27 @@ func _ready() -> void:
add_child(model_controller) add_child(model_controller)
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
update_ping_stat()
update_collision_shape_position() update_collision_shape_position()
handle_dragging() handle_dragging()
func _exit_tree() -> void: func _exit_tree() -> void:
if settings: settings.queue_free() if settings: settings.queue_free()
if stats: stats.queue_free()
func get_display_name() -> String: func get_display_name() -> String:
if nickname: return "Player '%s' (%d)" % [ nickname, peer_id ] if nickname: return "Player '%s' (%d)" % [ nickname, peer_id ]
else: return "Player (%d)" % peer_id else: return "Player (%d)" % peer_id
func update_ping_stat() -> void:
if not stats: return
var peer := multiplayer.multiplayer_peer.get_peer(peer_id) as ENetMultiplayerPeer
if not peer: return
var ping := int(peer.get_statistic(ENetPacketPeer.PEER_LAST_ROUND_TRIP_TIME))
stats.set_ping(ping)
func change_nickname(new_nickname: String) -> void: func change_nickname(new_nickname: String) -> void:
new_nickname = new_nickname.strip_edges() new_nickname = new_nickname.strip_edges()
if new_nickname == "": return # Ignore empty nicknames. if new_nickname == "": return # Ignore empty nicknames.
@ -57,6 +73,10 @@ func change_nickname(new_nickname: String) -> void:
settings.value_changed.connect(func(value): transform = value) settings.value_changed.connect(func(value): transform = value)
settings.set_nickname(nickname) settings.set_nickname(nickname)
if !stats:
stats = module.new_player_stats()
stats.set_nickname(nickname)
## Attempts to change the model of this player. ## Attempts to change the model of this player.
func change_model( func change_model(
new_version: int, new_version: int,
@ -73,7 +93,7 @@ func change_model(
if not filename.is_valid_filename(): if not filename.is_valid_filename():
module.print_log("ERROR: '%s' is not a valid file name!" % filename) module.print_log("ERROR: '%s' is not a valid file name!" % filename)
return return
var full_path := module.cache.path_join(filename) var full_path: String = module.cache.path_join(filename)
if not FileAccess.file_exists(full_path): if not FileAccess.file_exists(full_path):
module.print_log("%s wanted to switch to '%s', but it doesn't exist, skipping" % [ get_display_name(), filename ]) module.print_log("%s wanted to switch to '%s', but it doesn't exist, skipping" % [ get_display_name(), filename ])
return return
@ -91,6 +111,9 @@ func change_model(
module.print_log("%s switched to '%s'" % [ get_display_name(), filename ]) module.print_log("%s switched to '%s'" % [ get_display_name(), filename ])
# TODO: Buffer and process 1 frame later if receiving 2 per frame or similar.
# Otherwise, we might receive 2 in a single frame and then 0 in the next,
# essentially halfing our framerate for double the bandwidth.
func sync_model_animation( func sync_model_animation(
current_version: int, current_version: int,
uncompressed_length: int, uncompressed_length: int,
@ -98,9 +121,10 @@ func sync_model_animation(
) -> void: ) -> void:
if version != current_version: return if version != current_version: return
if (not model) or (not skeleton) or (not anim_root): return if (not model) or (not skeleton) or (not anim_root): return
if stats: stats.push(buffer.size())
var uncompressed_buffer := buffer.decompress(uncompressed_length, FileAccess.COMPRESSION_ZSTD); var uncompressed_buffer := buffer.decompress(uncompressed_length, FileAccess.COMPRESSION_ZSTD);
var stream := StreamBuffer.from_buffer(uncompressed_buffer) var stream = StreamBuffer.from_buffer(uncompressed_buffer)
model.transform = stream.read_transform16() model.transform = stream.read_transform16()
@ -118,10 +142,10 @@ func sync_model_animation(
# 256 bones (and blendshapes) should be enough, right? # 256 bones (and blendshapes) should be enough, right?
for i in stream.read_uint8(): for i in stream.read_uint8():
var lookup := stream.read_uint8() var lookup: int = stream.read_uint8()
all_bones[lookup].pose = stream.read_bone_pose(all_bones[lookup].pose) all_bones[lookup].pose = stream.read_bone_pose(all_bones[lookup].pose)
for i in stream.read_uint8(): for i in stream.read_uint8():
var lookup := stream.read_uint8() var lookup: int = stream.read_uint8()
all_blendshapes[lookup].value = stream.read_range16() all_blendshapes[lookup].value = stream.read_range16()
# Apply all the values to bones / blendshapes. # Apply all the values to bones / blendshapes.
@ -134,14 +158,14 @@ func sync_model_animation(
anim_node.set("blend_shapes/" + blendshape.path.get_subname(0), blendshape.value) anim_node.set("blend_shapes/" + blendshape.path.get_subname(0), blendshape.value)
@warning_ignore("shadowed_variable") @warning_ignore("shadowed_variable")
static func send_model_animation(module: copyMultiplayer) -> void: static func send_model_animation(module) -> void:
# Check if there's other players we're connected to. # Check if there's other players we're connected to.
if module.multiplayer.get_peers().size() == 0: return if module.multiplayer.get_peers().size() == 0: return
var model := module.get_model() var model = module.get_model()
var skeleton := module.get_skeleton() var skeleton = module.get_skeleton()
var anim_player := model.find_child("AnimationPlayer", false, false) var anim_player = model.find_child("AnimationPlayer", false, false)
var anim_root := anim_player.get_node_or_null(anim_player.root_node) var anim_root = anim_player.get_node_or_null(anim_player.root_node)
if (not model) or (not skeleton) or (not anim_root): return if (not model) or (not skeleton) or (not anim_root): return
write_stream.write_transform16(model.transform) write_stream.write_transform16(model.transform)
@ -152,16 +176,16 @@ static func send_model_animation(module: copyMultiplayer) -> void:
var active_blendshapes := [] var active_blendshapes := []
for i in module.bone_lookup.size(): for i in module.bone_lookup.size():
var bone_name := module.bone_lookup[i] var bone_name = module.bone_lookup[i]
var bone_idx := skeleton.find_bone(bone_name) var bone_idx = skeleton.find_bone(bone_name)
var bone_pose := skeleton.get_bone_pose(bone_idx) var bone_pose = skeleton.get_bone_pose(bone_idx)
var bone_rest := skeleton.get_bone_rest(bone_idx) var bone_rest = skeleton.get_bone_rest(bone_idx)
if not bone_pose.is_equal_approx(bone_rest): if not bone_pose.is_equal_approx(bone_rest):
restless_bones.append({ lookup = i, pose = bone_pose, rest = bone_rest }) restless_bones.append({ lookup = i, pose = bone_pose, rest = bone_rest })
for i in module.blendshape_lookup.size(): for i in module.blendshape_lookup.size():
var anim_path := module.blendshape_lookup[i] var anim_path = module.blendshape_lookup[i]
var anim_node := anim_root.get_node_or_null(anim_path) var anim_node = anim_root.get_node_or_null(anim_path)
var value: float = anim_node.get("blend_shapes/" + anim_path.get_subname(0)) var value: float = anim_node.get("blend_shapes/" + anim_path.get_subname(0))
if not is_zero_approx(value): if not is_zero_approx(value):
active_blendshapes.append({ lookup = i, value = value }) active_blendshapes.append({ lookup = i, value = value })
@ -177,10 +201,11 @@ static func send_model_animation(module: copyMultiplayer) -> void:
write_stream.write_range16(blendshape.value) write_stream.write_range16(blendshape.value)
# The compression still helps, so we'll keep it for now. # The compression still helps, so we'll keep it for now.
var compressed_buffer := write_stream.slice().compress(FileAccess.COMPRESSION_ZSTD); var compressed_buffer = write_stream.slice().compress(FileAccess.COMPRESSION_ZSTD);
# Uncomment this to see packet size. Can we hit < 256 bytes? # Uncomment this to see packet size. Can we hit < 256 bytes?
# module.set_status("Packet size: %d bytes (%d uncompressed)" % [ compressed_buffer.size(), write_stream.size ]) # module.set_status("Packet size: %d bytes (%d uncompressed)" % [ compressed_buffer.size(), write_stream.size ])
module.sync_model_animation.rpc(module.version, write_stream.size, compressed_buffer) module.sync_model_animation.rpc(module.version, write_stream.size, compressed_buffer)
module.main_stats.push(compressed_buffer.size())
write_stream.clear() write_stream.clear()
@ -214,8 +239,10 @@ func _get_bone_position(bone_name: String) -> Vector3:
return pose.origin return pose.origin
func _input_event(_camera: Node, event: InputEvent, func _input_event(
pos: Vector3, _normal: Vector3, _idx: int) -> void: _camera: Node, event: InputEvent,
pos: Vector3, _normal: Vector3, _idx: int
) -> void:
if (module.can_move_players if (module.can_move_players
&& event is InputEventMouseButton && event is InputEventMouseButton
&& event.button_index == MOUSE_BUTTON_LEFT && event.button_index == MOUSE_BUTTON_LEFT

@ -1,4 +1,4 @@
class_name StreamBuffer #class_name StreamBuffer
extends Resource extends Resource
# This maximum capacity is just to ensure we're not doing something wrong, # This maximum capacity is just to ensure we're not doing something wrong,
@ -18,15 +18,15 @@ func _init(_buffer: PackedByteArray) -> void:
## Creates a new StreamBuffer with the specified capacity. ## Creates a new StreamBuffer with the specified capacity.
## This is intended for writing / encoding data. ## This is intended for writing / encoding data.
static func with_capacity(initial_capacity: int) -> StreamBuffer: static func with_capacity(initial_capacity: int): #-> StreamBuffer:
var _buffer = PackedByteArray() var _buffer = PackedByteArray()
_buffer.resize(initial_capacity) _buffer.resize(initial_capacity)
return StreamBuffer.new(_buffer) return new(_buffer)
## Creates a new StreamBuffer from the specified buffer, pre-initializing "size". ## Creates a new StreamBuffer from the specified buffer, pre-initializing "size".
## This is intended for reading / decoding data. ## This is intended for reading / decoding data.
static func from_buffer(_buffer: PackedByteArray) -> StreamBuffer: static func from_buffer(_buffer: PackedByteArray): #-> StreamBuffer:
var stream := StreamBuffer.new(_buffer) var stream := new(_buffer)
stream.size = stream.capacity stream.size = stream.capacity
return stream return stream

@ -1,12 +1,18 @@
class_name copyMultiplayer #class_name copyMultiplayer
extends Mod_Base extends Mod_Base
# NOTE: Due to issues with resource pack loading at runtime, we can't use
# typed references to this mod. This is a crime to all slime-kind.
static var SyncController = load("res://Mods/copyMultiplayer/Scenes/sync_controller.gd")
@export var cache := "" @export var cache := ""
@export var nickname := "" @export var nickname := ""
@export var address := "" @export var address := ""
@export var port := 52410 @export var port := 52410
var main_controller: ModelController var main_controller: ModelController
var main_stats #: PlayerStats
var ping_update_timer: Timer
## Hardcoded list of bone names that will get syncronized. ## Hardcoded list of bone names that will get syncronized.
var tracked_bones: Array[String] var tracked_bones: Array[String]
@ -27,6 +33,7 @@ var can_move_players := false
func _ready() -> void: func _ready() -> void:
# FIXME: Hardcoded way to get the main model controller. # FIXME: Hardcoded way to get the main model controller.
main_controller = $"/root/SnekStudio_Main/ModelController" main_controller = $"/root/SnekStudio_Main/ModelController"
main_stats = new_player_stats()
# FIXME: This is just thrown together. Dunno if this is an accurate list. # FIXME: This is just thrown together. Dunno if this is an accurate list.
# TODO: Allow specifying additional bones, with the help of a hierachical list of existing bones in the model. # TODO: Allow specifying additional bones, with the help of a hierachical list of existing bones in the model.
@ -55,9 +62,9 @@ func _ready() -> void:
setup_button_connections() setup_button_connections()
# Filter whitespace characters from nickname before saving it to "nickname" field. # Filter whitespace characters from nickname before saving it to "nickname" field.
var nickname_widget := get_settings_window().settings_nickname var nickname_widget = get_settings_window().settings_nickname
nickname_widget.text_changed.connect(func(new_text): modify_setting("nickname", new_text.strip_edges())) nickname_widget.text_changed.connect(func(new_text): modify_setting("nickname", new_text.strip_edges()))
nickname_widget.text_submitted.connect(func(_new_text): nickname_widget.text = nickname) nickname_widget.text_submitted.connect(func(_new_text): nickname_widget.text = nickname; main_stats.set_nickname(nickname))
nickname_widget.focus_exited.connect(func(): nickname_widget.text = nickname) nickname_widget.focus_exited.connect(func(): nickname_widget.text = nickname)
multiplayer.peer_connected .connect(on_peer_connected) multiplayer.peer_connected .connect(on_peer_connected)
@ -73,11 +80,11 @@ func _create_settings_window() -> Control:
return load("res://Mods/copyMultiplayer/Scenes/copy_multiplayer_settings.tscn").instantiate() return load("res://Mods/copyMultiplayer/Scenes/copy_multiplayer_settings.tscn").instantiate()
# Override base method to provide type hint. # Override base method to provide type hint.
func get_settings_window() -> copyMultiplayerSettings: # func get_settings_window() -> copyMultiplayerSettings:
return super.get_settings_window() # return super.get_settings_window()
func setup_setting_widget(category: String, setting: String, setup_events: bool) -> void: func setup_setting_widget(category: String, setting: String, setup_events: bool) -> void:
var settings := get_settings_window() var settings = get_settings_window()
var widget: Control = settings.get("%s_%s" % [ category, setting ]) var widget: Control = settings.get("%s_%s" % [ category, setting ])
_settings_properties.append({ name = setting, args = { } }) _settings_properties.append({ name = setting, args = { } })
@ -94,18 +101,29 @@ func setup_setting_widget(category: String, setting: String, setup_events: bool)
widget.set_meta("reset_button", { }) widget.set_meta("reset_button", { })
func setup_button_connections() -> void: func setup_button_connections() -> void:
var settings := get_settings_window() var settings = get_settings_window()
settings.connect_join.pressed.connect(on_join_pressed) settings.connect_join.pressed.connect(on_join_pressed)
settings.connect_host.pressed.connect(on_host_pressed) settings.connect_host.pressed.connect(on_host_pressed)
settings.connect_disconnect.pressed.connect(on_disconnect_pressed) settings.connect_disconnect.pressed.connect(on_disconnect_pressed)
func load_after(_old : Dictionary, _new : Dictionary) -> void:
main_stats.set_nickname(nickname)
var player_settings_scene: PackedScene = load("res://Mods/copyMultiplayer/Scenes/player_settings.tscn") var player_settings_scene: PackedScene = load("res://Mods/copyMultiplayer/Scenes/player_settings.tscn")
func new_player_settings() -> PlayerSettings: func new_player_settings(): #-> PlayerSettings:
var container = get_settings_window().get_node("Players/VBoxContainer") var container = get_settings_window().get_node("Players/VBoxContainer")
var result := player_settings_scene.instantiate() var result := player_settings_scene.instantiate()
container.add_child(result) container.add_child(result)
return result return result
var player_stats_scene: PackedScene = load("res://Mods/copyMultiplayer/Scenes/player_stats.tscn")
func new_player_stats(): #-> PlayerStats:
var container = get_settings_window().get_node("Stats/VBoxContainer")
var result := player_stats_scene.instantiate()
container.add_child(result)
return result
func on_join_pressed() -> void: func on_join_pressed() -> void:
var address_widget: LineEdit = get_settings_window().get_node("Connect/VBoxContainer/Join/Address") var address_widget: LineEdit = get_settings_window().get_node("Connect/VBoxContainer/Join/Address")
@ -128,6 +146,10 @@ func on_host_pressed() -> void:
update_status() update_status()
print_log("Opened server") print_log("Opened server")
update_enabled_state(true) update_enabled_state(true)
ping_update_timer = Timer.new()
ping_update_timer.timeout.connect(do_update_pings)
add_child(ping_update_timer)
ping_update_timer.start(1)
else: else:
print_log("Unable to open server!") print_log("Unable to open server!")
@ -138,6 +160,8 @@ func on_disconnect_pressed() -> void:
print_log("Closed server") print_log("Closed server")
update_enabled_state(false) update_enabled_state(false)
clear_player_models() clear_player_models()
ping_update_timer.queue_free()
ping_update_timer = null
multiplayer.multiplayer_peer.close() multiplayer.multiplayer_peer.close()
@ -182,13 +206,14 @@ func on_server_disconnected() -> void:
func update_enabled_state(is_online: bool) -> void: func update_enabled_state(is_online: bool) -> void:
var settings := get_settings_window() var settings = get_settings_window()
settings.settings_nickname .editable = !is_online settings.settings_nickname .editable = !is_online
settings.connect_address .editable = !is_online settings.connect_address .editable = !is_online
settings.connect_port .editable = !is_online settings.connect_port .editable = !is_online
settings.connect_join .disabled = is_online settings.connect_join .disabled = is_online
settings.connect_host .disabled = is_online settings.connect_host .disabled = is_online
settings.connect_disconnect.disabled = !is_online settings.connect_disconnect.disabled = !is_online
if not is_online: main_stats.set_ping(0)
func update_status() -> void: func update_status() -> void:
var num_players := 1 + multiplayer.get_peers().size() var num_players := 1 + multiplayer.get_peers().size()
@ -199,17 +224,18 @@ func update_status() -> void:
## Removes all networked player models. ## Removes all networked player models.
func clear_player_models() -> void: func clear_player_models() -> void:
for controller in get_children(): for controller in get_all_sync_controllers():
if controller is SyncController: remove_child(controller)
remove_child(controller) controller.queue_free()
controller.queue_free()
func _process(_delta: float) -> void: func _process(_delta: float) -> void:
SyncController.send_model_animation(self) SyncController.send_model_animation(self)
var settings: copyMultiplayerSettings = get_settings_window()
# TODO: Add a setting to allow moving players even if "Players" tab is not active. # TODO: Add a setting to allow moving players even if "Players" tab is not active.
can_move_players = settings.is_visible_in_tree() && settings.is_tab_selected("Players") # FIXME: Temporary fix until my pull request is accepted or at least the above TODO is implemented.
can_move_players = true
# var settings: copyMultiplayerSettings = get_settings_window()
# can_move_players = settings.is_visible_in_tree() && settings.is_tab_selected("Players")
# Called when module is initialized or load_vrm is called. # Called when module is initialized or load_vrm is called.
func scene_init() -> void: func scene_init() -> void:
@ -243,14 +269,46 @@ func scene_init() -> void:
change_model.rpc(version, bone_lookup, blendshape_lookup, filename) change_model.rpc(version, bone_lookup, blendshape_lookup, filename)
## Gets the SyncController for the player with the specified peer id. func get_sync_controller(peer_id: int): #-> SyncController:
func get_sync_controller(peer_id: int) -> SyncController: return get_node_or_null(str(peer_id)) #as SyncController
return get_node_or_null(str(peer_id)) as SyncController
func get_all_sync_controllers(): #-> Array[SyncController]:
return get_children()
# NOTE: Let's just assume all child nodes are SyncController.
# var result: Array[SyncController] = []
# for controller in get_children():
# if controller is SyncController:
# result.append(controller)
# return result
func get_player_stats(peer_id: int): #-> PlayerStats:
if peer_id == multiplayer.get_unique_id(): return main_stats
var controller = get_sync_controller(peer_id)
if controller: return controller.stats
return null
@rpc("authority", "unreliable_ordered", "call_local")
func update_pings(pairs: Array[int]) -> void:
for i in range(0, pairs.size(), 2):
var peer_id := pairs[i]
var ping := pairs[i + 1]
var stats = get_player_stats(peer_id)
if stats: stats.set_ping(ping)
func do_update_pings() -> void:
var pairs: Array[int] = []
var server: ENetMultiplayerPeer = multiplayer.multiplayer_peer
for peer_id in multiplayer.get_peers():
var peer := server.get_peer(peer_id)
var ping := peer.get_statistic(ENetPacketPeer.PEER_ROUND_TRIP_TIME)
pairs.append_array([ peer_id, int(ping) ])
update_pings.rpc(pairs)
@rpc("any_peer", "reliable") @rpc("any_peer", "reliable")
func change_nickname(new_nickname: String) -> void: func change_nickname(new_nickname: String) -> void:
var peer_id := multiplayer.get_remote_sender_id() var peer_id := multiplayer.get_remote_sender_id()
var controller := get_sync_controller(peer_id) var controller = get_sync_controller(peer_id)
if controller: controller.change_nickname(new_nickname) if controller: controller.change_nickname(new_nickname)
@rpc("any_peer", "reliable") @rpc("any_peer", "reliable")
@ -261,7 +319,7 @@ func change_model(
filename: String, filename: String,
) -> void: ) -> void:
var peer_id := multiplayer.get_remote_sender_id() var peer_id := multiplayer.get_remote_sender_id()
var controller := get_sync_controller(peer_id) var controller = get_sync_controller(peer_id)
if controller: controller.change_model(new_version, new_bone_lookup, new_blendshape_lookup, filename) if controller: controller.change_model(new_version, new_bone_lookup, new_blendshape_lookup, filename)
@rpc("any_peer", "unreliable_ordered") @rpc("any_peer", "unreliable_ordered")
@ -271,5 +329,5 @@ func sync_model_animation(
buffer: PackedByteArray, buffer: PackedByteArray,
) -> void: ) -> void:
var peer_id := multiplayer.get_remote_sender_id() var peer_id := multiplayer.get_remote_sender_id()
var controller := get_sync_controller(peer_id) var controller = get_sync_controller(peer_id)
if controller: controller.sync_model_animation(current_version, uncompressed_length, buffer) if controller: controller.sync_model_animation(current_version, uncompressed_length, buffer)

Loading…
Cancel
Save