From ad522eff9d7194db87caf7ee442f8b6a230ad418 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 03:45:49 -0500 Subject: [PATCH 01/27] Created new manager files. --- src/project.godot | 1 + src/scenes/managers/app/network.gd | 114 ++++++++++++++++++ src/scenes/managers/app/network.gd.uid | 1 + src/scenes/managers/app/rpc.gd | 25 ++++ src/scenes/managers/app/rpc.gd.uid | 1 + src/scenes/managers/app/rpc_manager.tscn | 2 +- .../{rpc_manager.gd => rpc_manager_old.gd} | 0 ..._manager.gd.uid => rpc_manager_old.gd.uid} | 0 src/scenes/managers/app/scene.gd | 60 +++++++++ src/scenes/managers/app/scene.gd.uid | 1 + src/scenes/managers/app/scene_manager.tscn | 4 +- ...{scene_manager.gd => scene_manager_old.gd} | 0 ...anager.gd.uid => scene_manager_old.gd.uid} | 0 src/scripts/enum.gd | 16 +++ src/scripts/enum.gd.uid | 1 + 15 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 src/scenes/managers/app/network.gd create mode 100644 src/scenes/managers/app/network.gd.uid create mode 100644 src/scenes/managers/app/rpc.gd create mode 100644 src/scenes/managers/app/rpc.gd.uid rename src/scenes/managers/app/{rpc_manager.gd => rpc_manager_old.gd} (100%) rename src/scenes/managers/app/{rpc_manager.gd.uid => rpc_manager_old.gd.uid} (100%) create mode 100644 src/scenes/managers/app/scene.gd create mode 100644 src/scenes/managers/app/scene.gd.uid rename src/scenes/managers/app/{scene_manager.gd => scene_manager_old.gd} (100%) rename src/scenes/managers/app/{scene_manager.gd.uid => scene_manager_old.gd.uid} (100%) create mode 100644 src/scripts/enum.gd create mode 100644 src/scripts/enum.gd.uid diff --git a/src/project.godot b/src/project.godot index d5870a9..c2c9808 100644 --- a/src/project.godot +++ b/src/project.godot @@ -34,6 +34,7 @@ Events="*uid://c656spc3ppdlw" OAuth="*uid://jd7qlsley1no" SessionQuery="*uid://bl3e1utjvw3ul" UrlParser="*uid://budprjmmpally" +Enum="*uid://b5amtwc5yahe3" [display] diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd new file mode 100644 index 0000000..297dea4 --- /dev/null +++ b/src/scenes/managers/app/network.gd @@ -0,0 +1,114 @@ +# --- License +# File: /client/src/scenes/managers/app/network.gd +# Project: OpenMinerva +# Created Date: 13 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +var n_c = preload("res://scripts/network/network_compression.gd").new() +var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") + +@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") +@onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") + +var _database = { + "sessions": {}, + "sessions_api": {} +} + +const _instance_database_template = { + "instance_name": "", + "instance_description": "", + "port": 0, + "max_connected_users": 1, + "instance_privacy": null, + + "connected_players": [], + "start_time": 0, + + "networking": { + "use_steam": false, + "use_lan": false + } +} + +func start_server(root_scene: Enum.BaseLevel, port: int): + # Get an available port. If port was defined, force that port or fail. + # Create a new peer. + # Create server master scene. + # Create server root scene. + # Start session managers. (Handles players, spawning, permissions (as host)) + return + +func stop_server(id: String): + # Kick all players (Server closing). + # Turn off all join requests. + # Destroy multiplayer api. + # Stop all managers. + # Destroy server master scene. + return + +func update_server(id: String, update_dict: Dictionary): + # Get server from database. + # Validate server updated data. + # Update the database entry. + # Emit server updated event to the server. + return + +func join_server(ip: String, port: int): + # Create multiplayer peer. + # Establish connection. + # Host handles everything after this with the managers? + return + +func leave_server(): + # Get server from database. + # Send leave packet. + # Destroy multiplayer API. + # Destroy server master scene. + return + +func kick_player(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + return + +func ban_player(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + return + +func on_kicked(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + return + +func on_banned(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + return + +func _find_available_port(target_port: int = 20205): + GlobalLogger.logs("Trying to find an available port starting at '%s'." % target_port) + var found_port = 0 + + while found_port == 0: + var port_available = !_is_port_in_use(target_port) + if port_available: + found_port = port_available + + target_port = target_port + 1 + + GlobalLogger.logs("Port found: '%s'" % target_port) + + return found_port + +func _is_port_in_use(port: int) -> bool: + var tcp_server = TCPServer.new() + var err = tcp_server.listen(port, "*") + + if err == OK: + tcp_server.stop() + return true + + return false \ No newline at end of file diff --git a/src/scenes/managers/app/network.gd.uid b/src/scenes/managers/app/network.gd.uid new file mode 100644 index 0000000..701a03c --- /dev/null +++ b/src/scenes/managers/app/network.gd.uid @@ -0,0 +1 @@ +uid://r51flk0bjypx diff --git a/src/scenes/managers/app/rpc.gd b/src/scenes/managers/app/rpc.gd new file mode 100644 index 0000000..2486d11 --- /dev/null +++ b/src/scenes/managers/app/rpc.gd @@ -0,0 +1,25 @@ +# --- License +# File: /client/src/scenes/managers/app/rpc_manager.gd +# Project: OpenMinerva +# Created Date: 13 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +var c := preload("res://scripts/rpc/client.gd").new() +var s := preload("res://scripts/rpc/server.gd").new() +var com := preload("res://scripts/rpc/common.gd").new() + +func _ready(): + # RPCs can not be called from outside of the scene tree, we are required to add them. + add_child(c) + add_child(s) + add_child(com) + + multiplayer.peer_connected.connect(s.on_peer_connected) + multiplayer.peer_disconnected.connect(s.on_peer_disconnected) + multiplayer.connected_to_server.connect(c.connected_to_server) + multiplayer.connection_failed.connect(c.connection_failed) \ No newline at end of file diff --git a/src/scenes/managers/app/rpc.gd.uid b/src/scenes/managers/app/rpc.gd.uid new file mode 100644 index 0000000..dcf65f8 --- /dev/null +++ b/src/scenes/managers/app/rpc.gd.uid @@ -0,0 +1 @@ +uid://bsyo854lkhbf2 diff --git a/src/scenes/managers/app/rpc_manager.tscn b/src/scenes/managers/app/rpc_manager.tscn index bcdecb5..30b4fb2 100644 --- a/src/scenes/managers/app/rpc_manager.tscn +++ b/src/scenes/managers/app/rpc_manager.tscn @@ -1,6 +1,6 @@ [gd_scene format=3 uid="uid://by0vghgshvbhd"] -[ext_resource type="Script" uid="uid://x23lqbiivx1q" path="res://scenes/managers/app/rpc_manager.gd" id="1_6timl"] +[ext_resource type="Script" uid="uid://x23lqbiivx1q" path="res://scenes/managers/app/rpc_manager_old.gd" id="1_6timl"] [node name="RpcManager" type="Node" unique_id=1836544617] script = ExtResource("1_6timl") diff --git a/src/scenes/managers/app/rpc_manager.gd b/src/scenes/managers/app/rpc_manager_old.gd similarity index 100% rename from src/scenes/managers/app/rpc_manager.gd rename to src/scenes/managers/app/rpc_manager_old.gd diff --git a/src/scenes/managers/app/rpc_manager.gd.uid b/src/scenes/managers/app/rpc_manager_old.gd.uid similarity index 100% rename from src/scenes/managers/app/rpc_manager.gd.uid rename to src/scenes/managers/app/rpc_manager_old.gd.uid diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd new file mode 100644 index 0000000..bf7bf8f --- /dev/null +++ b/src/scenes/managers/app/scene.gd @@ -0,0 +1,60 @@ +# --- License +# File: /client/src/scenes/managers/app/scene_manager.gd +# Project: OpenMinerva +# Created Date: 13 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +# Game managers +@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") +@onready var scene_container = get_tree().current_scene.get_node("Scenes") + +func _ready(): + return + +func create_master_scene(): + # Load scene template from disk. + # Give scene random identifier. + # Insert it into the scene root. + # return scene_id. + return + +func get_master_scene(id: String) -> Node3D: + # Find scene by id from scene list. + # return Node3D or null. + return + +func destroy_master_scene(id: String): + # get_master_scene + # Check if currently being used by a server. + # Check if being used as client to server. + # Queue free. + return + +func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> bool: + # get_master_scene. + # Find scene by scene_type. (Make function) + # Validate scene integrity. + # Find node "root". + # Destroy node. + # Replace with new scene. + return false + +func get_master_root(id: String) -> Node3D: + # get_master_scene + # Find node "root". + return + +func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> bool: + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + # get_master_scene + # Find scene from inventory. + # Validate scene integrity. + # Find node "root". + # Destroy node. + # Replace with new scene. + return false \ No newline at end of file diff --git a/src/scenes/managers/app/scene.gd.uid b/src/scenes/managers/app/scene.gd.uid new file mode 100644 index 0000000..02824f9 --- /dev/null +++ b/src/scenes/managers/app/scene.gd.uid @@ -0,0 +1 @@ +uid://3ylijxenr1bg diff --git a/src/scenes/managers/app/scene_manager.tscn b/src/scenes/managers/app/scene_manager.tscn index 435d9de..0ea8240 100644 --- a/src/scenes/managers/app/scene_manager.tscn +++ b/src/scenes/managers/app/scene_manager.tscn @@ -1,6 +1,6 @@ -[gd_scene load_steps=2 format=3 uid="uid://cmknpdx5ba15o"] +[gd_scene format=3 uid="uid://cmknpdx5ba15o"] -[ext_resource type="Script" uid="uid://bwe2gsnip66cr" path="res://scenes/managers/app/scene_manager.gd" id="1_i8qdd"] +[ext_resource type="Script" uid="uid://bwe2gsnip66cr" path="res://scenes/managers/app/scene_manager_old.gd" id="1_i8qdd"] [node name="SceneManager" type="Node"] script = ExtResource("1_i8qdd") diff --git a/src/scenes/managers/app/scene_manager.gd b/src/scenes/managers/app/scene_manager_old.gd similarity index 100% rename from src/scenes/managers/app/scene_manager.gd rename to src/scenes/managers/app/scene_manager_old.gd diff --git a/src/scenes/managers/app/scene_manager.gd.uid b/src/scenes/managers/app/scene_manager_old.gd.uid similarity index 100% rename from src/scenes/managers/app/scene_manager.gd.uid rename to src/scenes/managers/app/scene_manager_old.gd.uid diff --git a/src/scripts/enum.gd b/src/scripts/enum.gd new file mode 100644 index 0000000..4cb3cd8 --- /dev/null +++ b/src/scripts/enum.gd @@ -0,0 +1,16 @@ +extends Control + +enum BaseLevel { + DEBUG = 0, + EMPTY = 1, + GRID = 2, +} + +enum PrivacyLevel { + INVITE = 0, + PUBLIC = 1, + CONTACTS_PLUS = 2, + CONTACTS = 3, + FRIENDS_PLUS = 4, + FRIENDS = 5 +} \ No newline at end of file diff --git a/src/scripts/enum.gd.uid b/src/scripts/enum.gd.uid new file mode 100644 index 0000000..6e4d004 --- /dev/null +++ b/src/scripts/enum.gd.uid @@ -0,0 +1 @@ +uid://b5amtwc5yahe3 From f4ff36477a16cf16b173567fcaccf282adb2e182 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 04:16:34 -0500 Subject: [PATCH 02/27] Created base server scene. Created create_master_scene function. --- src/scenes/levels/base.tscn | 9 +++ src/scenes/managers/app/network.tscn | 6 ++ src/scenes/managers/app/network_manager.tscn | 6 -- .../app/{ => old}/network_manaager.gd | 0 .../app/{ => old}/network_manaager.gd.uid | 0 .../app/{ => old}/network_manager.gd.uid | 0 .../app/{ => old}/network_manager.old.gd | 0 .../app/{ => old}/network_manager.old.gd.uid | 0 .../managers/app/{ => old}/rpc_manager_old.gd | 0 .../app/{ => old}/rpc_manager_old.gd.uid | 0 .../app/{ => old}/scene_manager_old.gd | 0 .../app/{ => old}/scene_manager_old.gd.uid | 0 .../app/{rpc_manager.tscn => rpc.tscn} | 0 src/scenes/managers/app/scene.gd | 73 ++++++++++--------- .../app/{scene_manager.tscn => scene.tscn} | 0 src/scenes/master.tscn | 10 +-- 16 files changed, 59 insertions(+), 45 deletions(-) create mode 100644 src/scenes/levels/base.tscn create mode 100644 src/scenes/managers/app/network.tscn delete mode 100644 src/scenes/managers/app/network_manager.tscn rename src/scenes/managers/app/{ => old}/network_manaager.gd (100%) rename src/scenes/managers/app/{ => old}/network_manaager.gd.uid (100%) rename src/scenes/managers/app/{ => old}/network_manager.gd.uid (100%) rename src/scenes/managers/app/{ => old}/network_manager.old.gd (100%) rename src/scenes/managers/app/{ => old}/network_manager.old.gd.uid (100%) rename src/scenes/managers/app/{ => old}/rpc_manager_old.gd (100%) rename src/scenes/managers/app/{ => old}/rpc_manager_old.gd.uid (100%) rename src/scenes/managers/app/{ => old}/scene_manager_old.gd (100%) rename src/scenes/managers/app/{ => old}/scene_manager_old.gd.uid (100%) rename src/scenes/managers/app/{rpc_manager.tscn => rpc.tscn} (100%) rename src/scenes/managers/app/{scene_manager.tscn => scene.tscn} (100%) diff --git a/src/scenes/levels/base.tscn b/src/scenes/levels/base.tscn new file mode 100644 index 0000000..27f49a1 --- /dev/null +++ b/src/scenes/levels/base.tscn @@ -0,0 +1,9 @@ +[gd_scene format=3 uid="uid://bysrhijnau31g"] + +[node name="Base" type="Node3D" unique_id=2084163635] + +[node name="PlayerManager" type="Node" parent="." unique_id=412138074] + +[node name="ServerSignalManager" type="Node" parent="." unique_id=165926833] + +[node name="root" type="Node3D" parent="." unique_id=345927695] diff --git a/src/scenes/managers/app/network.tscn b/src/scenes/managers/app/network.tscn new file mode 100644 index 0000000..f7ae244 --- /dev/null +++ b/src/scenes/managers/app/network.tscn @@ -0,0 +1,6 @@ +[gd_scene format=3 uid="uid://5v8rbnp716b0"] + +[ext_resource type="Script" uid="uid://bh271sucj14cq" path="res://scenes/managers/app/network_manaager.gd" id="1_daels"] + +[node name="NetworkManager" type="Node"] +script = ExtResource("1_daels") diff --git a/src/scenes/managers/app/network_manager.tscn b/src/scenes/managers/app/network_manager.tscn deleted file mode 100644 index a76ebca..0000000 --- a/src/scenes/managers/app/network_manager.tscn +++ /dev/null @@ -1,6 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://5v8rbnp716b0"] - -[ext_resource type="Script" uid="uid://cc0ei8wuetrvh" path="res://scenes/managers/app/network_manager.gd" id="1_daels"] - -[node name="NetworkManager" type="Node"] -script = ExtResource("1_daels") diff --git a/src/scenes/managers/app/network_manaager.gd b/src/scenes/managers/app/old/network_manaager.gd similarity index 100% rename from src/scenes/managers/app/network_manaager.gd rename to src/scenes/managers/app/old/network_manaager.gd diff --git a/src/scenes/managers/app/network_manaager.gd.uid b/src/scenes/managers/app/old/network_manaager.gd.uid similarity index 100% rename from src/scenes/managers/app/network_manaager.gd.uid rename to src/scenes/managers/app/old/network_manaager.gd.uid diff --git a/src/scenes/managers/app/network_manager.gd.uid b/src/scenes/managers/app/old/network_manager.gd.uid similarity index 100% rename from src/scenes/managers/app/network_manager.gd.uid rename to src/scenes/managers/app/old/network_manager.gd.uid diff --git a/src/scenes/managers/app/network_manager.old.gd b/src/scenes/managers/app/old/network_manager.old.gd similarity index 100% rename from src/scenes/managers/app/network_manager.old.gd rename to src/scenes/managers/app/old/network_manager.old.gd diff --git a/src/scenes/managers/app/network_manager.old.gd.uid b/src/scenes/managers/app/old/network_manager.old.gd.uid similarity index 100% rename from src/scenes/managers/app/network_manager.old.gd.uid rename to src/scenes/managers/app/old/network_manager.old.gd.uid diff --git a/src/scenes/managers/app/rpc_manager_old.gd b/src/scenes/managers/app/old/rpc_manager_old.gd similarity index 100% rename from src/scenes/managers/app/rpc_manager_old.gd rename to src/scenes/managers/app/old/rpc_manager_old.gd diff --git a/src/scenes/managers/app/rpc_manager_old.gd.uid b/src/scenes/managers/app/old/rpc_manager_old.gd.uid similarity index 100% rename from src/scenes/managers/app/rpc_manager_old.gd.uid rename to src/scenes/managers/app/old/rpc_manager_old.gd.uid diff --git a/src/scenes/managers/app/scene_manager_old.gd b/src/scenes/managers/app/old/scene_manager_old.gd similarity index 100% rename from src/scenes/managers/app/scene_manager_old.gd rename to src/scenes/managers/app/old/scene_manager_old.gd diff --git a/src/scenes/managers/app/scene_manager_old.gd.uid b/src/scenes/managers/app/old/scene_manager_old.gd.uid similarity index 100% rename from src/scenes/managers/app/scene_manager_old.gd.uid rename to src/scenes/managers/app/old/scene_manager_old.gd.uid diff --git a/src/scenes/managers/app/rpc_manager.tscn b/src/scenes/managers/app/rpc.tscn similarity index 100% rename from src/scenes/managers/app/rpc_manager.tscn rename to src/scenes/managers/app/rpc.tscn diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index bf7bf8f..5587760 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -10,51 +10,56 @@ extends Node # Game managers -@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") -@onready var scene_container = get_tree().current_scene.get_node("Scenes") +@onready var network_m: Node = get_tree().current_scene.get_node("NetworkManager") +@onready var scene_container: Node3D = get_tree().current_scene.get_node("Scenes") func _ready(): - return + network_m.start_server() + return func create_master_scene(): - # Load scene template from disk. - # Give scene random identifier. - # Insert it into the scene root. - # return scene_id. - return + var _scene_id = Random.random_string() + var _base_scene = preload("res://scenes/levels/base.tscn") + + _base_scene = _base_scene.instantiate() + _base_scene.name = _scene_id + + scene_container.add_child(_base_scene) + + return _scene_id func get_master_scene(id: String) -> Node3D: - # Find scene by id from scene list. - # return Node3D or null. - return + # Find scene by id from scene list. + # return Node3D or null. + return func destroy_master_scene(id: String): - # get_master_scene - # Check if currently being used by a server. - # Check if being used as client to server. - # Queue free. - return + # get_master_scene + # Check if currently being used by a server. + # Check if being used as client to server. + # Queue free. + return func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> bool: - # get_master_scene. - # Find scene by scene_type. (Make function) - # Validate scene integrity. - # Find node "root". - # Destroy node. - # Replace with new scene. - return false + # get_master_scene. + # Find scene by scene_type. (Make function) + # Validate scene integrity. + # Find node "root". + # Destroy node. + # Replace with new scene. + return false func get_master_root(id: String) -> Node3D: - # get_master_scene - # Find node "root". - return + # get_master_scene + # Find node "root". + return func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> bool: - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) - # get_master_scene - # Find scene from inventory. - # Validate scene integrity. - # Find node "root". - # Destroy node. - # Replace with new scene. - return false \ No newline at end of file + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + # get_master_scene + # Find scene from inventory. + # Validate scene integrity. + # Find node "root". + # Destroy node. + # Replace with new scene. + return false diff --git a/src/scenes/managers/app/scene_manager.tscn b/src/scenes/managers/app/scene.tscn similarity index 100% rename from src/scenes/managers/app/scene_manager.tscn rename to src/scenes/managers/app/scene.tscn diff --git a/src/scenes/master.tscn b/src/scenes/master.tscn index bb1c9ea..336915c 100644 --- a/src/scenes/master.tscn +++ b/src/scenes/master.tscn @@ -1,9 +1,9 @@ [gd_scene format=3 uid="uid://cxk6c0uipjjpo"] -[ext_resource type="PackedScene" uid="uid://by0vghgshvbhd" path="res://scenes/managers/app/rpc_manager.tscn" id="1_h2qy3"] -[ext_resource type="PackedScene" uid="uid://5v8rbnp716b0" path="res://scenes/managers/app/network_manager.tscn" id="1_jooxx"] -[ext_resource type="PackedScene" uid="uid://cmknpdx5ba15o" path="res://scenes/managers/app/scene_manager.tscn" id="2_h2qy3"] -[ext_resource type="Script" uid="uid://bh271sucj14cq" path="res://scenes/managers/app/network_manaager.gd" id="3_q3f5g"] +[ext_resource type="PackedScene" uid="uid://by0vghgshvbhd" path="res://scenes/managers/app/rpc.tscn" id="1_h2qy3"] +[ext_resource type="PackedScene" uid="uid://5v8rbnp716b0" path="res://scenes/managers/app/network.tscn" id="1_jooxx"] +[ext_resource type="PackedScene" uid="uid://cmknpdx5ba15o" path="res://scenes/managers/app/scene.tscn" id="2_h2qy3"] +[ext_resource type="Script" uid="uid://3ylijxenr1bg" path="res://scenes/managers/app/scene.gd" id="4_q3f5g"] [ext_resource type="PackedScene" uid="uid://ckl5gw0xbduiv" path="res://userinterface/dash/hud.tscn" id="5_q3f5g"] [node name="Master" type="Node3D" unique_id=420526444] @@ -11,9 +11,9 @@ [node name="RpcManager" parent="." unique_id=1836544617 instance=ExtResource("1_h2qy3")] [node name="NetworkManager" parent="." unique_id=1960146969 instance=ExtResource("1_jooxx")] -script = ExtResource("3_q3f5g") [node name="SceneManager" parent="." unique_id=5477810 instance=ExtResource("2_h2qy3")] +script = ExtResource("4_q3f5g") [node name="Hud" parent="." unique_id=1053137144 instance=ExtResource("5_q3f5g")] visible = false From a42ff7d93d9d7e90d7d1b8a3617062c3397df7b5 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 05:19:22 -0500 Subject: [PATCH 03/27] Start multiplayer server. --- src/scenes/managers/app/network.gd | 64 ++++++++++++++++++++++++------ src/scenes/managers/app/scene.gd | 12 +++++- src/scenes/master.tscn | 4 ++ 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 297dea4..6fa948f 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -9,10 +9,12 @@ extends Node +const MAX_CLIENTS = 1000 + var n_c = preload("res://scripts/network/network_compression.gd").new() var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") -@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") +@onready var scene_m = get_tree().current_scene.get_node("SceneManager") @onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") var _database = { @@ -21,11 +23,11 @@ var _database = { } const _instance_database_template = { - "instance_name": "", - "instance_description": "", + "name": "", + "description": "", "port": 0, "max_connected_users": 1, - "instance_privacy": null, + "privacy": null, "connected_players": [], "start_time": 0, @@ -36,13 +38,49 @@ const _instance_database_template = { } } -func start_server(root_scene: Enum.BaseLevel, port: int): +func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRID) -> Dictionary: + var response_dict = {"ok": false, "error": null, "data": null} + # Get an available port. If port was defined, force that port or fail. - # Create a new peer. + if port: + var port_available = !_is_port_in_use(port) + if !port_available: + response_dict.error = "Port is not available." + return response_dict + else: + port = _find_available_port() + # Create server master scene. + var _scene: String = scene_m.create_master_scene() + var _instance = _instance_database_template.duplicate() + _instance.name = _scene + _instance.start_time = int(Time.get_unix_time_from_system()) + _instance.privacy = Enum.PrivacyLevel.INVITE + + # Create a new peer. + var _mp_api = SceneMultiplayer.new() + var _session_peer = ENetMultiplayerPeer.new() + var _create_server_response = _session_peer.create_server(port, MAX_CLIENTS) + _mp_api.multiplayer_peer = _session_peer + + _database.sessions_api.set(_scene, _mp_api) + _database.sessions.set(_scene, _instance) + + if _create_server_response != OK: + GlobalLogger.logs("Failed to start server. Error: '%s'" % _create_server_response, 1) + response_dict.error = str(_create_server_response) + # TODO: Destroy server scene + return response_dict + # Create server root scene. + if root_scene: + scene_m.set_master_root_from_program(_scene, root_scene) + else: + scene_m.set_master_root_from_program(_scene, Enum.BaseLevel.GRID) + # Start session managers. (Handles players, spawning, permissions (as host)) - return + + return response_dict func stop_server(id: String): # Kick all players (Server closing). @@ -88,20 +126,22 @@ func on_banned(): GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) return -func _find_available_port(target_port: int = 20205): +func _find_available_port(target_port: int = 20205) -> int: GlobalLogger.logs("Trying to find an available port starting at '%s'." % target_port) - var found_port = 0 + var _found_port = null + var _is_found = false - while found_port == 0: + while _is_found == false: var port_available = !_is_port_in_use(target_port) if port_available: - found_port = port_available + _found_port = target_port + _is_found = true target_port = target_port + 1 GlobalLogger.logs("Port found: '%s'" % target_port) - return found_port + return _found_port func _is_port_in_use(port: int) -> bool: var tcp_server = TCPServer.new() diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index 5587760..175f7b0 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -29,11 +29,13 @@ func create_master_scene(): return _scene_id func get_master_scene(id: String) -> Node3D: + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # Find scene by id from scene list. # return Node3D or null. return func destroy_master_scene(id: String): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # get_master_scene # Check if currently being used by a server. # Check if being used as client to server. @@ -41,6 +43,7 @@ func destroy_master_scene(id: String): return func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> bool: + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # get_master_scene. # Find scene by scene_type. (Make function) # Validate scene integrity. @@ -55,7 +58,7 @@ func get_master_root(id: String) -> Node3D: return func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> bool: - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # get_master_scene # Find scene from inventory. # Validate scene integrity. @@ -63,3 +66,10 @@ func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> b # Destroy node. # Replace with new scene. return false + +func start_master_scene(id: String): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) + # get_master_scene + # Start Player manager. + # Start ServerSignalManager. + return diff --git a/src/scenes/master.tscn b/src/scenes/master.tscn index 336915c..71d39c8 100644 --- a/src/scenes/master.tscn +++ b/src/scenes/master.tscn @@ -3,14 +3,18 @@ [ext_resource type="PackedScene" uid="uid://by0vghgshvbhd" path="res://scenes/managers/app/rpc.tscn" id="1_h2qy3"] [ext_resource type="PackedScene" uid="uid://5v8rbnp716b0" path="res://scenes/managers/app/network.tscn" id="1_jooxx"] [ext_resource type="PackedScene" uid="uid://cmknpdx5ba15o" path="res://scenes/managers/app/scene.tscn" id="2_h2qy3"] +[ext_resource type="Script" uid="uid://bsyo854lkhbf2" path="res://scenes/managers/app/rpc.gd" id="2_kjytq"] +[ext_resource type="Script" uid="uid://r51flk0bjypx" path="res://scenes/managers/app/network.gd" id="4_56xkn"] [ext_resource type="Script" uid="uid://3ylijxenr1bg" path="res://scenes/managers/app/scene.gd" id="4_q3f5g"] [ext_resource type="PackedScene" uid="uid://ckl5gw0xbduiv" path="res://userinterface/dash/hud.tscn" id="5_q3f5g"] [node name="Master" type="Node3D" unique_id=420526444] [node name="RpcManager" parent="." unique_id=1836544617 instance=ExtResource("1_h2qy3")] +script = ExtResource("2_kjytq") [node name="NetworkManager" parent="." unique_id=1960146969 instance=ExtResource("1_jooxx")] +script = ExtResource("4_56xkn") [node name="SceneManager" parent="." unique_id=5477810 instance=ExtResource("2_h2qy3")] script = ExtResource("4_q3f5g") From 257f08e061951f880c2273fd2a357346a688ced1 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 05:21:56 -0500 Subject: [PATCH 04/27] Fixed dashboard error. Could not find active sessions because there was not a function. --- src/scenes/managers/app/network.gd | 12 +++++++++++- src/userinterface/dash/home.gd | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 6fa948f..5cb3021 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -23,6 +23,7 @@ var _database = { } const _instance_database_template = { + "id": "", "name": "", "description": "", "port": 0, @@ -53,6 +54,7 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI # Create server master scene. var _scene: String = scene_m.create_master_scene() var _instance = _instance_database_template.duplicate() + _instance.id = _scene _instance.name = _scene _instance.start_time = int(Time.get_unix_time_from_system()) _instance.privacy = Enum.PrivacyLevel.INVITE @@ -78,7 +80,7 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI else: scene_m.set_master_root_from_program(_scene, Enum.BaseLevel.GRID) - # Start session managers. (Handles players, spawning, permissions (as host)) + # TODO: Start session managers. (Handles players, spawning, permissions (as host)) return response_dict @@ -126,6 +128,14 @@ func on_banned(): GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) return +func get_connected_sessions(): + var result = [] + + for session_id in _database.sessions.keys(): + result.append(_database.sessions[session_id].merged({"id": session_id})) + + return result + func _find_available_port(target_port: int = 20205) -> int: GlobalLogger.logs("Trying to find an available port starting at '%s'." % target_port) var _found_port = null diff --git a/src/userinterface/dash/home.gd b/src/userinterface/dash/home.gd index d042b8b..118c2b6 100644 --- a/src/userinterface/dash/home.gd +++ b/src/userinterface/dash/home.gd @@ -9,7 +9,7 @@ extends Control -@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") @onready var account_card_container = get_node("HBoxContainer/VBoxContainer/AccountDisplay") @onready var storage_card_container = get_node("HBoxContainer/VBoxContainer/StorageDisplay") @@ -48,7 +48,7 @@ func _display_active_sessions() -> void: if node is Button: node.queue_free() - var _sessions = network_manager.get_active_sessions() + var _sessions = network_m.get_connected_sessions() for session in _sessions: var _entry = active_session_template.duplicate() From 06d4d91b0c9fe388a4b7236ed1072508c8578cc6 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 05:39:43 -0500 Subject: [PATCH 05/27] Load scene root. --- src/scenes/managers/app/network.gd | 2 +- src/scenes/managers/app/scene.gd | 53 +++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 5cb3021..5bcb698 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -161,4 +161,4 @@ func _is_port_in_use(port: int) -> bool: tcp_server.stop() return true - return false \ No newline at end of file + return false diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index 175f7b0..6747bc8 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -29,10 +29,8 @@ func create_master_scene(): return _scene_id func get_master_scene(id: String) -> Node3D: - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) - # Find scene by id from scene list. - # return Node3D or null. - return + var _scene = scene_container.get_node(id) + return _scene func destroy_master_scene(id: String): GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) @@ -42,21 +40,31 @@ func destroy_master_scene(id: String): # Queue free. return -func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> bool: - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) - # get_master_scene. - # Find scene by scene_type. (Make function) - # Validate scene integrity. - # Find node "root". - # Destroy node. +func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> void: + # TODO: Disable spawning players while changing master root. + var _scene = get_master_scene(id) + + var _root_scene: PackedScene = _get_scene_by_type(scene_type) + + # TODO: Validate scene integrity. + + var _root_node = get_master_root(id) + _scene.remove_child(_root_node) + _root_node.queue_free() + # Replace with new scene. - return false + var _root_scene_node = _root_scene.instantiate() + _root_scene_node.name = "root" + _scene.add_child(_root_scene_node) -func get_master_root(id: String) -> Node3D: - # get_master_scene - # Find node "root". + # TODO: Emit signal of root changed. return +func get_master_root(id: String) -> Node3D: + var _scene: Node3D = get_master_scene(id) + var _root = _scene.get_node_or_null("root") + return _root + func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> bool: GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # get_master_scene @@ -73,3 +81,18 @@ func start_master_scene(id: String): # Start Player manager. # Start ServerSignalManager. return + +func _get_scene_by_type(scene_type: Enum.BaseLevel) -> PackedScene: + var _scene_dir: String = "" + + match scene_type: + Enum.BaseLevel.DEBUG: + _scene_dir = "res://scenes/levels/debug.tscn" + Enum.BaseLevel.EMPTY: + _scene_dir = "res://scenes/levels/empty.tscn" + Enum.BaseLevel.GRID: + _scene_dir = "res://scenes/levels/grid.tscn" + _: + _scene_dir = "res://scenes/levels/debug.tscn" + + return load(_scene_dir) \ No newline at end of file From 6fab2c07df8ff9a8762699269c9e64157635ec22 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 05:55:28 -0500 Subject: [PATCH 06/27] Force spawn the host. --- src/scenes/levels/base.tscn | 5 +++++ src/scenes/managers/app/network.gd | 3 +++ src/scenes/managers/app/scene.gd | 2 +- src/scenes/managers/scene/player.gd | 23 ++++++++++++++++++++++ src/scenes/managers/scene/player.gd.uid | 1 + src/scenes/managers/scene/signalbus.gd | 10 ++++++++++ src/scenes/managers/scene/signalbus.gd.uid | 1 + 7 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/scenes/managers/scene/player.gd create mode 100644 src/scenes/managers/scene/player.gd.uid create mode 100644 src/scenes/managers/scene/signalbus.gd create mode 100644 src/scenes/managers/scene/signalbus.gd.uid diff --git a/src/scenes/levels/base.tscn b/src/scenes/levels/base.tscn index 27f49a1..56159e7 100644 --- a/src/scenes/levels/base.tscn +++ b/src/scenes/levels/base.tscn @@ -1,9 +1,14 @@ [gd_scene format=3 uid="uid://bysrhijnau31g"] +[ext_resource type="Script" uid="uid://c13l6ywhxq6n5" path="res://scenes/managers/scene/player.gd" id="1_c3hqj"] +[ext_resource type="Script" uid="uid://cxbdjoelope42" path="res://scenes/managers/scene/signalbus.gd" id="2_fundk"] + [node name="Base" type="Node3D" unique_id=2084163635] [node name="PlayerManager" type="Node" parent="." unique_id=412138074] +script = ExtResource("1_c3hqj") [node name="ServerSignalManager" type="Node" parent="." unique_id=165926833] +script = ExtResource("2_fundk") [node name="root" type="Node3D" parent="." unique_id=345927695] diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 5bcb698..d254fc3 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -81,6 +81,9 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI scene_m.set_master_root_from_program(_scene, Enum.BaseLevel.GRID) # TODO: Start session managers. (Handles players, spawning, permissions (as host)) + + # DEV: Force spawn the host. + scene_m.get_master_scene(_scene).get_node("PlayerManager").spawn_player(1) return response_dict diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index 6747bc8..bd3135c 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -95,4 +95,4 @@ func _get_scene_by_type(scene_type: Enum.BaseLevel) -> PackedScene: _: _scene_dir = "res://scenes/levels/debug.tscn" - return load(_scene_dir) \ No newline at end of file + return load(_scene_dir) diff --git a/src/scenes/managers/scene/player.gd b/src/scenes/managers/scene/player.gd new file mode 100644 index 0000000..81dbdaa --- /dev/null +++ b/src/scenes/managers/scene/player.gd @@ -0,0 +1,23 @@ +# --- License +# File: /client/src/scenes/managers/scene/player.gd +# Project: OpenMinerva +# Created Date: 13 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +var active: bool = false + +func spawn_player(peer_id: int) -> void: + var _player_scene: PackedScene = load("res://scenes/players/player.tscn") + var _new_player: Node3D = _player_scene.instantiate() + _new_player.name = str(peer_id) + _new_player.position = Vector3(0, 0, 0) + get_parent().get_node("root").call_deferred("add_child", _new_player) + return + +func kill_player() -> void: + return \ No newline at end of file diff --git a/src/scenes/managers/scene/player.gd.uid b/src/scenes/managers/scene/player.gd.uid new file mode 100644 index 0000000..bb355fa --- /dev/null +++ b/src/scenes/managers/scene/player.gd.uid @@ -0,0 +1 @@ +uid://c13l6ywhxq6n5 diff --git a/src/scenes/managers/scene/signalbus.gd b/src/scenes/managers/scene/signalbus.gd new file mode 100644 index 0000000..92679cb --- /dev/null +++ b/src/scenes/managers/scene/signalbus.gd @@ -0,0 +1,10 @@ +# --- License +# File: /client/src/scenes/managers/scene/signalbus.gd +# Project: OpenMinerva +# Created Date: 13 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node diff --git a/src/scenes/managers/scene/signalbus.gd.uid b/src/scenes/managers/scene/signalbus.gd.uid new file mode 100644 index 0000000..9814658 --- /dev/null +++ b/src/scenes/managers/scene/signalbus.gd.uid @@ -0,0 +1 @@ +uid://cxbdjoelope42 From 6c51bb0c45c3148bdc891a6e5ffbc850eeef394b Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 06:05:50 -0500 Subject: [PATCH 07/27] Start server managers. --- src/scenes/levels/base.tscn | 2 +- src/scenes/managers/app/network.gd | 3 ++- src/scenes/managers/app/scene.gd | 16 ++++++++++++---- src/scenes/managers/scene/player.gd | 2 +- src/scenes/managers/scene/signalbus.gd | 2 ++ 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/scenes/levels/base.tscn b/src/scenes/levels/base.tscn index 56159e7..f84ff1d 100644 --- a/src/scenes/levels/base.tscn +++ b/src/scenes/levels/base.tscn @@ -8,7 +8,7 @@ [node name="PlayerManager" type="Node" parent="." unique_id=412138074] script = ExtResource("1_c3hqj") -[node name="ServerSignalManager" type="Node" parent="." unique_id=165926833] +[node name="SignalBus" type="Node" parent="." unique_id=165926833] script = ExtResource("2_fundk") [node name="root" type="Node3D" parent="." unique_id=345927695] diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index d254fc3..9512348 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -80,9 +80,10 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI else: scene_m.set_master_root_from_program(_scene, Enum.BaseLevel.GRID) - # TODO: Start session managers. (Handles players, spawning, permissions (as host)) + scene_m.start_master_scene(_scene) # DEV: Force spawn the host. + scene_m.get_master_scene(_scene).get_node("PlayerManager").spawn_player(1) return response_dict diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index bd3135c..f22e700 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -76,10 +76,18 @@ func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> b return false func start_master_scene(id: String): - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) - # get_master_scene - # Start Player manager. - # Start ServerSignalManager. + const MANAGERS = ["PlayerManager", "SignalBus"] + + var _scene = get_master_scene(id) + + for node_name in MANAGERS: + var _scene_manager = _scene.get_node_or_null(node_name) + if _scene_manager: + _scene_manager.active = true + GlobalLogger.logs("'%s' started in server '%s'" % [node_name, id]) + continue + + GlobalLogger.logs("Could not start invalid manager '%s' in server '%s'" % [node_name, id], 3) return func _get_scene_by_type(scene_type: Enum.BaseLevel) -> PackedScene: diff --git a/src/scenes/managers/scene/player.gd b/src/scenes/managers/scene/player.gd index 81dbdaa..09b0db1 100644 --- a/src/scenes/managers/scene/player.gd +++ b/src/scenes/managers/scene/player.gd @@ -20,4 +20,4 @@ func spawn_player(peer_id: int) -> void: return func kill_player() -> void: - return \ No newline at end of file + return diff --git a/src/scenes/managers/scene/signalbus.gd b/src/scenes/managers/scene/signalbus.gd index 92679cb..3dfbf28 100644 --- a/src/scenes/managers/scene/signalbus.gd +++ b/src/scenes/managers/scene/signalbus.gd @@ -8,3 +8,5 @@ # --- License extends Node + +var active = false \ No newline at end of file From f3ec248a2e5900f2ad31820c131ea602ae644018 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 06:17:58 -0500 Subject: [PATCH 08/27] Added function name to logger. --- src/scripts/logger.gd | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/scripts/logger.gd b/src/scripts/logger.gd index b2ae399..52bf1d2 100644 --- a/src/scripts/logger.gd +++ b/src/scripts/logger.gd @@ -46,11 +46,10 @@ func logs(message: String = "", level: int = 0): _log_to_file(message, level) if console_logging_enabled: - print_rich("[[color=%s]%s[/color]] %s" % [log_level_colors[level], log_level_names[level], message]) + var stack := get_stack() + print_rich("[[color=%s]%s[/color]] %s [[color=lightyellow]%s[/color]]" % [log_level_colors[level], log_level_names[level], message, stack[1]["function"]]) if level == 3: # We are skipping the first frame, otherwise this function will be logged. - var stack := get_stack() - for i in range(1, stack.size()): var frame = stack[i] print( From f94108a350748d8f1d7adf57ce439c93c158e9c9 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 13 Apr 2026 06:20:30 -0500 Subject: [PATCH 09/27] Removed old files. --- .../managers/app/old/network_manaager.gd | 141 ------------- .../managers/app/old/network_manaager.gd.uid | 1 - .../managers/app/old/network_manager.gd.uid | 1 - .../managers/app/old/network_manager.old.gd | 190 ------------------ .../app/old/network_manager.old.gd.uid | 1 - .../managers/app/old/rpc_manager_old.gd | 16 -- .../managers/app/old/rpc_manager_old.gd.uid | 1 - .../managers/app/old/scene_manager_old.gd | 37 ---- .../managers/app/old/scene_manager_old.gd.uid | 1 - 9 files changed, 389 deletions(-) delete mode 100644 src/scenes/managers/app/old/network_manaager.gd delete mode 100644 src/scenes/managers/app/old/network_manaager.gd.uid delete mode 100644 src/scenes/managers/app/old/network_manager.gd.uid delete mode 100644 src/scenes/managers/app/old/network_manager.old.gd delete mode 100644 src/scenes/managers/app/old/network_manager.old.gd.uid delete mode 100644 src/scenes/managers/app/old/rpc_manager_old.gd delete mode 100644 src/scenes/managers/app/old/rpc_manager_old.gd.uid delete mode 100644 src/scenes/managers/app/old/scene_manager_old.gd delete mode 100644 src/scenes/managers/app/old/scene_manager_old.gd.uid diff --git a/src/scenes/managers/app/old/network_manaager.gd b/src/scenes/managers/app/old/network_manaager.gd deleted file mode 100644 index 05df56a..0000000 --- a/src/scenes/managers/app/old/network_manaager.gd +++ /dev/null @@ -1,141 +0,0 @@ -# --- License -# File: /client/src/scenes/managers/app/network_manaager.gd -# Project: OpenMinerva -# Created Date: 06 April 2026 -# Copyright (c) 2026 OpenMinerva -# License: MIT License -# Authors: Armored Dragon -# --- License - -extends Node - -@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -@onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") -@onready var http = preload("res://scripts/network/http.gd").new() - -enum PrivacyLevel { - INVITE = 0, - PUBLIC = 1, - CONTACTS_PLUS = 2, - CONTACTS = 3, - FRIENDS_PLUS = 4, - FRIENDS = 5 -} - -enum BaseLevel { - DEBUG = 0, - EMPTY = 1, - GRID = 2, -} - -var _status = { - "focused_world": "", - # Keeps a running list of all sessions we are connected to. - "sessions": { - - } -} -# TODO: Get account username - -func start_server(port: int, base_level: BaseLevel) -> void: - # ! In Godot 4, you can not adjust the maximum connected clients without restarting the server. We will just reject join requests if they would go over the limit. - const MAX_CLIENTS = 1000 - - GlobalLogger.logs("Trying to start a new server. Port: '%s', Level: '%s'" % [port, base_level]) - var new_peer = ENetMultiplayerPeer.new() - - var err = new_peer.create_server(port, MAX_CLIENTS) - if err == 20: - # TODO: Handle port conflicts, try a different port? - GlobalLogger.logs("Failed to start server: Is the port in use?", 1) - return - - if err != OK: - GlobalLogger.logs("Failed to start server. Error: '%s'" % err, 1) - return - - var _entry = _get_session_entry() - var _entry_id = _entry.keys()[0] - _status.sessions[_entry_id] = _entry[_entry_id] - _status.sessions[_entry_id]["multiplayer_peer"] = new_peer - _status.sessions[_entry_id]["base_level"] = base_level - - GlobalLogger.logs("Successfully started server.", 1) - - match _status.sessions[_entry_id]["base_level"]: - BaseLevel.DEBUG: - GlobalLogger.logs("Loading the debug world template.") - await scene_manager.load_multiplayer_scene("res://scenes/levels/home.tscn", _entry_id) - BaseLevel.GRID: - GlobalLogger.logs("Loading the grid world template.") - await scene_manager.load_multiplayer_scene("res://scenes/levels/grid.tscn", _entry_id) - - # Force spawn the host. - rpc_lib.com.on_spawn_player(1) - - # Focus this world - _status.focused_world = _entry_id - return - -func _get_session_entry() -> Dictionary: - var session_id = Random.random_string() - - # TODO: Find first unused port and use that. - - var entry = { - session_id: { - "multiplayer_peer": null, - "instance_name": "", - "instance_description": "", - "port": 20205, - "max_connected_users": 1, - "instance_privacy": PrivacyLevel.INVITE, - - "user_list": [], - "base_level": BaseLevel.DEBUG, - - "kick_afk": false, - "kick_afk_timeout": 300, - "clean_unused_assets": false, - "clean_timeout": 300, - "autosave": false, - "autosave_timeout": 300, - - "networking": { - "use_steam": false, - "use_lan": true - } - } - } - return entry - -func spawn_player(player): - # FIXME: Placeholder for refactor - while scene_manager.get_current_session_node() == null: - await get_tree().process_frame - - scene_manager.get_current_session_node().call_deferred("add_child", player) - -func get_active_sessions() -> Array: - var result = [] - - for session_id in _status.sessions.keys(): - result.append(_status.sessions[session_id].merged({"id": session_id})) - - return result - -func change_session_settings(session_settings: Dictionary) -> void: - _status.sessions[_status.focused_world].instance_privacy = session_settings.instance_privacy - - if _status.sessions[_status.focused_world].instance_privacy > 0: - GlobalLogger.logs("Advertising session.") - var url = UrlParser.deconstruct("http://localhost:40500/api/v1/postSession") - # TODO: Error checking - url = url.data - var _body = { - "session_name": _status.focused_world, - } - var advertise_response = await http.req(HTTPClient.Method.METHOD_POST, url.host, url.path, url.port, ["Accept: application/json", "Content-Type: application/json", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key], JSON.stringify(_body)) - print(advertise_response) - - return \ No newline at end of file diff --git a/src/scenes/managers/app/old/network_manaager.gd.uid b/src/scenes/managers/app/old/network_manaager.gd.uid deleted file mode 100644 index 1795578..0000000 --- a/src/scenes/managers/app/old/network_manaager.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bh271sucj14cq diff --git a/src/scenes/managers/app/old/network_manager.gd.uid b/src/scenes/managers/app/old/network_manager.gd.uid deleted file mode 100644 index 3935f11..0000000 --- a/src/scenes/managers/app/old/network_manager.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cc0ei8wuetrvh diff --git a/src/scenes/managers/app/old/network_manager.old.gd b/src/scenes/managers/app/old/network_manager.old.gd deleted file mode 100644 index a768282..0000000 --- a/src/scenes/managers/app/old/network_manager.old.gd +++ /dev/null @@ -1,190 +0,0 @@ -# --- License -# File: /client/src/scenes/managers/app/network_manager.gd -# Project: OpenMinerva -# Created Date: 05 February 2026 -# Copyright (c) 2026 OpenMinerva -# License: MIT License -# Authors: Armored Dragon -# --- License - -extends Node - -var n_c = preload("res://scripts/network/network_compression.gd").new() -var rsa = preload("res://scripts/crypto/rsa.gd").new() -var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") - -# TODO: Bandwidth toggles -@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -@onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") - -var active_session = "" - -# This file contains all of the session management and client communication. -# Anything that goes through the network should first route through here at some point. -enum PrivacyLevel { - INVITE = 0, - PUBLIC = 1, - CONTACTS_PLUS = 2, - CONTACTS = 3, - FRIENDS_PLUS = 4, - FRIENDS = 5 -} - -var status = { - "hosting": false, - "client": false -} - -var config = { - "instance_name": "", - "instance_description": "", - "port": 20205, - "max_connected_users": 1, - "instance_privacy": PrivacyLevel.INVITE, - - "networking": { - "use_steam": false, - "use_lan": true - } -} - -var info = { - "level": "res://scenes/levels/home.tscn", - "level_node_name": "", - "clients": [] -} - -func start_server(port: int = config.port, max_clients: int = config.max_clients, ignore_port: bool = false) -> void: - # TODO: In ignore_port = true, keep trying to make a server until it succeeds. - if status.hosting: - # This ideally should not trigger - GlobalLogger.logs("Can not start server: Server is already running.", 2) - status.hosting = false - status.client = false - return - - var new_peer = ENetMultiplayerPeer.new() - # FIXME: Error handling is required here - info.clients.append({"username": "Me!", "multiplayer_id": 1}) - var err = new_peer.create_server(port, max_clients) - # FIXME: This client append is happening too early, this is a debug position - - if err == 20: - # Port is in use - GlobalLogger.logs("Failed to start server: Is the port in use?", 1) - # FIXME: HACK: Just try again with the default port + 1. - err = new_peer.create_server(port + 1, max_clients) - status.hosting = false - status.client = false - - if err != OK: - GlobalLogger.logs("Failed to start server. Error: '%s'" % err, 1) - status.hosting = false - status.client = false - return - - - multiplayer.multiplayer_peer = new_peer - GlobalLogger.logs("Successfully started server.", 1) - - while status.hosting == false: - await get_tree().process_frame - status.hosting = true - status.client = false - - # TODO: Hardcoded spawn host value, is there a better way? - rpc_lib.com.on_spawn_player(1) - -func close_server(): - # Disconnect all players. - # Remove listings from all used networking. - # Update server config. - # TODO: OfflineMultiplayerPeer is a test. Check to see if this actually works. - multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new() - status.hosting = false - status.client = false - return - -func update_server(): - # Update our config. - # Submit a update to any active networking service. - GlobalLogger.logs("Not implemented.", 3) - return - -func join_server(ip: String = "", port: int = config.port) -> void: - # Client connects to a server. - if ip.is_empty(): - GlobalLogger.logs("No IP to connect to.", 2) - return - - if status.hosting: - # This ideally should not trigger - GlobalLogger.logs("Can not join server: We are currently hosting a server.", 2) - close_server() - # return - - var new_peer = ENetMultiplayerPeer.new() - new_peer.create_client(ip, port) - multiplayer.multiplayer_peer = new_peer - - status.hosting = false - status.client = true - GlobalLogger.logs("Connected to the server.", 1) - return - -func kick_player(player_id: int, reason: String = "No reason specified"): - # Server kicks a player from the session. - GlobalLogger.logs("Not implemented.", 3) - return - -func ban_player(): - # Server permanatly bans a user. - GlobalLogger.logs("Not implemented.", 3) - return - -func set_networking_config(options: Dictionary) -> void: - if !options: - GlobalLogger.logs("Tried to set networking config without options", 2) - return - - # LAN connections - if options.lan == true: - config.use_lan = true - else: - config.use_lan = false - - # Steam connections - if options.steam == true: - config.use_steam = true - else: - config.use_steam = false - -func parse_url(url: String) -> Dictionary: - var result = { - "scheme": "", - "host": "", - "port": 0, - "path": "" - } - - var matches = url_regex.search(url) - if matches: - result["scheme"] = matches.get_string(1).to_lower() - result["host"] = matches.get_string(2) - result["port"] = int(matches.get_string(3)) if matches.get_string(3) != "" else (443 if result["scheme"] == "https" else 80) - result["path"] = matches.get_string(4) if matches.get_string(4) != "" else "/" - - return result - -func spawn_player(player): - # FIXME: Placeholder for refactor - while scene_manager.get_current_session_node() == null: - await get_tree().process_frame - - scene_manager.get_current_session_node().call_deferred("add_child", player) - -func player_exists(name: String) -> Node3D: - # FIXME: Placeholder for refactor - var target_node = scene_manager.get_current_session_node().get_node_or_null(name) - - return target_node diff --git a/src/scenes/managers/app/old/network_manager.old.gd.uid b/src/scenes/managers/app/old/network_manager.old.gd.uid deleted file mode 100644 index 5bdeec9..0000000 --- a/src/scenes/managers/app/old/network_manager.old.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://v6t8nqyh3ywb diff --git a/src/scenes/managers/app/old/rpc_manager_old.gd b/src/scenes/managers/app/old/rpc_manager_old.gd deleted file mode 100644 index b8ee3ca..0000000 --- a/src/scenes/managers/app/old/rpc_manager_old.gd +++ /dev/null @@ -1,16 +0,0 @@ -extends Node - -var c := preload("res://scripts/rpc/client.gd").new() -var s := preload("res://scripts/rpc/server.gd").new() -var com := preload("res://scripts/rpc/common.gd").new() - -func _ready(): - # RPCs can not be called from outside of the scene tree, we are required to add them. - add_child(c) - add_child(s) - add_child(com) - - multiplayer.peer_connected.connect(s.on_peer_connected) - multiplayer.peer_disconnected.connect(s.on_peer_disconnected) - multiplayer.connected_to_server.connect(c.connected_to_server) - multiplayer.connection_failed.connect(c.connection_failed) \ No newline at end of file diff --git a/src/scenes/managers/app/old/rpc_manager_old.gd.uid b/src/scenes/managers/app/old/rpc_manager_old.gd.uid deleted file mode 100644 index a7c8542..0000000 --- a/src/scenes/managers/app/old/rpc_manager_old.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://x23lqbiivx1q diff --git a/src/scenes/managers/app/old/scene_manager_old.gd b/src/scenes/managers/app/old/scene_manager_old.gd deleted file mode 100644 index db1a454..0000000 --- a/src/scenes/managers/app/old/scene_manager_old.gd +++ /dev/null @@ -1,37 +0,0 @@ -extends Node - -# TODO: Allow multiple sessions -# Right now only one session is allowed and is destroyed when the player joins another session. - -# Game managers -@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") -@onready var scene_work_root = get_tree().current_scene.get_node("Scenes") -@onready var player_home_scene: PackedScene = load("res://scenes/levels/home.tscn") - -var server_init: bool = false - -func _ready(): - await network_manager.start_server(20205, 2) - -func load_multiplayer_scene(scene_dir: String, scene_name: String): - await _clean_scene_work_root() - var scene_packed: PackedScene = load(scene_dir) - - var scene = scene_packed.instantiate() - scene.name = scene_name - scene_work_root.add_child(scene) - await get_tree().process_frame - return - -func _clean_scene_work_root(): - var nodes_to_destroy = scene_work_root.get_children() - for node in nodes_to_destroy: - node.queue_free() - await get_tree().process_frame - return - -func _spawn_host_player(): - network_manager.spawn_player(1) - -func get_current_session_node(): - return get_tree().current_scene.get_node("Scenes").get_child(0) diff --git a/src/scenes/managers/app/old/scene_manager_old.gd.uid b/src/scenes/managers/app/old/scene_manager_old.gd.uid deleted file mode 100644 index 44c2b63..0000000 --- a/src/scenes/managers/app/old/scene_manager_old.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bwe2gsnip66cr From 7dce4473abc5fb005aa6138cc4c888b45f891380 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 14 Apr 2026 14:24:53 -0500 Subject: [PATCH 10/27] Fixed port finder bug. The function would never find a port, woopsie! --- src/scenes/managers/app/network.gd | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 9512348..d7a8ac1 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -43,7 +43,7 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI var response_dict = {"ok": false, "error": null, "data": null} # Get an available port. If port was defined, force that port or fail. - if port: + if port == 0: var port_available = !_is_port_in_use(port) if !port_available: response_dict.error = "Port is not available." @@ -163,6 +163,8 @@ func _is_port_in_use(port: int) -> bool: if err == OK: tcp_server.stop() - return true + return false + + return true return false From 44110db9ba4c66cf0994ce19f31dbb1ac369354d Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 14 Apr 2026 14:40:24 -0500 Subject: [PATCH 11/27] Initial session advertising work. --- src/scenes/managers/app/network.gd | 33 ++++++++++++++++++++++++++++-- src/userinterface/dash/instance.gd | 6 ++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index d7a8ac1..09b9c94 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -96,11 +96,18 @@ func stop_server(id: String): # Destroy server master scene. return -func update_server(id: String, update_dict: Dictionary): +func update_server(id: String, server_info: Dictionary): # Get server from database. # Validate server updated data. # Update the database entry. # Emit server updated event to the server. + # If server is now public, and advertising is enabled, advertise to the session-server(s). + # TODO: Get session_servers from client config + # TODO: Get enabled session_servers from server config + # TODO: For each enabled session_server: + _advertise_session(server_info, "http://localhost:40500") + # TODO: Get list of successful session advertisements, and start heartbeats. + return func join_server(ip: String, port: int): @@ -167,4 +174,26 @@ func _is_port_in_use(port: int) -> bool: return true - return false +func _advertise_session(session_info: Dictionary, session_server: String) -> Dictionary: + var response_dict = {"ok": false, "error": null, "data": null} + GlobalLogger.logs("Advertising session '%s' to the server '%s'" % [session_info, session_server]) + var url = UrlParser.deconstruct("%s/api/v1/postSession" % session_server) + # TODO: Error checking + url = url.data + + var _body = { + "session_name": session_info.id, + } + + var advertise_response = await http.req( + HTTPClient.Method.METHOD_POST, + url.host, + url.path, + url.port, + ["Accept: application/json", "Content-Type: application/json", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key], + JSON.stringify(_body) + ) + + print(advertise_response) + + return response_dict diff --git a/src/userinterface/dash/instance.gd b/src/userinterface/dash/instance.gd index 56dab6a..486751e 100644 --- a/src/userinterface/dash/instance.gd +++ b/src/userinterface/dash/instance.gd @@ -18,7 +18,7 @@ enum PrivacyLevel { FRIENDS = 5 } -@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") @onready var instance_privacy_container = get_node("VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name") @onready var instance_privacy_public_btn = instance_privacy_container.get_node("Public") @@ -84,5 +84,7 @@ func update_instance(instance: Dictionary) -> void: func _handle_save_session_info() -> void: # TODO: Have this page know what instance it currently occupies. - network_manager.change_session_settings({"instance_privacy": 1}) + var _sessions = network_m.get_connected_sessions() + + network_m.update_server(_sessions[0].id, _sessions[0]) return \ No newline at end of file From c4637fc8e8d5b8f22e2f2d4592a33316184d2b01 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 14 Apr 2026 15:24:49 -0500 Subject: [PATCH 12/27] Removed unneeded Enum. --- src/userinterface/dash/instance.gd | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/userinterface/dash/instance.gd b/src/userinterface/dash/instance.gd index 486751e..7a90f2d 100644 --- a/src/userinterface/dash/instance.gd +++ b/src/userinterface/dash/instance.gd @@ -9,14 +9,6 @@ extends Control -enum PrivacyLevel { - INVITE = 0, - PUBLIC = 1, - CONTACTS_PLUS = 2, - CONTACTS = 3, - FRIENDS_PLUS = 4, - FRIENDS = 5 -} @onready var network_m = get_tree().current_scene.get_node("NetworkManager") @@ -29,16 +21,16 @@ enum PrivacyLevel { @onready var save_changes_btn = get_node("VBoxContainer/HBoxContainer2/SaveChanges") func _ready(): - instance_privacy_public_btn.pressed.connect(_update_instance_privacy.bind(PrivacyLevel.PUBLIC)) - instance_privacy_contacts_btn.pressed.connect(_update_instance_privacy.bind(PrivacyLevel.CONTACTS)) - instance_privacy_friends_btn.pressed.connect(_update_instance_privacy.bind(PrivacyLevel.FRIENDS)) - instance_privacy_invite_btn.pressed.connect(_update_instance_privacy.bind(PrivacyLevel.INVITE)) + instance_privacy_public_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.PUBLIC)) + instance_privacy_contacts_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.CONTACTS)) + instance_privacy_friends_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.FRIENDS)) + instance_privacy_invite_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.INVITE)) save_changes_btn.pressed.connect(_handle_save_session_info) Events.connect("instance_updated", update_instance) - _update_instance_privacy(PrivacyLevel.INVITE) + _update_instance_privacy(Enum.PrivacyLevel.INVITE) return func _update_instance_privacy(level): @@ -51,13 +43,13 @@ func _update_instance_privacy(level): if node is Button: _privacy_button_disable(node) match level: - PrivacyLevel.INVITE: + Enum.PrivacyLevel.INVITE: _privacy_button_enable(instance_privacy_invite_btn) - PrivacyLevel.PUBLIC: + Enum.PrivacyLevel.PUBLIC: _privacy_button_enable(instance_privacy_public_btn) - PrivacyLevel.CONTACTS: + Enum.PrivacyLevel.CONTACTS: _privacy_button_enable(instance_privacy_contacts_btn) - PrivacyLevel.FRIENDS: + Enum.PrivacyLevel.FRIENDS: _privacy_button_enable(instance_privacy_friends_btn) return From 57c104e2749e004374fc5b225a77d76daf5fcf1f Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 14 Apr 2026 15:25:42 -0500 Subject: [PATCH 13/27] Initial heartbeat work. --- src/project.godot | 2 +- src/scenes/managers/app/network.gd | 65 ++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/project.godot b/src/project.godot index c2c9808..40ad193 100644 --- a/src/project.godot +++ b/src/project.godot @@ -33,8 +33,8 @@ GlobalAccount="*uid://dtlb70kxvbtvn" Events="*uid://c656spc3ppdlw" OAuth="*uid://jd7qlsley1no" SessionQuery="*uid://bl3e1utjvw3ul" -UrlParser="*uid://budprjmmpally" Enum="*uid://b5amtwc5yahe3" +UrlParser="*uid://budprjmmpally" [display] diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 09b9c94..f8473e7 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -14,10 +14,12 @@ const MAX_CLIENTS = 1000 var n_c = preload("res://scripts/network/network_compression.gd").new() var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") +@onready var http = preload("res://scripts/network/http.gd").new() @onready var scene_m = get_tree().current_scene.get_node("SceneManager") @onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") var _database = { + "heartbeats": {}, "sessions": {}, "sessions_api": {} } @@ -105,9 +107,11 @@ func update_server(id: String, server_info: Dictionary): # TODO: Get session_servers from client config # TODO: Get enabled session_servers from server config # TODO: For each enabled session_server: - _advertise_session(server_info, "http://localhost:40500") - # TODO: Get list of successful session advertisements, and start heartbeats. + var advertise_response = await _advertise_session(server_info, "http://localhost:40500") + # TODO: Get list of successful session advertisements, and start heartbeats. + if advertise_response.ok == true: + _create_heartbeat_timer(advertise_response.data.id) return func join_server(ip: String, port: int): @@ -174,9 +178,49 @@ func _is_port_in_use(port: int) -> bool: return true +func _create_heartbeat_timer(session_id: String): + GlobalLogger.logs("Creating a heartbeat timer for server '%s'" % session_id) + # FIXME: Hardcoded time for timer. + var timer = get_tree().create_timer(20) + + _database.heartbeats[session_id] = timer + + timer.timeout.connect(_heartbeat_timer_timeout.bind(session_id)) + return + +func _heartbeat_timer_timeout(session_id): + GlobalLogger.logs("Sending a heartbeat for server '%s'" % session_id) + if _database.heartbeats.has(session_id) == false: + GlobalLogger.logs("Server '%s' does not exist anymore, not sending a heartbeat." % session_id) + return + + _heartbeat_session(session_id) + + _create_heartbeat_timer(session_id) + return + +func _heartbeat_session(session_id: String): + # FIXME: Hardcoded localhost link. + var url_parts = UrlParser.deconstruct("http://localhost:40500/api/v1/heartbeatSession") + url_parts = url_parts.data + var body = {"session_id": session_id} + + var response = await http.req( + HTTPClient.Method.METHOD_POST, + url_parts.host, + url_parts.path, + url_parts.port, + ["Accept: application/json", "Content-Type: application/json", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key], + JSON.stringify(body) + ) + + if response and response.get("ok"): + GlobalLogger.logs("Heartbeat sent for session '%s'" % session_id, 0) + return + func _advertise_session(session_info: Dictionary, session_server: String) -> Dictionary: var response_dict = {"ok": false, "error": null, "data": null} - GlobalLogger.logs("Advertising session '%s' to the server '%s'" % [session_info, session_server]) + GlobalLogger.logs("Advertising session '%s' to the server '%s'" % [session_info.id, session_server]) var url = UrlParser.deconstruct("%s/api/v1/postSession" % session_server) # TODO: Error checking url = url.data @@ -194,6 +238,19 @@ func _advertise_session(session_info: Dictionary, session_server: String) -> Dic JSON.stringify(_body) ) - print(advertise_response) + # FIXME: What is this flow? This is bad? + if advertise_response.ok == false: + response_dict.error = advertise_response.error + return response_dict + + advertise_response = JSON.parse_string(advertise_response.body) + if advertise_response.ok == false: + response_dict.error = advertise_response.error + return response_dict + + advertise_response = advertise_response.data + + response_dict.ok = true + response_dict.data = advertise_response return response_dict From 900e6772ef16474da8428ebbacbbbb2490e1f539 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 14 Apr 2026 18:19:46 -0500 Subject: [PATCH 14/27] Session Updating. Sessions can now update settings on the fly. --- src/scenes/managers/app/network.gd | 42 ++++++++++++++++++++++++++-- src/userinterface/dash/hud.tscn | 30 ++++++++++---------- src/userinterface/dash/instance.gd | 45 ++++++++++++++++++++++++------ 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index f8473e7..4080d2b 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -20,6 +20,7 @@ var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$" var _database = { "heartbeats": {}, + "sessions_id": {}, "sessions": {}, "sessions_api": {} } @@ -107,12 +108,21 @@ func update_server(id: String, server_info: Dictionary): # TODO: Get session_servers from client config # TODO: Get enabled session_servers from server config # TODO: For each enabled session_server: + # TODO: Check if we are already advertising this server, then submit a generic update to all session servers. + if _database.heartbeats.has(id): + GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) + await _update_session(server_info, "http://localhost:40500") + return + var advertise_response = await _advertise_session(server_info, "http://localhost:40500") # TODO: Get list of successful session advertisements, and start heartbeats. if advertise_response.ok == true: - _create_heartbeat_timer(advertise_response.data.id) + # TODO: Create a helper to manage the session IDs, or database by itself? + _database.sessions_id.set(server_info.id, advertise_response.data.id) + _create_heartbeat_timer(server_info.id) return + # If the server is now private, kill the heartbeat and submit a request to review the session from the session server func join_server(ip: String, port: int): # Create multiplayer peer. @@ -151,6 +161,32 @@ func get_connected_sessions(): return result +func _update_session(session_info: Dictionary, session_server: String) -> Dictionary: + var response_dict = {"ok": false, "error": null, "data": null} + + GlobalLogger.logs("Updating session '%s' to the server '%s'" % [session_info.id, session_server]) + var url = UrlParser.deconstruct("%s/api/v1/updateSession" % session_server) + # TODO: Error checking + url = url.data + + var _body = { + "id": _database.sessions_id.get(session_info.id), + "session_name": session_info.name, + "session_description": session_info.description, + "session_privacy": session_info.privacy, + } + + var _update_response = await http.req( + HTTPClient.Method.METHOD_POST, + url.host, + url.path, + url.port, + ["Accept: application/json", "Content-Type: application/json", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key], + JSON.stringify(_body) + ) + + return response_dict + func _find_available_port(target_port: int = 20205) -> int: GlobalLogger.logs("Trying to find an available port starting at '%s'." % target_port) var _found_port = null @@ -226,7 +262,9 @@ func _advertise_session(session_info: Dictionary, session_server: String) -> Dic url = url.data var _body = { - "session_name": session_info.id, + "session_name": session_info.name, + "session_description": session_info.description, + "session_privacy": session_info.privacy, } var advertise_response = await http.req( diff --git a/src/userinterface/dash/hud.tscn b/src/userinterface/dash/hud.tscn index aaa4a59..a272649 100644 --- a/src/userinterface/dash/hud.tscn +++ b/src/userinterface/dash/hud.tscn @@ -842,15 +842,15 @@ theme_override_constants/margin_top = 5 theme_override_constants/margin_right = 5 theme_override_constants/margin_bottom = 5 -[node name="Instance Name" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer/MarginContainer" unique_id=1986372326] +[node name="InstanceName" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer/MarginContainer" unique_id=1986372326] layout_mode = 2 size_flags_horizontal = 3 -[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer/MarginContainer/Instance Name" unique_id=1002302148] +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer/MarginContainer/InstanceName" unique_id=1002302148] layout_mode = 2 text = "Instance Name" -[node name="LineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer/MarginContainer/Instance Name" unique_id=2029691082] +[node name="InstanceNameField" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer/MarginContainer/InstanceName" unique_id=2029691082] layout_mode = 2 text = "My Instance" placeholder_text = "Instance Name" @@ -865,15 +865,15 @@ theme_override_constants/margin_top = 5 theme_override_constants/margin_right = 5 theme_override_constants/margin_bottom = 5 -[node name="Instance Name2" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2/MarginContainer" unique_id=2036761912] +[node name="InstanceDescriptionContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2/MarginContainer" unique_id=2036761912] layout_mode = 2 size_flags_horizontal = 3 -[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2/MarginContainer/Instance Name2" unique_id=1778162350] +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2/MarginContainer/InstanceDescriptionContainer" unique_id=1778162350] layout_mode = 2 text = "Instance Description" -[node name="TextEdit" type="TextEdit" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2/MarginContainer/Instance Name2" unique_id=1883764719] +[node name="InstanceDescription" type="TextEdit" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2/MarginContainer/InstanceDescriptionContainer" unique_id=1883764719] custom_minimum_size = Vector2(0, 100) layout_mode = 2 text = "A basic instance description" @@ -901,7 +901,7 @@ layout_mode = 2 size_flags_horizontal = 3 text = "Max connected users" -[node name="SpinBox" type="SpinBox" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceSettings/MarginContainer/HBoxContainer" unique_id=218460777] +[node name="MaxConnectedUsers" type="SpinBox" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceSettings/MarginContainer/HBoxContainer" unique_id=218460777] layout_mode = 2 min_value = 1.0 max_value = 1000.0 @@ -918,47 +918,47 @@ theme_override_constants/margin_top = 5 theme_override_constants/margin_right = 5 theme_override_constants/margin_bottom = 5 -[node name="Instance Name" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer" unique_id=44619055] +[node name="InstancePrivacyContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer" unique_id=44619055] layout_mode = 2 size_flags_horizontal = 3 -[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=1242833055] +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=1242833055] layout_mode = 2 text = "Instance Privacy" -[node name="Public" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=700002280] +[node name="Public" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=700002280] custom_minimum_size = Vector2(0, 50) layout_mode = 2 toggle_mode = true text = "Public" -[node name="Contacts+" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=1719321337] +[node name="Contacts+" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=1719321337] visible = false custom_minimum_size = Vector2(0, 50) layout_mode = 2 toggle_mode = true text = "Contacts+" -[node name="Contacts" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=1878668850] +[node name="Contacts" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=1878668850] custom_minimum_size = Vector2(0, 40) layout_mode = 2 toggle_mode = true text = "Contacts" -[node name="Friends+" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=1834008150] +[node name="Friends+" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=1834008150] visible = false custom_minimum_size = Vector2(0, 50) layout_mode = 2 toggle_mode = true text = "Friends+" -[node name="Friends" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=4967885] +[node name="Friends" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=4967885] custom_minimum_size = Vector2(0, 40) layout_mode = 2 toggle_mode = true text = "Friends" -[node name="InviteOnly" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name" unique_id=1625370876] +[node name="InviteOnly" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer" unique_id=1625370876] custom_minimum_size = Vector2(0, 40) layout_mode = 2 toggle_mode = true diff --git a/src/userinterface/dash/instance.gd b/src/userinterface/dash/instance.gd index 7a90f2d..44e9ff7 100644 --- a/src/userinterface/dash/instance.gd +++ b/src/userinterface/dash/instance.gd @@ -9,10 +9,14 @@ extends Control - @onready var network_m = get_tree().current_scene.get_node("NetworkManager") -@onready var instance_privacy_container = get_node("VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy/MarginContainer/Instance Name") +@onready var instance_settings_root = get_node("VBoxContainer/HBoxContainer") +@onready var instance_name = instance_settings_root.get_node("VBoxContainer2/PanelContainer/MarginContainer/InstanceName/InstanceNameField") +@onready var instance_description = instance_settings_root.get_node("VBoxContainer2/PanelContainer2/MarginContainer/InstanceDescriptionContainer/InstanceDescription") +@onready var instance_max_users = instance_settings_root.get_node("VBoxContainer/InstanceSettings/MarginContainer/HBoxContainer/MaxConnectedUsers") + +@onready var instance_privacy_container = instance_settings_root.get_node("VBoxContainer/InstancePrivacy/MarginContainer/InstancePrivacyContainer") @onready var instance_privacy_public_btn = instance_privacy_container.get_node("Public") @onready var instance_privacy_contacts_btn = instance_privacy_container.get_node("Contacts") @onready var instance_privacy_friends_btn = instance_privacy_container.get_node("Friends") @@ -20,25 +24,30 @@ extends Control @onready var save_changes_btn = get_node("VBoxContainer/HBoxContainer2/SaveChanges") +var session_privacy: Enum.PrivacyLevel + func _ready(): - instance_privacy_public_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.PUBLIC)) - instance_privacy_contacts_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.CONTACTS)) - instance_privacy_friends_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.FRIENDS)) - instance_privacy_invite_btn.pressed.connect(_update_instance_privacy.bind(Enum.PrivacyLevel.INVITE)) + instance_privacy_public_btn.pressed.connect(_update_instance_privacy_visual.bind(Enum.PrivacyLevel.PUBLIC)) + instance_privacy_contacts_btn.pressed.connect(_update_instance_privacy_visual.bind(Enum.PrivacyLevel.CONTACTS)) + instance_privacy_friends_btn.pressed.connect(_update_instance_privacy_visual.bind(Enum.PrivacyLevel.FRIENDS)) + instance_privacy_invite_btn.pressed.connect(_update_instance_privacy_visual.bind(Enum.PrivacyLevel.INVITE)) save_changes_btn.pressed.connect(_handle_save_session_info) Events.connect("instance_updated", update_instance) - _update_instance_privacy(Enum.PrivacyLevel.INVITE) + _update_instance_privacy_visual(Enum.PrivacyLevel.INVITE) return -func _update_instance_privacy(level): + +func _update_instance_privacy_visual(level): if !is_multiplayer_authority(): return GlobalLogger.logs("Updating instance privacy.") + session_privacy = level + for node in instance_privacy_container.get_children(): if node is Button: _privacy_button_disable(node) @@ -78,5 +87,23 @@ func _handle_save_session_info() -> void: # TODO: Have this page know what instance it currently occupies. var _sessions = network_m.get_connected_sessions() + # Get current session info settings from the dashboard. + # TODO: Get the current session we are connected to. + # Update the database to the new settings. + var _current_session_settings = _get_server_settings() + + _sessions[0].set("name", _current_session_settings.name) + _sessions[0].set("description", _current_session_settings.description) + _sessions[0].set("max_connected_users", _current_session_settings.max_connected_users) + _sessions[0].set("privacy", _current_session_settings.privacy) + network_m.update_server(_sessions[0].id, _sessions[0]) - return \ No newline at end of file + return + +func _get_server_settings() -> Dictionary: + return { + "name": instance_name.text, + "description": instance_description.text, + "max_connected_users": int(instance_max_users.value), + "privacy": session_privacy, + } \ No newline at end of file From b8f293d538353327ca7688cce1ffd5e2272e5ac8 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 14 Apr 2026 18:42:02 -0500 Subject: [PATCH 15/27] Session delisting. Remove sessions from the session server. --- src/scenes/managers/app/network.gd | 71 +++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 4080d2b..f7347b8 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -105,24 +105,32 @@ func update_server(id: String, server_info: Dictionary): # Update the database entry. # Emit server updated event to the server. # If server is now public, and advertising is enabled, advertise to the session-server(s). - # TODO: Get session_servers from client config - # TODO: Get enabled session_servers from server config - # TODO: For each enabled session_server: - # TODO: Check if we are already advertising this server, then submit a generic update to all session servers. - if _database.heartbeats.has(id): - GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) - await _update_session(server_info, "http://localhost:40500") - return - - var advertise_response = await _advertise_session(server_info, "http://localhost:40500") - - # TODO: Get list of successful session advertisements, and start heartbeats. - if advertise_response.ok == true: - # TODO: Create a helper to manage the session IDs, or database by itself? - _database.sessions_id.set(server_info.id, advertise_response.data.id) - _create_heartbeat_timer(server_info.id) + if server_info.privacy > Enum.PrivacyLevel.INVITE: + # TODO: Get session_servers from client config + # TODO: Get enabled session_servers from server config + # TODO: For each enabled session_server: + # TODO: Check if we are already advertising this server, then submit a generic update to all session servers. + if _database.heartbeats.has(id): + GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) + await _update_session(server_info, "http://localhost:40500") + else: + var advertise_response = await _advertise_session(server_info, "http://localhost:40500") + + # TODO: Get list of successful session advertisements, and start heartbeats. + if advertise_response.ok == true: + # TODO: Create a helper to manage the session IDs, or database by itself? + _database.sessions_id.set(server_info.id, advertise_response.data.id) + _create_heartbeat_timer(server_info.id) + + if server_info.privacy == Enum.PrivacyLevel.INVITE: + if _database.heartbeats.has(id): + GlobalLogger.logs("Destroying session heartbeat for '%s'" % id) + _database.heartbeats.erase(id) + + _remove_session_from_server(id, "http://localhost:40500") + # Kill any heartbeats + # Send removal request to session server(s) return - # If the server is now private, kill the heartbeat and submit a request to review the session from the session server func join_server(ip: String, port: int): # Create multiplayer peer. @@ -187,6 +195,35 @@ func _update_session(session_info: Dictionary, session_server: String) -> Dictio return response_dict +func _remove_session_from_server(server_id: String, session_server: String) -> Dictionary: + var response_dict = {"ok": false, "error": null, "data": {}} + + GlobalLogger.logs("Removing session '%s' to the server '%s'" % [server_id, session_server]) + var url = UrlParser.deconstruct("%s/api/v1/deleteSession" % session_server) + # TODO: Error checking + url = url.data + + var _body = { + "id": _database.sessions_id.get(server_id), + } + + var _removal_response = await http.req( + HTTPClient.Method.METHOD_DELETE, + url.host, + url.path, + url.port, + ["Accept: application/json", "Content-Type: application/json", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key], + JSON.stringify(_body) + ) + + if _removal_response.ok: + response_dict.ok = true + response_dict.data = JSON.parse_string(_removal_response.body) + return response_dict + + response_dict.error = _removal_response.error + return response_dict + func _find_available_port(target_port: int = 20205) -> int: GlobalLogger.logs("Trying to find an available port starting at '%s'." % target_port) var _found_port = null From 923da57039fd2c126797e9dd36d5e8abd0017b65 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 11:15:55 -0500 Subject: [PATCH 16/27] Settings placeholder. List session servers. --- src/project.godot | 1 + src/scripts/enum.gd | 12 +- src/scripts/managers/settings.gd | 80 ++++++++++++ src/scripts/managers/settings.gd.uid | 1 + src/scripts/utils/files.gd | 11 +- src/userinterface/dash/hud.tscn | 183 +++++++++++++++++++++++++++ src/userinterface/dash/settings.gd | 31 ++++- 7 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 src/scripts/managers/settings.gd create mode 100644 src/scripts/managers/settings.gd.uid diff --git a/src/project.godot b/src/project.godot index 40ad193..498faea 100644 --- a/src/project.godot +++ b/src/project.godot @@ -27,6 +27,7 @@ boot_splash/minimum_display_time=1000 LaunchArguments="*uid://c45jrfmrjtnyn" GlobalLogger="*uid://dgmfafi41y1nk" FileManager="*uid://d2s50p717g3n" +SettingsManager="*uid://bj4giyk05v042" AccountServers="*uid://bpysjoq7n0ytu" Random="*uid://1js68qt8w0mv" GlobalAccount="*uid://dtlb70kxvbtvn" diff --git a/src/scripts/enum.gd b/src/scripts/enum.gd index 4cb3cd8..5d71e53 100644 --- a/src/scripts/enum.gd +++ b/src/scripts/enum.gd @@ -1,4 +1,4 @@ -extends Control +extends Object enum BaseLevel { DEBUG = 0, @@ -13,4 +13,14 @@ enum PrivacyLevel { CONTACTS = 3, FRIENDS_PLUS = 4, FRIENDS = 5 +} + +const Settings = { + Graphics = { + DisplayMode = { + FULLSCREEN = 0, + WINDOWED = 1, + BORDERLESS = 2 + } + } } \ No newline at end of file diff --git a/src/scripts/managers/settings.gd b/src/scripts/managers/settings.gd new file mode 100644 index 0000000..44e7c59 --- /dev/null +++ b/src/scripts/managers/settings.gd @@ -0,0 +1,80 @@ +# --- License +# File: /client/src/scripts/managers/settings.gd +# Project: OpenMinerva +# Created Date: 16 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +var _settings = {} + +func _ready(): + _load_settings() + +# Settings versioning +# Settings file upgrade + +# Get setting +func get_session_servers() -> Array: + var return_arr = [] + + return_arr = _settings.get("config", {}).get("session_servers", []) + + return return_arr + +func add_session_server(session_server: Dictionary) -> bool: + var _name = session_server.get("name", "") + var _url = session_server.get("url", "") + var _date_added = Time.get_unix_time_from_system() + + _settings.config.session_servers.append({"name": _name, "url": _url, "date_added": _date_added}) + _save_settings() + return true + +func _save_settings(): + var _file = FileAccess.open("user://settings/current.json", FileAccess.WRITE) + var _settings_string: String = JSON.stringify(_settings) + _file.store_string(_settings_string) + _file.close() + GlobalLogger.logs("Saved settings file.", 1) + return + +func _load_settings() -> void: + var _settings_exist: bool = FileAccess.file_exists("user://settings/current.json") + if _settings_exist: + var _file = FileAccess.open("user://settings/current.json", FileAccess.READ) + var _content = _file.get_as_text() + var _parsed = JSON.parse_string(_content) + _settings = _parsed + GlobalLogger.logs("Settings have been loaded.", 1) + return + + # TODO: Check if backup settings exist. + GlobalLogger.logs("Settings file does not exist, creating new settings file.", 1) + FileManager.create_file("user://settings/", "current.json") + _settings = _templates.settings_file + _save_settings() + GlobalLogger.logs("Blank settings have been loaded.", 1) + return + +const _templates = { + # The full settings file that is saved and stored. + "settings_file": { + "graphics": { + "display_mode": Enum.Settings.Graphics.DisplayMode.FULLSCREEN + }, + "config": { + "session_servers": [] + } + }, + + # Small templates that are duplicated and used to + "session_server": { + "name": "", + "url": "", + "date_added": int(0) + } +} diff --git a/src/scripts/managers/settings.gd.uid b/src/scripts/managers/settings.gd.uid new file mode 100644 index 0000000..ab6ac06 --- /dev/null +++ b/src/scripts/managers/settings.gd.uid @@ -0,0 +1 @@ +uid://bj4giyk05v042 diff --git a/src/scripts/utils/files.gd b/src/scripts/utils/files.gd index 963a26d..e409c4a 100644 --- a/src/scripts/utils/files.gd +++ b/src/scripts/utils/files.gd @@ -14,11 +14,12 @@ func create_log_file() -> String: GlobalLogger.logs("Log file '%s' created." % log_file_path, 0) return log_file_path -func create_client_file(_dir: String) -> void: - # Create a file to store in-game user data. This is for in-game data storage! - # IMPORTANT: DO NOT STORE PRIVATE DATA IN THIS DIRECTORY AS IT IS INTENDED TO BE READ AND WRITTEN TO FREELY! - GlobalLogger.logs("Not implemented.", 3) - return +func create_file(dir: String, name: String) -> void: + _maybe_make_directory(dir) + # TODO: Sanitize name + var file = FileAccess.open("%s/%s" % [dir, name], FileAccess.WRITE) + GlobalLogger.logs("File '%s' created at '%s'." % [name, dir], 0) + file.close() func _maybe_make_directory(dir: String): var dir_access = DirAccess.open("user://") diff --git a/src/userinterface/dash/hud.tscn b/src/userinterface/dash/hud.tscn index a272649..ccf6038 100644 --- a/src/userinterface/dash/hud.tscn +++ b/src/userinterface/dash/hud.tscn @@ -26,6 +26,7 @@ [ext_resource type="Script" uid="uid://dpo2x4ddv3ftt" path="res://userinterface/dash/instance.gd" id="14_3ff87"] [ext_resource type="Script" uid="uid://crlujtfv2bh1w" path="res://userinterface/dash/contacts.gd" id="15_gll50"] [ext_resource type="Script" uid="uid://e1djdo6st2a7" path="res://userinterface/dash/exit.gd" id="17_3rk53"] +[ext_resource type="Script" uid="uid://cq26hbvdqb4sf" path="res://userinterface/dash/settings.gd" id="17_shwvw"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kgckq"] draw_center = false @@ -810,6 +811,7 @@ mouse_default_cursor_shape = 2 theme_override_styles/normal = SubResource("StyleBoxFlat_kgckq") [node name="Instance" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=919985343] +visible = false layout_mode = 0 anchor_right = 1.0 anchor_bottom = 1.0 @@ -2046,6 +2048,187 @@ layout_mode = 2 text = "Block Avatar" horizontal_alignment = 1 +[node name="Settings" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=362656680] +layout_mode = 0 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 +script = ExtResource("17_shwvw") + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings" unique_id=1829621060] +layout_mode = 2 + +[node name="Nav" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer" unique_id=954760906] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 + +[node name="General" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=985920354] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "General" + +[node name="Display" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=1395666760] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Display" + +[node name="Audio" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=38839733] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Audio" + +[node name="Controls" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=875250441] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Controls" + +[node name="Interface" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=540067912] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Interface" + +[node name="Network" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=1675516200] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Network" + +[node name="Config" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=1660210073] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Config" + +[node name="Security" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=1489360741] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Security" + +[node name="Misc" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=1234025146] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Misc" + +[node name="Advanced" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Nav" unique_id=378374610] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 +text = "Advanced" + +[node name="Container" type="Control" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer" unique_id=444043284] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="General" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container" unique_id=351943332] +visible = false +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 + +[node name="Config" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container" unique_id=661151710] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 + +[node name="SessionServers" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config" unique_id=749605939] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers" unique_id=1413203981] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer" unique_id=1294718765] +layout_mode = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/MarginContainer" unique_id=510822724] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/MarginContainer/HBoxContainer" unique_id=1248908459] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 20 +text = "Session Servers" + +[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/MarginContainer/HBoxContainer" unique_id=2141833094] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Show List" + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer" unique_id=1524295507] +layout_mode = 2 + +[node name="MarginContainer2" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer" unique_id=1107486151] +custom_minimum_size = Vector2(0, 200) +layout_mode = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2" unique_id=1281162897] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/ScrollContainer" unique_id=1146094050] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/ScrollContainer/MarginContainer" unique_id=1455078587] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Description" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer" unique_id=1192735413] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Description" unique_id=1672934352] +layout_mode = 2 +size_flags_vertical = 1 +theme_override_font_sizes/font_size = 20 +text = "This is some top text, next we may be able to afford some bottom text. For now we will just be able to afford some medium text." +autowrap_mode = 3 + +[node name="Templates" type="Control" parent="MarginContainer/VBoxContainer/Master/Settings" unique_id=717770235] +visible = false +layout_mode = 2 + +[node name="SessionServerListing" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/Templates" unique_id=877735623] +layout_mode = 0 +offset_left = 319.0 +offset_top = 57.0 +offset_right = 1553.0 +offset_bottom = 80.0 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Settings/Templates/SessionServerListing" unique_id=22006714] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0.73333335, 0.73333335, 0.73333335, 1) +text = "https://servers.openmierva.org" + +[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/Templates/SessionServerListing" unique_id=725542881] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +text = "X" + [node name="Exit" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=719734468] visible = false layout_mode = 0 diff --git a/src/userinterface/dash/settings.gd b/src/userinterface/dash/settings.gd index 49aad29..062a537 100644 --- a/src/userinterface/dash/settings.gd +++ b/src/userinterface/dash/settings.gd @@ -7,4 +7,33 @@ # Authors: Armored Dragon # --- License -extends Control \ No newline at end of file +extends Control + +@onready var _templates_session_server_listing = get_node("Templates/SessionServerListing") +@onready var _session_server_container = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/ScrollContainer/MarginContainer/VBoxContainer") + +func _ready(): + _load_session_servers() + + Events.dash_switch_tab.connect(_handle_page_opened) + return + +func _handle_page_opened(page_name) -> void: + if page_name != "Settings": + return + + _load_session_servers() + return + +func _load_session_servers() -> void: + GlobalLogger.logs("Loading session servers.") + var _servers = SettingsManager.get_session_servers() + + for _existing_listing in _session_server_container.get_children(): + _existing_listing.queue_free() + + for server in _servers: + var _template = _templates_session_server_listing.duplicate() + _template.get_node("Label").text = server.url + _session_server_container.add_child(_template) + return \ No newline at end of file From 3c974795ffb0acb6189da7845adc7f1248db5a39 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 11:34:06 -0500 Subject: [PATCH 17/27] Remove session server from config. --- src/scripts/managers/settings.gd | 13 +++++++++++++ src/userinterface/dash/hud.tscn | 2 +- src/userinterface/dash/settings.gd | 7 ++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/scripts/managers/settings.gd b/src/scripts/managers/settings.gd index 44e7c59..f8e9f73 100644 --- a/src/scripts/managers/settings.gd +++ b/src/scripts/managers/settings.gd @@ -34,6 +34,19 @@ func add_session_server(session_server: Dictionary) -> bool: _save_settings() return true +func remove_session_server(url: String) -> bool: + var _index = -1 + for i in range(_settings.config.session_servers.size()): + if _settings.config.session_servers[i]["url"] == url: + _index = i + break + + if _index != -1: + _settings.config.session_servers.remove_at(_index) + + _save_settings() + return false + func _save_settings(): var _file = FileAccess.open("user://settings/current.json", FileAccess.WRITE) var _settings_string: String = JSON.stringify(_settings) diff --git a/src/userinterface/dash/hud.tscn b/src/userinterface/dash/hud.tscn index ccf6038..b2dcf7d 100644 --- a/src/userinterface/dash/hud.tscn +++ b/src/userinterface/dash/hud.tscn @@ -2224,7 +2224,7 @@ size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.73333335, 0.73333335, 0.73333335, 1) text = "https://servers.openmierva.org" -[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/Templates/SessionServerListing" unique_id=725542881] +[node name="Remove" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/Templates/SessionServerListing" unique_id=725542881] custom_minimum_size = Vector2(100, 0) layout_mode = 2 text = "X" diff --git a/src/userinterface/dash/settings.gd b/src/userinterface/dash/settings.gd index 062a537..94e1046 100644 --- a/src/userinterface/dash/settings.gd +++ b/src/userinterface/dash/settings.gd @@ -35,5 +35,10 @@ func _load_session_servers() -> void: for server in _servers: var _template = _templates_session_server_listing.duplicate() _template.get_node("Label").text = server.url + _template.get_node("Remove").pressed.connect(_remove_session_server.bind(server.url)) _session_server_container.add_child(_template) - return \ No newline at end of file + return + +func _remove_session_server(url: String) -> void: + SettingsManager.remove_session_server(url) + _load_session_servers() \ No newline at end of file From 83be1139ab21cd1ea0b968e2f59e342ecece683f Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 11:52:38 -0500 Subject: [PATCH 18/27] Add session servers. --- src/userinterface/dash/hud.tscn | 32 +++++++++++++++++++++++++++--- src/userinterface/dash/settings.gd | 21 ++++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/userinterface/dash/hud.tscn b/src/userinterface/dash/hud.tscn index b2dcf7d..151d2db 100644 --- a/src/userinterface/dash/hud.tscn +++ b/src/userinterface/dash/hud.tscn @@ -2178,10 +2178,36 @@ theme_override_constants/margin_top = 5 theme_override_constants/margin_right = 5 theme_override_constants/margin_bottom = 5 -[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2" unique_id=1281162897] +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2" unique_id=90574433] layout_mode = 2 -[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/ScrollContainer" unique_id=1146094050] +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer" unique_id=1903245076] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer" unique_id=1621920772] +layout_mode = 2 +text = "Add Session Server" + +[node name="Name" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer" unique_id=351217801] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Friendly Name" + +[node name="URL" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer" unique_id=512184669] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "URL" + +[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer" unique_id=475127911] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +text = "Add" + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer" unique_id=1281162897] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/ScrollContainer" unique_id=1146094050] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 @@ -2190,7 +2216,7 @@ theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 -[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/ScrollContainer/MarginContainer" unique_id=1455078587] +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/ScrollContainer/MarginContainer" unique_id=1455078587] layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 diff --git a/src/userinterface/dash/settings.gd b/src/userinterface/dash/settings.gd index 94e1046..13e7eea 100644 --- a/src/userinterface/dash/settings.gd +++ b/src/userinterface/dash/settings.gd @@ -10,11 +10,17 @@ extends Control @onready var _templates_session_server_listing = get_node("Templates/SessionServerListing") -@onready var _session_server_container = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/ScrollContainer/MarginContainer/VBoxContainer") +@onready var _session_server_container = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer") + +@onready var _add_session_server_btn = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer/Button") +@onready var _add_session_server_name = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer/Name") +@onready var _add_session_server_url = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer/URL") func _ready(): _load_session_servers() + _add_session_server_btn.pressed.connect(_add_session_server) + Events.dash_switch_tab.connect(_handle_page_opened) return @@ -41,4 +47,15 @@ func _load_session_servers() -> void: func _remove_session_server(url: String) -> void: SettingsManager.remove_session_server(url) - _load_session_servers() \ No newline at end of file + _load_session_servers() + +func _add_session_server() -> void: + var _name = _add_session_server_name.text + var _url = _add_session_server_url.text + + SettingsManager.add_session_server({"name": _name, "url": _url}) + + _add_session_server_name.text = "" + _add_session_server_url.text = "" + _load_session_servers() + return From 4c55963daa42f7893cecdcdeb6954536f6033304 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 11:55:15 -0500 Subject: [PATCH 19/27] Don't add empty session servers. --- src/userinterface/dash/settings.gd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/userinterface/dash/settings.gd b/src/userinterface/dash/settings.gd index 13e7eea..ce866ab 100644 --- a/src/userinterface/dash/settings.gd +++ b/src/userinterface/dash/settings.gd @@ -53,6 +53,12 @@ func _add_session_server() -> void: var _name = _add_session_server_name.text var _url = _add_session_server_url.text + if _name == "": + return + + if _url == "": + return + SettingsManager.add_session_server({"name": _name, "url": _url}) _add_session_server_name.text = "" From beca15eb8db051f79c21ad802abe0e4e47165785 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 12:01:27 -0500 Subject: [PATCH 20/27] Hide session server dialog in settings. --- src/userinterface/dash/hud.tscn | 1 + src/userinterface/dash/settings.gd | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/userinterface/dash/hud.tscn b/src/userinterface/dash/hud.tscn index 151d2db..9f4be93 100644 --- a/src/userinterface/dash/hud.tscn +++ b/src/userinterface/dash/hud.tscn @@ -2168,6 +2168,7 @@ layout_mode = 2 text = "Show List" [node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer" unique_id=1524295507] +visible = false layout_mode = 2 [node name="MarginContainer2" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer" unique_id=1107486151] diff --git a/src/userinterface/dash/settings.gd b/src/userinterface/dash/settings.gd index ce866ab..79d6583 100644 --- a/src/userinterface/dash/settings.gd +++ b/src/userinterface/dash/settings.gd @@ -12,6 +12,8 @@ extends Control @onready var _templates_session_server_listing = get_node("Templates/SessionServerListing") @onready var _session_server_container = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/ScrollContainer/MarginContainer/VBoxContainer") +@onready var _show_session_server_info_btn = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/MarginContainer/HBoxContainer/Button") +@onready var _session_server_info = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer") @onready var _add_session_server_btn = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer/Button") @onready var _add_session_server_name = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer/Name") @onready var _add_session_server_url = get_node("HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2/VBoxContainer/HBoxContainer/URL") @@ -19,6 +21,7 @@ extends Control func _ready(): _load_session_servers() + _show_session_server_info_btn.pressed.connect(_show_session_server_info_dialog) _add_session_server_btn.pressed.connect(_add_session_server) Events.dash_switch_tab.connect(_handle_page_opened) @@ -65,3 +68,7 @@ func _add_session_server() -> void: _add_session_server_url.text = "" _load_session_servers() return + +func _show_session_server_info_dialog() -> void: + _session_server_info.visible = !_session_server_info.visible + return From c791da89bbc3d0561128ccdf30b632dd0498bb9b Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 12:09:15 -0500 Subject: [PATCH 21/27] Use session server list to advertise sessions to. --- src/scenes/managers/app/network.gd | 34 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index f7347b8..e3a6816 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -104,23 +104,29 @@ func update_server(id: String, server_info: Dictionary): # Validate server updated data. # Update the database entry. # Emit server updated event to the server. - # If server is now public, and advertising is enabled, advertise to the session-server(s). if server_info.privacy > Enum.PrivacyLevel.INVITE: - # TODO: Get session_servers from client config + # TODO: Load the session server list into the instance settings dialog. + # TODO: When updating the server, pass the valid session_server array in the settings. + # For now, all servers installed are going to be advertised to. + var _saved_session_servers = SettingsManager.get_session_servers() + # TODO: Get enabled session_servers from server config + # var _session_servers_to_advertise_to = null + # TODO: For each enabled session_server: + for _server in _saved_session_servers: # TODO: Check if we are already advertising this server, then submit a generic update to all session servers. - if _database.heartbeats.has(id): - GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) - await _update_session(server_info, "http://localhost:40500") - else: - var advertise_response = await _advertise_session(server_info, "http://localhost:40500") - - # TODO: Get list of successful session advertisements, and start heartbeats. - if advertise_response.ok == true: - # TODO: Create a helper to manage the session IDs, or database by itself? - _database.sessions_id.set(server_info.id, advertise_response.data.id) - _create_heartbeat_timer(server_info.id) + if _database.heartbeats.has(id): + GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) + await _update_session(server_info, _server.url) + else: + var advertise_response = await _advertise_session(server_info, _server.url) + + # TODO: Get list of successful session advertisements, and start heartbeats. + if advertise_response.ok == true: + # TODO: Create a helper to manage the session IDs, or database by itself? + _database.sessions_id.set(server_info.id, advertise_response.data.id) + _create_heartbeat_timer(server_info.id) if server_info.privacy == Enum.PrivacyLevel.INVITE: if _database.heartbeats.has(id): @@ -128,8 +134,6 @@ func update_server(id: String, server_info: Dictionary): _database.heartbeats.erase(id) _remove_session_from_server(id, "http://localhost:40500") - # Kill any heartbeats - # Send removal request to session server(s) return func join_server(ip: String, port: int): From d68fd6ecd3f22d86354140735e2fff47c8585a35 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 12:14:59 -0500 Subject: [PATCH 22/27] Error fixes. Makes the debugger not angry with me. --- src/scenes/managers/app/network.gd | 13 +++++++------ src/scenes/managers/app/network.tscn | 2 +- src/scenes/managers/app/rpc.tscn | 2 +- src/scenes/managers/app/scene.gd | 4 ++-- src/scenes/managers/app/scene.tscn | 2 +- src/scenes/master.tscn | 6 ------ src/scripts/enum.gd | 11 ++++++++++- src/scripts/libs/account.gd | 2 +- src/scripts/signal_bus.gd | 10 ++++++++++ src/scripts/utils/files.gd | 6 +++--- src/userinterface/dash/account_create.gd | 2 +- src/userinterface/dash/instance.gd | 7 ++----- src/userinterface/session_query.gd | 4 ++-- 13 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index e3a6816..a89bc54 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -91,7 +91,7 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI return response_dict -func stop_server(id: String): +func stop_server(_id: String): # Kick all players (Server closing). # Turn off all join requests. # Destroy multiplayer api. @@ -136,7 +136,8 @@ func update_server(id: String, server_info: Dictionary): _remove_session_from_server(id, "http://localhost:40500") return -func join_server(ip: String, port: int): +func join_server(_ip: String, _port: int): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # Create multiplayer peer. # Establish connection. # Host handles everything after this with the managers? @@ -150,19 +151,19 @@ func leave_server(): return func kick_player(): - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) return func ban_player(): - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) return func on_kicked(): - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) return func on_banned(): - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["name"]) + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) return func get_connected_sessions(): diff --git a/src/scenes/managers/app/network.tscn b/src/scenes/managers/app/network.tscn index f7ae244..6344fef 100644 --- a/src/scenes/managers/app/network.tscn +++ b/src/scenes/managers/app/network.tscn @@ -1,6 +1,6 @@ [gd_scene format=3 uid="uid://5v8rbnp716b0"] -[ext_resource type="Script" uid="uid://bh271sucj14cq" path="res://scenes/managers/app/network_manaager.gd" id="1_daels"] +[ext_resource type="Script" uid="uid://r51flk0bjypx" path="res://scenes/managers/app/network.gd" id="1_daels"] [node name="NetworkManager" type="Node"] script = ExtResource("1_daels") diff --git a/src/scenes/managers/app/rpc.tscn b/src/scenes/managers/app/rpc.tscn index 30b4fb2..a027a14 100644 --- a/src/scenes/managers/app/rpc.tscn +++ b/src/scenes/managers/app/rpc.tscn @@ -1,6 +1,6 @@ [gd_scene format=3 uid="uid://by0vghgshvbhd"] -[ext_resource type="Script" uid="uid://x23lqbiivx1q" path="res://scenes/managers/app/rpc_manager_old.gd" id="1_6timl"] +[ext_resource type="Script" uid="uid://bsyo854lkhbf2" path="res://scenes/managers/app/rpc.gd" id="1_6timl"] [node name="RpcManager" type="Node" unique_id=1836544617] script = ExtResource("1_6timl") diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index f22e700..2e92f70 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -32,7 +32,7 @@ func get_master_scene(id: String) -> Node3D: var _scene = scene_container.get_node(id) return _scene -func destroy_master_scene(id: String): +func destroy_master_scene(_id: String): GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # get_master_scene # Check if currently being used by a server. @@ -65,7 +65,7 @@ func get_master_root(id: String) -> Node3D: var _root = _scene.get_node_or_null("root") return _root -func set_master_root_from_inventory(id: String, scene_type: Enum.BaseLevel) -> bool: +func set_master_root_from_inventory(_id: String, _scene_type: Enum.BaseLevel) -> bool: GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # get_master_scene # Find scene from inventory. diff --git a/src/scenes/managers/app/scene.tscn b/src/scenes/managers/app/scene.tscn index 0ea8240..d382583 100644 --- a/src/scenes/managers/app/scene.tscn +++ b/src/scenes/managers/app/scene.tscn @@ -1,6 +1,6 @@ [gd_scene format=3 uid="uid://cmknpdx5ba15o"] -[ext_resource type="Script" uid="uid://bwe2gsnip66cr" path="res://scenes/managers/app/scene_manager_old.gd" id="1_i8qdd"] +[ext_resource type="Script" uid="uid://3ylijxenr1bg" path="res://scenes/managers/app/scene.gd" id="1_i8qdd"] [node name="SceneManager" type="Node"] script = ExtResource("1_i8qdd") diff --git a/src/scenes/master.tscn b/src/scenes/master.tscn index 71d39c8..ed3221b 100644 --- a/src/scenes/master.tscn +++ b/src/scenes/master.tscn @@ -3,21 +3,15 @@ [ext_resource type="PackedScene" uid="uid://by0vghgshvbhd" path="res://scenes/managers/app/rpc.tscn" id="1_h2qy3"] [ext_resource type="PackedScene" uid="uid://5v8rbnp716b0" path="res://scenes/managers/app/network.tscn" id="1_jooxx"] [ext_resource type="PackedScene" uid="uid://cmknpdx5ba15o" path="res://scenes/managers/app/scene.tscn" id="2_h2qy3"] -[ext_resource type="Script" uid="uid://bsyo854lkhbf2" path="res://scenes/managers/app/rpc.gd" id="2_kjytq"] -[ext_resource type="Script" uid="uid://r51flk0bjypx" path="res://scenes/managers/app/network.gd" id="4_56xkn"] -[ext_resource type="Script" uid="uid://3ylijxenr1bg" path="res://scenes/managers/app/scene.gd" id="4_q3f5g"] [ext_resource type="PackedScene" uid="uid://ckl5gw0xbduiv" path="res://userinterface/dash/hud.tscn" id="5_q3f5g"] [node name="Master" type="Node3D" unique_id=420526444] [node name="RpcManager" parent="." unique_id=1836544617 instance=ExtResource("1_h2qy3")] -script = ExtResource("2_kjytq") [node name="NetworkManager" parent="." unique_id=1960146969 instance=ExtResource("1_jooxx")] -script = ExtResource("4_56xkn") [node name="SceneManager" parent="." unique_id=5477810 instance=ExtResource("2_h2qy3")] -script = ExtResource("4_q3f5g") [node name="Hud" parent="." unique_id=1053137144 instance=ExtResource("5_q3f5g")] visible = false diff --git a/src/scripts/enum.gd b/src/scripts/enum.gd index 5d71e53..76ff4ff 100644 --- a/src/scripts/enum.gd +++ b/src/scripts/enum.gd @@ -1,4 +1,13 @@ -extends Object +# --- License +# File: /client/src/scripts/enum.gd +# Project: OpenMinerva +# Created Date: 13 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node enum BaseLevel { DEBUG = 0, diff --git a/src/scripts/libs/account.gd b/src/scripts/libs/account.gd index cd1fa7a..08df77e 100644 --- a/src/scripts/libs/account.gd +++ b/src/scripts/libs/account.gd @@ -115,7 +115,7 @@ func update(id: String, data: Dictionary) -> void: _save_account_database() return -func authenticate_oauth(id: String, remember_me: bool = false) -> void: +func authenticate_oauth(id: String, _remember_me: bool = false) -> void: # TODO: Error checks GlobalLogger.logs("Attempting to connect account '%s' using oauth." % id) var account = _get_account_by_id(id) diff --git a/src/scripts/signal_bus.gd b/src/scripts/signal_bus.gd index b97b793..56fbe44 100644 --- a/src/scripts/signal_bus.gd +++ b/src/scripts/signal_bus.gd @@ -8,14 +8,24 @@ # --- License extends Node + # Dashboard +@warning_ignore("unused_signal") signal dash_set_state(is_open: bool) +@warning_ignore("unused_signal") signal dash_switch_tab(page_name: String) +@warning_ignore("unused_signal") signal dash_active_account_changed(account: Dictionary) +@warning_ignore("unused_signal") signal dash_storage_changed(storage_data: Dictionary) +@warning_ignore("unused_signal") signal dash_session_changed(session_data: Dictionary) +@warning_ignore("unused_signal") signal dash_message_received(message: Dictionary) +@warning_ignore("unused_signal") signal dash_notification(notification: Dictionary) +@warning_ignore("unused_signal") signal dash_account_list_loaded(account_list: PackedStringArray) +@warning_ignore("unused_signal") signal instance_updated(instance: Dictionary) \ No newline at end of file diff --git a/src/scripts/utils/files.gd b/src/scripts/utils/files.gd index e409c4a..535c95a 100644 --- a/src/scripts/utils/files.gd +++ b/src/scripts/utils/files.gd @@ -14,11 +14,11 @@ func create_log_file() -> String: GlobalLogger.logs("Log file '%s' created." % log_file_path, 0) return log_file_path -func create_file(dir: String, name: String) -> void: +func create_file(dir: String, file_name: String) -> void: _maybe_make_directory(dir) # TODO: Sanitize name - var file = FileAccess.open("%s/%s" % [dir, name], FileAccess.WRITE) - GlobalLogger.logs("File '%s' created at '%s'." % [name, dir], 0) + var file = FileAccess.open("%s/%s" % [dir, file_name], FileAccess.WRITE) + GlobalLogger.logs("File '%s' created at '%s'." % [file_name, dir], 0) file.close() func _maybe_make_directory(dir: String): diff --git a/src/userinterface/dash/account_create.gd b/src/userinterface/dash/account_create.gd index 19d6233..238083f 100644 --- a/src/userinterface/dash/account_create.gd +++ b/src/userinterface/dash/account_create.gd @@ -53,7 +53,7 @@ func _create_oauth() -> void: "account_server": account_server } - var res = GlobalAccount.create(account, "oauth") + GlobalAccount.create(account, "oauth") Events.emit_signal("dash_switch_tab", "AccountDisplay") diff --git a/src/userinterface/dash/instance.gd b/src/userinterface/dash/instance.gd index 44e9ff7..0396a2c 100644 --- a/src/userinterface/dash/instance.gd +++ b/src/userinterface/dash/instance.gd @@ -73,12 +73,9 @@ func _privacy_button_enable(node) -> void: node.custom_minimum_size = Vector2(0, 50) return -func update_instance(instance: Dictionary) -> void: +func update_instance(_instance: Dictionary) -> void: + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # TODO Session Permissions: Admins can change instance settings. - if !is_multiplayer_authority(): - return - - GlobalLogger.logs("Updating session.") # Publish changes to the session server. # Update running server return diff --git a/src/userinterface/session_query.gd b/src/userinterface/session_query.gd index 814eb87..1ef8340 100644 --- a/src/userinterface/session_query.gd +++ b/src/userinterface/session_query.gd @@ -69,7 +69,7 @@ func get_sessions() -> Array: return _return_arr -func _session_request_received(host: String, response: Dictionary) -> Dictionary: +func _session_request_received(_host: String, response: Dictionary) -> Dictionary: var _return_arr = {"ok": false, "error": "", "data": null} # TODO: If response.ok @@ -82,7 +82,7 @@ func _session_request_received(host: String, response: Dictionary) -> Dictionary return _return_arr -func _authentication_request_received(host: String, response: Dictionary) -> Dictionary: +func _authentication_request_received(_host: String, response: Dictionary) -> Dictionary: var _return_arr = {"ok": false, "error": "", "data": null} # TODO: If response.ok From 2820ab107df4cbd289c23677bfe105c48de9f073 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 12:54:14 -0500 Subject: [PATCH 23/27] Properly destroy master scene when creating a server failed. --- src/scenes/managers/app/network.gd | 7 +++++-- src/scenes/managers/app/scene.gd | 14 ++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index a89bc54..c893654 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -74,7 +74,11 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI if _create_server_response != OK: GlobalLogger.logs("Failed to start server. Error: '%s'" % _create_server_response, 1) response_dict.error = str(_create_server_response) - # TODO: Destroy server scene + + _database.sessions_api.erase(_scene) + _database.sessions.erase(_scene) + + scene_m.destroy_master_scene(_scene) return response_dict # Create server root scene. @@ -86,7 +90,6 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI scene_m.start_master_scene(_scene) # DEV: Force spawn the host. - scene_m.get_master_scene(_scene).get_node("PlayerManager").spawn_player(1) return response_dict diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index 2e92f70..246aa6d 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -32,12 +32,14 @@ func get_master_scene(id: String) -> Node3D: var _scene = scene_container.get_node(id) return _scene -func destroy_master_scene(_id: String): - GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) - # get_master_scene - # Check if currently being used by a server. - # Check if being used as client to server. - # Queue free. +func destroy_master_scene(id: String): + var _scene = scene_container.get_node_or_null(id) + + if _scene == null: + GlobalLogger.logs("'%s' does not exist, could not delete." % id, 2) + return + + _scene.queue_free() return func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> void: From b1a380ab2cff9b2fd981683839288e04d512c4e0 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 13:01:37 -0500 Subject: [PATCH 24/27] Error handling. Removed some out of date TODOs. --- src/scenes/managers/app/network.gd | 45 +++++++++++++++++++----------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index c893654..83f61f7 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -95,6 +95,7 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI return response_dict func stop_server(_id: String): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # Kick all players (Server closing). # Turn off all join requests. # Destroy multiplayer api. @@ -108,26 +109,20 @@ func update_server(id: String, server_info: Dictionary): # Update the database entry. # Emit server updated event to the server. if server_info.privacy > Enum.PrivacyLevel.INVITE: - # TODO: Load the session server list into the instance settings dialog. - # TODO: When updating the server, pass the valid session_server array in the settings. + # TODO: Only advertise to specific session servers per instance. + # Load the session server list into the instance settings dialog. + # When updating the server, pass the valid session_server array in the settings. # For now, all servers installed are going to be advertised to. var _saved_session_servers = SettingsManager.get_session_servers() - # TODO: Get enabled session_servers from server config - # var _session_servers_to_advertise_to = null - - # TODO: For each enabled session_server: for _server in _saved_session_servers: - # TODO: Check if we are already advertising this server, then submit a generic update to all session servers. if _database.heartbeats.has(id): GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) - await _update_session(server_info, _server.url) + await _update_session_server_listing(server_info, _server.url) else: var advertise_response = await _advertise_session(server_info, _server.url) - # TODO: Get list of successful session advertisements, and start heartbeats. if advertise_response.ok == true: - # TODO: Create a helper to manage the session IDs, or database by itself? _database.sessions_id.set(server_info.id, advertise_response.data.id) _create_heartbeat_timer(server_info.id) @@ -147,6 +142,7 @@ func join_server(_ip: String, _port: int): return func leave_server(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], 3) # Get server from database. # Send leave packet. # Destroy multiplayer API. @@ -177,12 +173,17 @@ func get_connected_sessions(): return result -func _update_session(session_info: Dictionary, session_server: String) -> Dictionary: +func _update_session_server_listing(session_info: Dictionary, session_server: String) -> Dictionary: var response_dict = {"ok": false, "error": null, "data": null} GlobalLogger.logs("Updating session '%s' to the server '%s'" % [session_info.id, session_server]) var url = UrlParser.deconstruct("%s/api/v1/updateSession" % session_server) - # TODO: Error checking + + if url.ok != true: + GlobalLogger.logs("Failed to deconstruct the URL '%s'. Error: '%s'" % [session_server, url.error]) + response_dict.error = url.error + return response_dict + url = url.data var _body = { @@ -205,10 +206,16 @@ func _update_session(session_info: Dictionary, session_server: String) -> Dictio func _remove_session_from_server(server_id: String, session_server: String) -> Dictionary: var response_dict = {"ok": false, "error": null, "data": {}} + var _full_url = "%s/api/v1/deleteSession" % session_server GlobalLogger.logs("Removing session '%s' to the server '%s'" % [server_id, session_server]) - var url = UrlParser.deconstruct("%s/api/v1/deleteSession" % session_server) - # TODO: Error checking + var url = UrlParser.deconstruct(_full_url) + + if url.ok != true: + GlobalLogger.logs("Failed to deconstruct the URL '%s'. Error: '%s'" % [_full_url, url.error]) + response_dict.error = url.error + return response_dict + url = url.data var _body = { @@ -302,8 +309,14 @@ func _heartbeat_session(session_id: String): func _advertise_session(session_info: Dictionary, session_server: String) -> Dictionary: var response_dict = {"ok": false, "error": null, "data": null} GlobalLogger.logs("Advertising session '%s' to the server '%s'" % [session_info.id, session_server]) - var url = UrlParser.deconstruct("%s/api/v1/postSession" % session_server) - # TODO: Error checking + var _full_url = "%s/api/v1/postSession" % session_server + var url = UrlParser.deconstruct(_full_url) + + if url.ok != true: + GlobalLogger.logs("Failed to deconstruct the URL '%s'. Error: '%s'" % [_full_url, url.error]) + response_dict.error = url.error + return response_dict + url = url.data var _body = { From c540eca5e45aee11bdeab86c5d4089781f3ff86c Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 13:20:35 -0500 Subject: [PATCH 25/27] Stop scene when replacing root. --- src/scenes/managers/app/scene.gd | 31 ++++++++++++++++++++++++----- src/scenes/managers/scene/player.gd | 3 +++ src/scripts/signal_bus.gd | 5 ++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/scenes/managers/app/scene.gd b/src/scenes/managers/app/scene.gd index 246aa6d..6b041af 100644 --- a/src/scenes/managers/app/scene.gd +++ b/src/scenes/managers/app/scene.gd @@ -43,23 +43,29 @@ func destroy_master_scene(id: String): return func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> void: - # TODO: Disable spawning players while changing master root. var _scene = get_master_scene(id) var _root_scene: PackedScene = _get_scene_by_type(scene_type) + var _root_node = get_master_root(id) - # TODO: Validate scene integrity. + # Stop everything + stop_master_scene(id) - var _root_node = get_master_root(id) + # Remove everything _scene.remove_child(_root_node) _root_node.queue_free() - # Replace with new scene. + # Get new scene var _root_scene_node = _root_scene.instantiate() _root_scene_node.name = "root" + + # Add new scene _scene.add_child(_root_scene_node) - # TODO: Emit signal of root changed. + # Start everything + start_master_scene(id) + + Events.emit_signal("instance_root_changed") return func get_master_root(id: String) -> Node3D: @@ -92,6 +98,21 @@ func start_master_scene(id: String): GlobalLogger.logs("Could not start invalid manager '%s' in server '%s'" % [node_name, id], 3) return +func stop_master_scene(id: String): + const MANAGERS = ["PlayerManager", "SignalBus"] + + var _scene = get_master_scene(id) + + for node_name in MANAGERS: + var _scene_manager = _scene.get_node_or_null(node_name) + if _scene_manager: + _scene_manager.active = true + GlobalLogger.logs("'%s' started in server '%s'" % [node_name, id]) + continue + + GlobalLogger.logs("Could not start invalid manager '%s' in server '%s'" % [node_name, id], 3) + return + func _get_scene_by_type(scene_type: Enum.BaseLevel) -> PackedScene: var _scene_dir: String = "" diff --git a/src/scenes/managers/scene/player.gd b/src/scenes/managers/scene/player.gd index 09b0db1..1a7bd4d 100644 --- a/src/scenes/managers/scene/player.gd +++ b/src/scenes/managers/scene/player.gd @@ -12,6 +12,9 @@ extends Node var active: bool = false func spawn_player(peer_id: int) -> void: + if active == false: + return + var _player_scene: PackedScene = load("res://scenes/players/player.tscn") var _new_player: Node3D = _player_scene.instantiate() _new_player.name = str(peer_id) diff --git a/src/scripts/signal_bus.gd b/src/scripts/signal_bus.gd index 56fbe44..9629779 100644 --- a/src/scripts/signal_bus.gd +++ b/src/scripts/signal_bus.gd @@ -27,5 +27,8 @@ signal dash_notification(notification: Dictionary) @warning_ignore("unused_signal") signal dash_account_list_loaded(account_list: PackedStringArray) +# Instance @warning_ignore("unused_signal") -signal instance_updated(instance: Dictionary) \ No newline at end of file +signal instance_updated(instance: Dictionary) +@warning_ignore("unused_signal") +signal instance_root_changed() \ No newline at end of file From 218871f16f16960d0a983c326ae57a5382a779cc Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 13:57:51 -0500 Subject: [PATCH 26/27] Removed hardcoded localhost values used for debugging and development. --- src/scenes/managers/app/network.gd | 38 +++++++++++++++++------------- src/userinterface/dash/sessions.gd | 3 ++- src/userinterface/session_query.gd | 23 +++++++++--------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 83f61f7..8ee604c 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -108,13 +108,13 @@ func update_server(id: String, server_info: Dictionary): # Validate server updated data. # Update the database entry. # Emit server updated event to the server. + var _saved_session_servers = SettingsManager.get_session_servers() + if server_info.privacy > Enum.PrivacyLevel.INVITE: # TODO: Only advertise to specific session servers per instance. # Load the session server list into the instance settings dialog. # When updating the server, pass the valid session_server array in the settings. # For now, all servers installed are going to be advertised to. - var _saved_session_servers = SettingsManager.get_session_servers() - for _server in _saved_session_servers: if _database.heartbeats.has(id): GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) @@ -124,14 +124,15 @@ func update_server(id: String, server_info: Dictionary): if advertise_response.ok == true: _database.sessions_id.set(server_info.id, advertise_response.data.id) - _create_heartbeat_timer(server_info.id) + _create_heartbeat_timer(server_info.id, _server.url) if server_info.privacy == Enum.PrivacyLevel.INVITE: if _database.heartbeats.has(id): GlobalLogger.logs("Destroying session heartbeat for '%s'" % id) _database.heartbeats.erase(id) - _remove_session_from_server(id, "http://localhost:40500") + for _server in _saved_session_servers: + _remove_session_from_server(id, _server.url) return func join_server(_ip: String, _port: int): @@ -266,38 +267,43 @@ func _is_port_in_use(port: int) -> bool: return true -func _create_heartbeat_timer(session_id: String): +func _create_heartbeat_timer(session_id: String, session_server_url: String): GlobalLogger.logs("Creating a heartbeat timer for server '%s'" % session_id) # FIXME: Hardcoded time for timer. var timer = get_tree().create_timer(20) _database.heartbeats[session_id] = timer - timer.timeout.connect(_heartbeat_timer_timeout.bind(session_id)) + timer.timeout.connect(_heartbeat_timer_timeout.bind(session_id, session_server_url)) return -func _heartbeat_timer_timeout(session_id): +func _heartbeat_timer_timeout(session_id: String, session_server_url: String): GlobalLogger.logs("Sending a heartbeat for server '%s'" % session_id) if _database.heartbeats.has(session_id) == false: GlobalLogger.logs("Server '%s' does not exist anymore, not sending a heartbeat." % session_id) return - _heartbeat_session(session_id) + _heartbeat_session(session_id, session_server_url) - _create_heartbeat_timer(session_id) + _create_heartbeat_timer(session_id, session_server_url) return -func _heartbeat_session(session_id: String): - # FIXME: Hardcoded localhost link. - var url_parts = UrlParser.deconstruct("http://localhost:40500/api/v1/heartbeatSession") - url_parts = url_parts.data +func _heartbeat_session(session_id: String, session_server_url: String) -> void: + var _full_url = "%s/api/v1/heartbeatSession" % session_server_url + var _url = UrlParser.deconstruct(_full_url) + + if _url.ok != true: + GlobalLogger.logs("Failed to deconstruct the URL '%s'. Error: '%s'" % [_full_url, _url.error]) + return + + _url = _url.data var body = {"session_id": session_id} var response = await http.req( HTTPClient.Method.METHOD_POST, - url_parts.host, - url_parts.path, - url_parts.port, + _url.host, + _url.path, + _url.port, ["Accept: application/json", "Content-Type: application/json", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key], JSON.stringify(body) ) diff --git a/src/userinterface/dash/sessions.gd b/src/userinterface/dash/sessions.gd index 0729ad3..6c554f1 100644 --- a/src/userinterface/dash/sessions.gd +++ b/src/userinterface/dash/sessions.gd @@ -28,7 +28,8 @@ func _handle_page_opened(page_name: String) -> void: return # TODO: Check if we need to authenticate, if so - await SessionQuery.authenticate() + for _session_server in SettingsManager.get_session_servers(): + await SessionQuery.authenticate(_session_server.url) # Get a list of all sessions from our saved sessions_list var session_list = await SessionQuery.get_sessions() diff --git a/src/userinterface/session_query.gd b/src/userinterface/session_query.gd index 1ef8340..ce97557 100644 --- a/src/userinterface/session_query.gd +++ b/src/userinterface/session_query.gd @@ -11,17 +11,15 @@ extends Node var http = preload("res://scripts/network/http.gd").new() -const SESSION_SERVERS = ["http://localhost:40500/"] +func authenticate(url: String) -> Dictionary: + var _return_dict: Dictionary = {"ok": false, "error": ""} -func authenticate() -> Dictionary: - var _return_arr = {"ok": false, "error": ""} - - var url_deconstructed = UrlParser.deconstruct("http://localhost:40500") + var url_deconstructed = UrlParser.deconstruct(url) if url_deconstructed.ok == false: - const ERROR_MESSAGE = "Failed to parse the session server URL." - GlobalLogger.logs(ERROR_MESSAGE, 1) - _return_arr.error = ERROR_MESSAGE - return _return_arr + var ERROR_MESSAGE = "Failed to deconstruct the url '%s'" % url + GlobalLogger.logs(ERROR_MESSAGE, 2) + _return_dict.error = ERROR_MESSAGE + return _return_dict url_deconstructed = url_deconstructed.data var body: Dictionary = { @@ -40,16 +38,17 @@ func authenticate() -> Dictionary: # Response contains api key, or error - return _return_arr + return _return_dict func get_sessions() -> Array: var _return_arr = [] + var _session_servers = SettingsManager.get_session_servers() - for server in SESSION_SERVERS: + for server in _session_servers: var search = "" var tags = "" - var url_deconstructed = UrlParser.deconstruct(server) + var url_deconstructed = UrlParser.deconstruct(server.url) if url_deconstructed.ok == false: GlobalLogger.logs("Failed to parse the session server URL.", 1) continue From d71959062d28aeac0c1493024613bf387b9297f5 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 16 Apr 2026 14:06:58 -0500 Subject: [PATCH 27/27] Moved TODO to it's own issue. https://github.com/OpenMinerva/client/issues/85 --- src/scenes/managers/app/network.gd | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd index 8ee604c..cfcdeed 100644 --- a/src/scenes/managers/app/network.gd +++ b/src/scenes/managers/app/network.gd @@ -111,10 +111,6 @@ func update_server(id: String, server_info: Dictionary): var _saved_session_servers = SettingsManager.get_session_servers() if server_info.privacy > Enum.PrivacyLevel.INVITE: - # TODO: Only advertise to specific session servers per instance. - # Load the session server list into the instance settings dialog. - # When updating the server, pass the valid session_server array in the settings. - # For now, all servers installed are going to be advertised to. for _server in _saved_session_servers: if _database.heartbeats.has(id): GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id)