diff --git a/Resources/copy_multiplayer_settings.tscn b/Resources/copy_multiplayer_settings.tscn index 7e0582c..9431bd5 100644 --- a/Resources/copy_multiplayer_settings.tscn +++ b/Resources/copy_multiplayer_settings.tscn @@ -50,7 +50,6 @@ text = "Name" [node name="LineEdit" type="LineEdit" parent="Name"] layout_mode = 2 size_flags_horizontal = 3 -placeholder_text = "Anonymous" [node name="Host" type="HBoxContainer" parent="."] layout_mode = 2 diff --git a/copyMultiplayer.gd b/copyMultiplayer.gd index d4f2bde..fb34c66 100644 --- a/copyMultiplayer.gd +++ b/copyMultiplayer.gd @@ -2,7 +2,7 @@ class_name copyMultiplayer extends Mod_Base @export var cache := "" -@export var nickname := "" # FIXME: Not used for anything yet. +@export var nickname := "" @export var address := "" @export var port := 52410 @@ -18,6 +18,10 @@ var player_order: Array[int] = [] var starting_offset := Vector3(-1.25, 0.0, 0.0) var accumulative_offset := Vector3( 0.35, 0.0, 0.0) +# FIXME: There is an edge case where you can load the settings while connected, +# resulting in out-of-sync information with connected players, but so far +# I don't believe this should be a source of issues. + func _ready() -> void: # 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. @@ -40,12 +44,18 @@ func _ready() -> void: main_controller = $"/root/SnekStudio_Main/ModelController" main_controller.child_entered_tree.connect(on_model_controller_child_added) - setup_setting_widget("cache" , "Cache/LineEdit") - setup_setting_widget("nickname", "Name/LineEdit" ) - setup_setting_widget("address" , "Host/Address" ) - setup_setting_widget("port" , "Host/Port" ) + setup_setting_widget("cache" , "Cache/LineEdit", true ) + setup_setting_widget("nickname", "Name/LineEdit" , false) + setup_setting_widget("address" , "Host/Address" , true ) + setup_setting_widget("port" , "Host/Port" , true ) setup_button_connections() + # Filter whitespace characters from nickname before saving it to "nickname" field. + var nickname_widget: LineEdit = get_settings_window().get_node("Name/LineEdit") + 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.focus_exited.connect(func(): nickname_widget.text = nickname) + multiplayer.peer_connected .connect(on_peer_connected) multiplayer.peer_disconnected .connect(on_peer_disconnected) multiplayer.connected_to_server.connect(on_connected_to_server) @@ -58,17 +68,18 @@ func _exit_tree() -> void: func _create_settings_window() -> Control: return load("res://Mods/copyMultiplayer/Resources/copy_multiplayer_settings.tscn").instantiate() -func setup_setting_widget(setting_name: String, path: NodePath) -> void: +func setup_setting_widget(setting_name: String, path: NodePath, setup_events: bool) -> void: var settings = get_settings_window() var widget: Control = settings.get_node(path) _settings_properties.append({ name = setting_name, args = { } }) _settings_widgets_by_setting_name[setting_name] = widget - if widget is LineEdit: widget.text_changed.connect( - func(text): modify_setting(setting_name, text)) - if widget is SpinBox: widget.value_changed.connect( - func(number): modify_setting(setting_name, roundi(number))) + if setup_events: + if widget is LineEdit: widget.text_changed.connect( + func(text): modify_setting(setting_name, text)) + if widget is SpinBox: widget.value_changed.connect( + func(number): modify_setting(setting_name, roundi(number))) func setup_button_connections() -> void: var window = get_settings_window() @@ -112,9 +123,6 @@ func on_disconnect_pressed() -> void: func on_peer_connected(id: int) -> void: - update_status() - print_log(["Player ", id, " connected"]) - var model_controller := ModelController.new() model_controller.name = str(id) var sync_controller := SyncController.new() @@ -125,20 +133,28 @@ func on_peer_connected(id: int) -> void: player_order.append(id) update_model_transforms() + # Send information to the newly connected player about ourselves. + # (Technically this doesn't need to be relayed through the server, but oh well.) + if nickname: change_nickname.rpc_id(id, nickname) var filename = main_controller._last_loaded_vrm.get_file() if filename.is_valid_filename(): change_model.rpc_id(id, filename) -func on_peer_disconnected(id: int) -> void: update_status() - print_log(["Player ", id, " disconnected"]) + print_log("%s connected" % sync_controller.get_display_name()) + +func on_peer_disconnected(id: int) -> void: + var controller = get_sync_controller(id) + if not controller: return - var controller: ModelController = get_node(str(id)) - remove_child(controller) - controller.queue_free() + remove_child(controller.model_controller) + controller.model_controller.queue_free() player_order.remove_at(player_order.find(id)) update_model_transforms() + update_status() + print_log("Player %s disconnected" % id) + func on_connected_to_server() -> void: print_log("Connected to server") @@ -167,7 +183,7 @@ func update_status() -> void: var num_players := 1 + multiplayer.get_peers().size() var side := "Hosting" if multiplayer.is_server() else "Connected" var s := "s" if num_players != 1 else "" - set_status([side, ": ", num_players, " player", s]) + set_status("%s: %d player%s" % [side, num_players, s]) # FIXME: Temporary hardcoded way to assign some offset to other players. @@ -213,6 +229,12 @@ func get_sync_controller(peer_id: int) -> SyncController: if not model_controller: return null return model_controller.get_node_or_null("SyncController") as SyncController +@rpc("any_peer", "reliable") +func change_nickname(new_nickname: String) -> void: + var peer_id := multiplayer.get_remote_sender_id() + var controller := get_sync_controller(peer_id) + if controller: controller.change_nickname(new_nickname) + @rpc("any_peer", "reliable") func change_model(filename: String) -> void: var peer_id := multiplayer.get_remote_sender_id() diff --git a/sync_controller.gd b/sync_controller.gd index 96f6ecf..18a83d0 100644 --- a/sync_controller.gd +++ b/sync_controller.gd @@ -5,7 +5,7 @@ var module: copyMultiplayer var model_controller: ModelController var peer_id: int -var nickname: String # FIXME: Not assigned or used. +var nickname: String var model_name: String var model: Node var skeleton: Skeleton3D @@ -18,6 +18,16 @@ func _ready() -> void: model_controller = get_parent() peer_id = model_controller.name.to_int() +func get_display_name() -> String: + if nickname: return "Player '%s' (%d)" % [ nickname, peer_id ] + else: return "Player (%d)" % peer_id + +func change_nickname(new_nickname: String) -> void: + new_nickname = new_nickname.strip_edges() + if new_nickname == "": return # Ignore empty nicknames. + module.print_log("%s is now known as '%s'" % [ get_display_name(), new_nickname ]) + nickname = new_nickname + ## Attempts to change the model of this player. func change_model(filename: String) -> void: if not filename.is_valid_filename(): @@ -25,12 +35,12 @@ func change_model(filename: String) -> void: return var full_path := module.cache.path_join(filename) if not FileAccess.file_exists(full_path): - module.print_log("Player %d wanted to switch to '%s', but it doesn't exist, skipping" % [ peer_id, filename ]) + module.print_log("%s wanted to switch to '%s', but it doesn't exist, skipping" % [ get_display_name(), filename ]) return if not model_controller.load_vrm(full_path): module.print_log("ERROR: Model '%s' could not be loaded!" % filename) return - module.print_log("Player %d switched to '%s'" % [ peer_id, filename ]) + module.print_log("%s switched to '%s'" % [ get_display_name(), filename ]) model_name = filename model = model_controller.get_node_or_null("Model") skeleton = model_controller._get_model_skeleton()