diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4760075 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/src/.godot +/src/android/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d43ab76 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/addons/godot-oauth2client"] + path = src/addons/godot-oauth2client + url = https://github.com/OpenMinerva/godot-oauth2client.git +[submodule "src/addons/godot-urlparser"] + path = src/addons/godot-urlparser + url = https://github.com/OpenMinerva/godot-urlparser.git diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..40c3f0c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,53 @@ +{ + "psi-header.config": { + "forceToTop": true, + "blankLinesAfter": 1, + "spacesBetweenYears": false, + "license": "MIT", + "company": "OpenMinerva", + "creationDateZero": "asIs", + }, + "psi-header.lang-config": [ + { + "language": "gdscript", + "begin": "# --- License", + "prefix": "# ", + "lineLength": 80, + "end": "# --- License", + "forceToTop": true, + "blankLinesAfter": 1, + "beforeHeader": [], + "afterHeader": [], + "rootDirFileName": "client", + "modAuthor": "Modified By:", + "modDate": "Last Modified:", + "modDateFormat": "dd/MM/yyyy hh:nn:ss", + "replace": [ + "Filename:", + "Project" + ], + "ignoreLines": [] + } + ], + "psi-header.templates": [ + { + "language": "gdscript", + "template": [ + "File: <>", + "Project: <>", + "Created Date: <>", + "Copyright (c) <> <>", + "License: <>", + "Authors: <>", + ], + "changeLogCaption": "HISTORY", + "changeLogHeaderLineCount": 2, + "changeLogEntryTemplate": [ + "", + "<>\t<>\t" + ], + "changeLogNaturalOrder": false, + "changeLogFooterLineCount": 0 + } + ], +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..77033a9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 OpenMinerva + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index ad739ae..282669f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ -# Prism -VR Social platform with a focus on Education +> [!WARNING] +> This repository is currently pre-alpha, breaking changes happen frequently, there have not been any security audits, and data loss is expected. + +

+ +

+

+ + Discord + +

+ +# Client + +Client is the interface used to connect and interact with the virtual world of OpenMinerva. + +## Development Quick Start + +### 1: Download source code +For cloning this project on Linux: +```bash +# Clone this repository. +git clone --recurse-submodules https://github.com/OpenMinerva/client +``` + +### 2: Import project into Godot. +2.a: Open your copy of the [Godot Engine](https://godotengine.org/). + +2.b: Click on "Import" near the top of the application window. + +2.c: Navigate to the location where the source code was downloaded to. + +2.d: Navigate to the subdirectory `/src`. + +2.e: Select `project.godot` to import this project. + +### 3: Open +Double click on the added entry. + +## Contributing +See [CONTRIBUTING.md](https://github.com/OpenMinerva/client/blob/alpha/CONTRIBUTING.md) for guidelines, and information. diff --git a/docs/logos/om-logo-big.webp b/docs/logos/om-logo-big.webp new file mode 100644 index 0000000..04fe460 Binary files /dev/null and b/docs/logos/om-logo-big.webp differ diff --git a/src/addons/godot-oauth2client b/src/addons/godot-oauth2client new file mode 160000 index 0000000..a835738 --- /dev/null +++ b/src/addons/godot-oauth2client @@ -0,0 +1 @@ +Subproject commit a835738a2674feb2679576a247169fc53c2d4682 diff --git a/src/addons/godot-urlparser b/src/addons/godot-urlparser new file mode 160000 index 0000000..5063250 --- /dev/null +++ b/src/addons/godot-urlparser @@ -0,0 +1 @@ +Subproject commit 50632506d6a5a8a65bda52640339df98957fd0cd diff --git a/src/openminerva_darkpanel.tres b/src/openminerva_darkpanel.tres new file mode 100644 index 0000000..a6890af --- /dev/null +++ b/src/openminerva_darkpanel.tres @@ -0,0 +1,4 @@ +[gd_resource type="StyleBoxFlat" format=3 uid="uid://cxx1q037xaswi"] + +[resource] +bg_color = Color(0.0625, 0.0625, 0.0625, 1) diff --git a/src/openminerva_default.tres b/src/openminerva_default.tres new file mode 100644 index 0000000..a390c0e --- /dev/null +++ b/src/openminerva_default.tres @@ -0,0 +1,19 @@ +[gd_resource type="Theme" format=3 uid="uid://bg2nbganyysst"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_31r6k"] +bg_color = Color(0, 0, 0, 0.78431374) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_f2aq3"] +bg_color = Color(0.14117648, 0.39215687, 0.5686275, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fbptr"] +bg_color = Color(0.02, 0.02, 0.02, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fq2hb"] +bg_color = Color(0.01773237, 0.0177324, 0.017732374, 0.8) + +[resource] +Button/styles/normal = SubResource("StyleBoxFlat_31r6k") +Button/styles/pressed = SubResource("StyleBoxFlat_f2aq3") +Panel/styles/normal = SubResource("StyleBoxFlat_fbptr") +Panel/styles/panel = SubResource("StyleBoxFlat_fq2hb") diff --git a/src/project.godot b/src/project.godot new file mode 100644 index 0000000..be75d1e --- /dev/null +++ b/src/project.godot @@ -0,0 +1,107 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[animation] + +compatibility/default_parent_skeleton_in_mesh_instance_3d=true + +[application] + +config/name="OpenMinerva Client" +run/main_scene="uid://cxk6c0uipjjpo" +config/features=PackedStringArray("4.6", "Forward Plus") +run/max_fps=144 +boot_splash/stretch_mode=0 +boot_splash/image="uid://dpdryj57noc4c" +config/icon="res://resources/icons/logos/logo.webp" +config/macos_native_icon="res://resources/icons/logos/logo.icns" +config/windows_native_icon="res://resources/icons/logos/logo.ico" +boot_splash/minimum_display_time=1500 + +[autoload] + +LaunchArguments="*uid://c45jrfmrjtnyn" +GlobalLogger="*uid://dgmfafi41y1nk" +FileManager="*uid://d2s50p717g3n" +SettingsManager="*uid://bj4giyk05v042" +AccountServers="*uid://bpysjoq7n0ytu" +Random="*uid://1js68qt8w0mv" +GlobalAccount="*uid://dtlb70kxvbtvn" +Events="*uid://c656spc3ppdlw" +SessionQuery="*uid://dbrabs53sf0x5" +Enum="*uid://dgpvcem71xdbn" +Bootstrap="*uid://cvj10vdw6hlqw" +NetworkCompression="*uid://b2iq75uom64x2" +HTTP="*uid://d3cnfdwjxopsx" +OAuth2Client="*uid://c4b880pwgfrty" +UrlParser="*uid://budprjmmpally" + +[display] + +window/size/viewport_width=1920 +window/size/viewport_height=1080 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/godot-oauth2client/plugin.cfg", "res://addons/godot-urlparser/plugin.cfg") + +[input] + +jump={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) +] +} +forward={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +backward={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} +left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} +context={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null) +] +} +escape={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +sprint={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} + +[physics] + +3d/run_on_separate_thread=true +3d/physics_engine="Jolt Physics" + +[rendering] + +textures/default_filters/use_nearest_mipmap_filter=true diff --git a/src/resources/icons/account.svg b/src/resources/icons/account.svg new file mode 100644 index 0000000..a0afd11 --- /dev/null +++ b/src/resources/icons/account.svg @@ -0,0 +1,42 @@ + + + + + + diff --git a/src/resources/icons/account.svg.import b/src/resources/icons/account.svg.import new file mode 100644 index 0000000..2c4211b --- /dev/null +++ b/src/resources/icons/account.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://coqi7w7inqyv1" +path="res://.godot/imported/account.svg-4dd6f4e3bd6806ac9bb3535ef447dd9a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/account.svg" +dest_files=["res://.godot/imported/account.svg-4dd6f4e3bd6806ac9bb3535ef447dd9a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/apps.svg b/src/resources/icons/apps.svg new file mode 100644 index 0000000..60a8cc2 --- /dev/null +++ b/src/resources/icons/apps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/apps.svg.import b/src/resources/icons/apps.svg.import new file mode 100644 index 0000000..41f2d7d --- /dev/null +++ b/src/resources/icons/apps.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dp5u16rwxd0bp" +path="res://.godot/imported/apps.svg-062353f68bab189f9e392acee8f22033.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/apps.svg" +dest_files=["res://.godot/imported/apps.svg-062353f68bab189f9e392acee8f22033.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/art.svg b/src/resources/icons/art.svg new file mode 100644 index 0000000..247db39 --- /dev/null +++ b/src/resources/icons/art.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/art.svg.import b/src/resources/icons/art.svg.import new file mode 100644 index 0000000..bbecb1d --- /dev/null +++ b/src/resources/icons/art.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crrjk7c2g4q55" +path="res://.godot/imported/art.svg-c5ead3145c8db393dce7fc50a2521349.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/art.svg" +dest_files=["res://.godot/imported/art.svg-c5ead3145c8db393dce7fc50a2521349.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/circus.svg b/src/resources/icons/circus.svg new file mode 100644 index 0000000..575b677 --- /dev/null +++ b/src/resources/icons/circus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/circus.svg.import b/src/resources/icons/circus.svg.import new file mode 100644 index 0000000..9296b75 --- /dev/null +++ b/src/resources/icons/circus.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0qo67fgifqe8" +path="res://.godot/imported/circus.svg-055ea0232929f7da2d9389531cde2519.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/circus.svg" +dest_files=["res://.godot/imported/circus.svg-055ea0232929f7da2d9389531cde2519.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/contacts.svg b/src/resources/icons/contacts.svg new file mode 100644 index 0000000..ee85868 --- /dev/null +++ b/src/resources/icons/contacts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/contacts.svg.import b/src/resources/icons/contacts.svg.import new file mode 100644 index 0000000..c212229 --- /dev/null +++ b/src/resources/icons/contacts.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfckge3u00boi" +path="res://.godot/imported/contacts.svg-cfa1710a5ebd0d6d19edfd359b5c2f4e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/contacts.svg" +dest_files=["res://.godot/imported/contacts.svg-cfa1710a5ebd0d6d19edfd359b5c2f4e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/debug.svg b/src/resources/icons/debug.svg new file mode 100644 index 0000000..cd86722 --- /dev/null +++ b/src/resources/icons/debug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/debug.svg.import b/src/resources/icons/debug.svg.import new file mode 100644 index 0000000..48d27b8 --- /dev/null +++ b/src/resources/icons/debug.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8fed1h3aqw1s" +path="res://.godot/imported/debug.svg-73aced604934a13740a0d50d04426de7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/debug.svg" +dest_files=["res://.godot/imported/debug.svg-73aced604934a13740a0d50d04426de7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/dummy16-9.webp b/src/resources/icons/dummy16-9.webp new file mode 100644 index 0000000..b50f87f Binary files /dev/null and b/src/resources/icons/dummy16-9.webp differ diff --git a/src/resources/icons/dummy16-9.webp.import b/src/resources/icons/dummy16-9.webp.import new file mode 100644 index 0000000..de2981e --- /dev/null +++ b/src/resources/icons/dummy16-9.webp.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blkl4og334med" +path="res://.godot/imported/dummy16-9.webp-5a004b2714a0a12aeacb8a560e85c6af.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/dummy16-9.webp" +dest_files=["res://.godot/imported/dummy16-9.webp-5a004b2714a0a12aeacb8a560e85c6af.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/src/resources/icons/edit.svg b/src/resources/icons/edit.svg new file mode 100644 index 0000000..c97ced3 --- /dev/null +++ b/src/resources/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/edit.svg.import b/src/resources/icons/edit.svg.import new file mode 100644 index 0000000..46a84a5 --- /dev/null +++ b/src/resources/icons/edit.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckoajckje5mq2" +path="res://.godot/imported/edit.svg-eca169d9395b96f7bd6a43743745a869.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/edit.svg" +dest_files=["res://.godot/imported/edit.svg-eca169d9395b96f7bd6a43743745a869.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/environment.svg b/src/resources/icons/environment.svg new file mode 100644 index 0000000..55b530c --- /dev/null +++ b/src/resources/icons/environment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/environment.svg.import b/src/resources/icons/environment.svg.import new file mode 100644 index 0000000..0044528 --- /dev/null +++ b/src/resources/icons/environment.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnhy47i7saehq" +path="res://.godot/imported/environment.svg-e5081c4d8b4cb57b48121ddccbf78859.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/environment.svg" +dest_files=["res://.godot/imported/environment.svg-e5081c4d8b4cb57b48121ddccbf78859.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/exit.svg b/src/resources/icons/exit.svg new file mode 100644 index 0000000..c06c85c --- /dev/null +++ b/src/resources/icons/exit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/exit.svg.import b/src/resources/icons/exit.svg.import new file mode 100644 index 0000000..6686168 --- /dev/null +++ b/src/resources/icons/exit.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8fwxg1ipf0fp" +path="res://.godot/imported/exit.svg-b4526ff8097e7b69e7a3907e51f5cfd2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/exit.svg" +dest_files=["res://.godot/imported/exit.svg-b4526ff8097e7b69e7a3907e51f5cfd2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=8 +process/channel_remap/blue=8 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/flowchart.svg b/src/resources/icons/flowchart.svg new file mode 100644 index 0000000..933c7e5 --- /dev/null +++ b/src/resources/icons/flowchart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/flowchart.svg.import b/src/resources/icons/flowchart.svg.import new file mode 100644 index 0000000..46b7a33 --- /dev/null +++ b/src/resources/icons/flowchart.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddchrocw45muh" +path="res://.godot/imported/flowchart.svg-1dfadc4d5729c8ef52a1ba1899b04c1f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/flowchart.svg" +dest_files=["res://.godot/imported/flowchart.svg-1dfadc4d5729c8ef52a1ba1899b04c1f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/home.svg b/src/resources/icons/home.svg new file mode 100644 index 0000000..47c1005 --- /dev/null +++ b/src/resources/icons/home.svg @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/src/resources/icons/home.svg.import b/src/resources/icons/home.svg.import new file mode 100644 index 0000000..01010db --- /dev/null +++ b/src/resources/icons/home.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7mgxvhy58nhp" +path="res://.godot/imported/home.svg-2b476043e26df33e5e1ff306597ac95d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/home.svg" +dest_files=["res://.godot/imported/home.svg-2b476043e26df33e5e1ff306597ac95d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/inventory.svg b/src/resources/icons/inventory.svg new file mode 100644 index 0000000..360351c --- /dev/null +++ b/src/resources/icons/inventory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/inventory.svg.import b/src/resources/icons/inventory.svg.import new file mode 100644 index 0000000..9f5a813 --- /dev/null +++ b/src/resources/icons/inventory.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://br6hpysqvuhek" +path="res://.godot/imported/inventory.svg-4df2a15583a1909dfe17d480c9308b85.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/inventory.svg" +dest_files=["res://.godot/imported/inventory.svg-4df2a15583a1909dfe17d480c9308b85.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/logos/logo-big.png b/src/resources/icons/logos/logo-big.png new file mode 100644 index 0000000..9323883 Binary files /dev/null and b/src/resources/icons/logos/logo-big.png differ diff --git a/src/resources/icons/logos/logo-big.png.import b/src/resources/icons/logos/logo-big.png.import new file mode 100644 index 0000000..87ddf99 --- /dev/null +++ b/src/resources/icons/logos/logo-big.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpdryj57noc4c" +path="res://.godot/imported/logo-big.png-8df91e22a8df12fa9010f3b5b5799846.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/logos/logo-big.png" +dest_files=["res://.godot/imported/logo-big.png-8df91e22a8df12fa9010f3b5b5799846.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/src/resources/icons/logos/logo.icns b/src/resources/icons/logos/logo.icns new file mode 100644 index 0000000..4dcaa94 Binary files /dev/null and b/src/resources/icons/logos/logo.icns differ diff --git a/src/resources/icons/logos/logo.ico b/src/resources/icons/logos/logo.ico new file mode 100644 index 0000000..87c9bd9 Binary files /dev/null and b/src/resources/icons/logos/logo.ico differ diff --git a/src/resources/icons/logos/logo.svg b/src/resources/icons/logos/logo.svg new file mode 100644 index 0000000..00edb2b --- /dev/null +++ b/src/resources/icons/logos/logo.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + diff --git a/src/resources/icons/logos/logo.svg.import b/src/resources/icons/logos/logo.svg.import new file mode 100644 index 0000000..d21bf7c --- /dev/null +++ b/src/resources/icons/logos/logo.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bt4tkqkxr41ob" +path="res://.godot/imported/logo.svg-6ed21cb00320c581a372293d3be1caf2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/logos/logo.svg" +dest_files=["res://.godot/imported/logo.svg-6ed21cb00320c581a372293d3be1caf2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/logos/logo.webp b/src/resources/icons/logos/logo.webp new file mode 100644 index 0000000..8c42e03 Binary files /dev/null and b/src/resources/icons/logos/logo.webp differ diff --git a/src/resources/icons/logos/logo.webp.import b/src/resources/icons/logos/logo.webp.import new file mode 100644 index 0000000..64c377f --- /dev/null +++ b/src/resources/icons/logos/logo.webp.import @@ -0,0 +1,36 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://du7qkxsv0v82d" +valid=false + +[deps] + +source_file="res://resources/icons/logos/logo.webp" + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/src/resources/icons/science.svg b/src/resources/icons/science.svg new file mode 100644 index 0000000..44ef30a --- /dev/null +++ b/src/resources/icons/science.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/science.svg.import b/src/resources/icons/science.svg.import new file mode 100644 index 0000000..37a6512 --- /dev/null +++ b/src/resources/icons/science.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fkfiymss8p57" +path="res://.godot/imported/science.svg-cafdac4bdbb3c0808227ace8b523bf6d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/science.svg" +dest_files=["res://.godot/imported/science.svg-cafdac4bdbb3c0808227ace8b523bf6d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/search.svg b/src/resources/icons/search.svg new file mode 100644 index 0000000..1de92db --- /dev/null +++ b/src/resources/icons/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/search.svg.import b/src/resources/icons/search.svg.import new file mode 100644 index 0000000..bb324b1 --- /dev/null +++ b/src/resources/icons/search.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rdp5i1i18jus" +path="res://.godot/imported/search.svg-82a26f800f89de19aeb402de255cb51a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/search.svg" +dest_files=["res://.godot/imported/search.svg-82a26f800f89de19aeb402de255cb51a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/send.svg b/src/resources/icons/send.svg new file mode 100644 index 0000000..826ac4c --- /dev/null +++ b/src/resources/icons/send.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/send.svg.import b/src/resources/icons/send.svg.import new file mode 100644 index 0000000..2bc6aec --- /dev/null +++ b/src/resources/icons/send.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dc24ve1rlrlpq" +path="res://.godot/imported/send.svg-bb321525a8b68976793a8352e1678c24.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/send.svg" +dest_files=["res://.godot/imported/send.svg-bb321525a8b68976793a8352e1678c24.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/settings.svg b/src/resources/icons/settings.svg new file mode 100644 index 0000000..87e6549 --- /dev/null +++ b/src/resources/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/settings.svg.import b/src/resources/icons/settings.svg.import new file mode 100644 index 0000000..ce424d0 --- /dev/null +++ b/src/resources/icons/settings.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://basvqob80u3l6" +path="res://.godot/imported/settings.svg-0b4f826fa9066b225cd3dbb381f4b59c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/settings.svg" +dest_files=["res://.godot/imported/settings.svg-0b4f826fa9066b225cd3dbb381f4b59c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/subtitles.svg b/src/resources/icons/subtitles.svg new file mode 100644 index 0000000..b128d91 --- /dev/null +++ b/src/resources/icons/subtitles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/subtitles.svg.import b/src/resources/icons/subtitles.svg.import new file mode 100644 index 0000000..3cdb66e --- /dev/null +++ b/src/resources/icons/subtitles.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2yhyfcuop7gx" +path="res://.godot/imported/subtitles.svg-9f1342bf97e0fea8eeb93e4877d4aa61.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/subtitles.svg" +dest_files=["res://.godot/imported/subtitles.svg-9f1342bf97e0fea8eeb93e4877d4aa61.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/icons/world.svg b/src/resources/icons/world.svg new file mode 100644 index 0000000..5d5101b --- /dev/null +++ b/src/resources/icons/world.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/icons/world.svg.import b/src/resources/icons/world.svg.import new file mode 100644 index 0000000..db923a2 --- /dev/null +++ b/src/resources/icons/world.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7ofnclcof0ud" +path="res://.godot/imported/world.svg-e1d8d3b0524fba3a4befec8a9a4380bd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://resources/icons/world.svg" +dest_files=["res://.godot/imported/world.svg-e1d8d3b0524fba3a4befec8a9a4380bd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=5.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/src/resources/shaders/grid_shader.gdshader b/src/resources/shaders/grid_shader.gdshader new file mode 100644 index 0000000..955d78a --- /dev/null +++ b/src/resources/shaders/grid_shader.gdshader @@ -0,0 +1,49 @@ +shader_type spatial; + +uniform float line_width : hint_range(0.001, 0.5, 0.001) = 0.005; + +// Controls how far the grid is visible +uniform float fade_start : hint_range(0, 100, 1) = 5.0; +uniform float fade_end : hint_range(0, 100, 1) = 20.0; + +varying vec3 world_position; +varying vec3 world_normal; + +void vertex() { + vec4 world_pos = MODEL_MATRIX * vec4(VERTEX, 1.0); + world_position = world_pos.xyz; + world_normal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz); +} + +void fragment() { + // 1. Calculate the basic grid (Hard lines, no AA) + vec2 grid_xz = fract(world_position.xz); + vec2 grid_xy = fract(world_position.xy); + vec2 grid_yz = fract(world_position.yz); + + float line_xz = step(grid_xz.x, line_width) + (1.0 - step(grid_xz.x, 1.0 - line_width)) + + step(grid_xz.y, line_width) + (1.0 - step(grid_xz.y, 1.0 - line_width)); + float line_xy = step(grid_xy.x, line_width) + (1.0 - step(grid_xy.x, 1.0 - line_width)) + + step(grid_xy.y, line_width) + (1.0 - step(grid_xy.y, 1.0 - line_width)); + float line_yz = step(grid_yz.x, line_width) + (1.0 - step(grid_yz.x, 1.0 - line_width)) + + step(grid_yz.y, line_width) + (1.0 - step(grid_yz.y, 1.0 - line_width)); + + // 2. Calculate distance to camera + // CAMERA_POSITION_WORLD is a built-in in Godot 4 fragment shaders + float dist = distance(CAMERA_POSITION_WORLD, world_position); + + // 3. Calculate fade factor (1.0 = visible, 0.0 = invisible) + // smoothstep transitions smoothly from fade_start to fade_end + float fade = 1.0 - smoothstep(fade_start, fade_end, dist); + + // 4. Determine visible grid based on normals + float w_xz = abs(world_normal.y); + float w_xy = abs(world_normal.z); + float w_yz = abs(world_normal.x); + + float grid = max(line_xz * w_xz, max(line_xy * w_xy, line_yz * w_yz)); + + // 5. Mix colors, applying the fade + // As 'fade' drops to 0, the white lines mix into the black background + ALBEDO = mix(vec3(0.0), vec3(1.0), step(0.5, grid) * fade); +} diff --git a/src/resources/shaders/grid_shader.gdshader.uid b/src/resources/shaders/grid_shader.gdshader.uid new file mode 100644 index 0000000..d4a6ee4 --- /dev/null +++ b/src/resources/shaders/grid_shader.gdshader.uid @@ -0,0 +1 @@ +uid://bh250q6rfv615 diff --git a/src/scenes/levels/base.tscn b/src/scenes/levels/base.tscn new file mode 100644 index 0000000..6d3b923 --- /dev/null +++ b/src/scenes/levels/base.tscn @@ -0,0 +1,22 @@ +[gd_scene format=3 uid="uid://bysrhijnau31g"] + +[ext_resource type="Script" uid="uid://bx6abcqetw04c" path="res://scenes/managers/scene/entity.gd" id="1_5on25"] +[ext_resource type="Script" uid="uid://c13l6ywhxq6n5" path="res://scenes/managers/scene/player.gd" id="1_c3hqj"] +[ext_resource type="Script" uid="uid://ckxuws4l4alod" path="res://scenes/managers/scene/network.gd" id="1_fundk"] +[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="EntityManager" type="Node" parent="." unique_id=1378414814] +script = ExtResource("1_5on25") + +[node name="NetworkManager" type="Node" parent="." unique_id=2144728320] +script = ExtResource("1_fundk") + +[node name="PlayerManager" type="Node" parent="." unique_id=412138074] +script = ExtResource("1_c3hqj") + +[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/levels/grid.tscn b/src/scenes/levels/grid.tscn new file mode 100644 index 0000000..e598ffa --- /dev/null +++ b/src/scenes/levels/grid.tscn @@ -0,0 +1,38 @@ +[gd_scene format=3 uid="uid://b3t1dk4vpjk6p"] + +[ext_resource type="Shader" uid="uid://bh250q6rfv615" path="res://resources/shaders/grid_shader.gdshader" id="1_ikf4c"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_dbach"] +sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1) +ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1) + +[sub_resource type="Sky" id="Sky_ikf4c"] +sky_material = SubResource("ProceduralSkyMaterial_dbach") + +[sub_resource type="Environment" id="Environment_q28r8"] +background_mode = 2 +sky = SubResource("Sky_ikf4c") +tonemap_mode = 2 +glow_enabled = true + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_q28r8"] +render_priority = 0 +shader = ExtResource("1_ikf4c") +shader_parameter/line_width = 0.010000000475 +shader_parameter/fade_start = 5.0 +shader_parameter/fade_end = 50.0 + +[node name="Home" type="Node3D" unique_id=53262658] + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1916430820] +transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0) +shadow_enabled = true + +[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1457348223] +environment = SubResource("Environment_q28r8") + +[node name="CSGBox3D" type="CSGBox3D" parent="." unique_id=533069808] +transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0) +use_collision = true +size = Vector3(1000, 0.01, 1000) +material = SubResource("ShaderMaterial_q28r8") diff --git a/src/scenes/managers/app/network.gd b/src/scenes/managers/app/network.gd new file mode 100644 index 0000000..6b44e50 --- /dev/null +++ b/src/scenes/managers/app/network.gd @@ -0,0 +1,463 @@ +# --- 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 + +const MAX_CLIENTS = 1000 +const MINIMUM_INCREMENTAL_PORT = 20205 + +var url_regex: RegEx = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") + +@onready var scene_m = get_node("../SceneManager") + +var _database = { + "heartbeats": {}, + "sessions_id": {}, + "sessions": {}, + "sessions_api": {} +} + +const _instance_database_template = { + "id": "", + "name": "", + "description": "", + "port": 0, + "max_connected_users": 1, + "privacy": null, + "active": false, + + "connected_players": [], + "start_time": 0, + + "networking": { + "use_steam": false, + "use_lan": false + } +} + +func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRID) -> Dictionary: + var response_dict = {"ok": false, "error": null, "data": null} + GlobalLogger.logs("Starting a new server.") + + # Get an available port. If port was defined, force that port or fail. + if port != 0: + GlobalLogger.logs("Forcing port '%s'" % 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.id = _scene + _instance.name = _scene + _instance.start_time = int(Time.get_unix_time_from_system()) + _instance.privacy = Enum.PrivacyLevel.INVITE + _instance.port = port + _instance.type = "host" + + # 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 + + var master_scene = scene_m.get_master_scene(_scene) + get_tree().set_multiplayer(_mp_api, master_scene.get_path()) + _mp_api.set_root_path(master_scene.get_path()) + var net_manager = master_scene.get_node("NetworkManager") + net_manager.setup_connection(_mp_api, _scene) + + _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, Enum.LogLevel.INFO) + response_dict.error = str(_create_server_response) + + _database.sessions_api.erase(_scene) + _database.sessions.erase(_scene) + + scene_m.destroy_master_scene(_scene) + # HACK: Retry creating a server again. + if _create_server_response == 20: + # Port is in use + return start_server(0, root_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) + + scene_m.start_master_scene(_scene) + + # DEV: Force spawn the host. + scene_m.get_master_scene(_scene).get_node("PlayerManager").add_player(1) + scene_m.set_active_session(_scene) + + return response_dict + +func stop_server(id: String): + var database_has_sessions: bool = _database.sessions.has(id) + var database_has_sessions_api: bool = _database.sessions_api.has(id) + GlobalLogger.logs("Stopping server '%s'." % id) + + # TODO: Disable join requests to server + + if !database_has_sessions && !database_has_sessions_api: + GlobalLogger.logs("Session '%s' does not exist, cannot stop the server." % id, Enum.LogLevel.WARNING) + return + + if database_has_sessions_api: + var mp_api: SceneMultiplayer = _database.sessions_api.get(id) + var all_peers = mp_api.get_peers() + + # Kick all players + for _peer in all_peers: + kick_player(id, _peer, "Server Closing") + + # Close the server + mp_api.multiplayer_peer.close() + mp_api.multiplayer_peer = null + + # Application cleanup + scene_m.stop_master_scene(id) + scene_m.destroy_master_scene(id) + + # Database cleanup + _database.sessions_api.erase(id) + _database.sessions.erase(id) + return + +func update_server(id: String, server_info: Dictionary): + GlobalLogger.logs("Updating server '%s'." % id) + var _saved_session_servers = SettingsManager.get_session_servers() + + if server_info.privacy > Enum.PrivacyLevel.INVITE: + for _server in _saved_session_servers: + if _database.heartbeats.has(id): + GlobalLogger.logs("Session '%s' is already advertised. Updating instead." % id) + await _update_session_server_listing(server_info, _server.url) + else: + var advertise_response = await _advertise_session(server_info, _server.url) + + if advertise_response.ok == true: + _database.sessions_id.set(server_info.id, advertise_response.data.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) + + for _server in _saved_session_servers: + _remove_session_from_server(id, _server.url) + + Events.emit_signal("instance_updated") + return + +func join_server(ip: String, port: int): + var response_dict = {"ok": false, "error": null, "data": null} + + GlobalLogger.logs("Joining server at '%s:%s'" % [ip, port], Enum.LogLevel.INFO) + var _port_is_valid = port > 0 && port < 65535 + + if ip.is_empty() || !_port_is_valid: + GlobalLogger.logs("Server information is invalid '%s:%s'." % [ip, port], Enum.LogLevel.INFO) + response_dict.error = "Server information is invalid." + return response_dict + + # 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 + _instance.port = port + _instance.type = "client" + + var _mp_api = SceneMultiplayer.new() + var _session_peer = ENetMultiplayerPeer.new() + var connect_error = _session_peer.create_client(ip, port) + + if connect_error != OK: + GlobalLogger.logs("Failed to join server. Error: '%s'" % connect_error, Enum.LogLevel.INFO) + response_dict.error = "Failed to join server. Error: '%s'" % connect_error + return response_dict + + _mp_api.multiplayer_peer = _session_peer + + var master_scene = scene_m.get_master_scene(_scene) + get_tree().set_multiplayer(_mp_api, master_scene.get_path()) + _mp_api.set_root_path(master_scene.get_path()) + var net_manager = master_scene.get_node("NetworkManager") + net_manager.setup_connection(_mp_api, _scene) + + _database.sessions_api.set(_scene, _mp_api) + _database.sessions.set(_scene, _instance) + + Events.emit_signal("session_joined") + return + +func leave_server(id: String): + GlobalLogger.logs("Trying to leave server '%s'." % id) + var database_has_sessions: bool = _database.sessions.has(id) + var database_has_sessions_api: bool = _database.sessions_api.has(id) + + if !database_has_sessions && !database_has_sessions_api: + GlobalLogger.logs("Session '%s' does not exist, cannot disconnect." % id, Enum.LogLevel.WARNING) + return + + if database_has_sessions_api: + var mp_api: SceneMultiplayer = _database.sessions_api.get(id) + + if mp_api.multiplayer_peer: + mp_api.multiplayer_peer.close() + GlobalLogger.logs("Disconnected from session '%s'." % id, Enum.LogLevel.DEBUG) + + scene_m.set_active_session(get_connected_sessions()[0].id) + + scene_m.stop_master_scene(id) + scene_m.destroy_master_scene(id) + + _database.sessions_api.erase(id) + _database.sessions.erase(id) + + GlobalLogger.logs("Successfully disconnected from session '%s' and cleaned up." % id, Enum.LogLevel.DEBUG) + Events.emit_signal("session_left") + return + +func kick_player(server_id:String, peer_id: int, reason: String): + GlobalLogger.logs("Kicking peer '%s' from '%s' for reason '%s'" % [peer_id, server_id, reason], Enum.LogLevel.DEBUG) + var database_has_sessions_api: bool = _database.sessions_api.has(server_id) + # TODO: Check if peer exists + if database_has_sessions_api: + var mp_api: SceneMultiplayer = _database.sessions_api.get(server_id) + # TODO: Notify user of kick + mp_api.disconnect_peer(peer_id) + 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 set_active_session(id: String): + if _database.sessions.has(id): + GlobalLogger.logs("Tried to mark an invalid session as active: '%s'" % id, Enum.LogLevel.WARNING) + return + + for session_id in _database.sessions.keys(): + var my_id = _database.sessions_api[session_id].multiplayer.get_unique_id() + _database.sessions[session_id].active = false + scene_m.get_master_root(session_id).get_node("PlayerManager").players.get(my_id).get("node").camera.current = false + + var my_id = _database.sessions_api[id].multiplayer.get_unique_id() + scene_m.get_master_root(id).get_node("PlayerManager").players.get(my_id).get("node").camera.current = true + _database.sessions[id].active = true + scene_m.set_active_session(id) + return + +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) + + 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 = { + "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 _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(_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 = { + "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 = MINIMUM_INCREMENTAL_PORT) -> int: + GlobalLogger.logs("Trying to find an available port starting at '%s'." % target_port) + var _found_port = null + var _is_found = false + + while _is_found == false: + var port_available = !_is_port_in_use(target_port) + if port_available: + _found_port = target_port + _is_found = true + break + target_port = target_port + 1 + + GlobalLogger.logs("Port found: '%s'" % target_port) + + return _found_port + +func _is_port_in_use(port: int) -> bool: + var udp_server = UDPServer.new() + var err_udp = udp_server.listen(port, "*") + var tcp_server = TCPServer.new() + var err_tcp = tcp_server.listen(port, "*") + + if err_udp == OK && err_tcp == OK: + udp_server.stop() + tcp_server.stop() + return false + + udp_server.stop() + tcp_server.stop() + return true + +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, session_server_url)) + return + +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, session_server_url) + + _create_heartbeat_timer(session_id, session_server_url) + return + +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": _database.sessions_id.get(session_id)} + + var 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) + ) + + if response and response.get("ok"): + GlobalLogger.logs("Heartbeat sent for session '%s'" % session_id) + 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.id, session_server]) + 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 = { + "session_name": session_info.name, + "session_description": session_info.description, + "session_privacy": session_info.privacy, + "session_port": session_info.port, + } + + 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) + ) + + # FIXME: What is this flow? This is bad? + if advertise_response.ok != true: + 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 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/network.tscn b/src/scenes/managers/app/network.tscn new file mode 100644 index 0000000..6344fef --- /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://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/scene.gd b/src/scenes/managers/app/scene.gd new file mode 100644 index 0000000..439391c --- /dev/null +++ b/src/scenes/managers/app/scene.gd @@ -0,0 +1,183 @@ +# --- 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_m: Node = get_tree().current_scene.get_node("NetworkManager") +@onready var scene_container: Node3D = get_tree().current_scene.get_node("Scenes") +var active_session: String = "" + +func _ready(): + network_m.start_server() + return + +func create_master_scene(): + 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 + _base_scene.top_level = true + _base_scene.visible = false + + scene_container.add_child(_base_scene) + + return _scene_id + +func get_master_scene(id: String) -> Node3D: + var _scene = scene_container.get_node(id) + return _scene + +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, Enum.LogLevel.WARNING) + return + + _scene.queue_free() + return + +func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> void: + var _scene = get_master_scene(id) + + var _root_scene: PackedScene = _get_scene_by_type(scene_type) + var _root_node = get_master_root(id) + + # Stop everything + stop_master_scene(id) + + # Remove everything + _scene.remove_child(_root_node) + _root_node.queue_free() + + # Get new scene + var _root_scene_node = _root_scene.instantiate() + _root_scene_node.name = "root" + + # Add new scene + _scene.add_child(_root_scene_node) + + # Start everything + start_master_scene(id) + + Events.emit_signal("instance_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"], Enum.LogLevel.WARNING) + # get_master_scene + # Find scene from inventory. + # Validate scene integrity. + # Find node "root". + # Destroy node. + # Replace with new scene. + return false + +func start_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.module_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], Enum.LogLevel.ERROR) + 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.module_active = true + GlobalLogger.logs("'%s' stopped in server '%s'" % [node_name, id]) + continue + + GlobalLogger.logs("Could not stop invalid manager '%s' in server '%s'" % [node_name, id], Enum.LogLevel.ERROR) + 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) + +func set_active_session(session_id: String): + GlobalLogger.logs("Setting session '%s' active." % session_id) + + for _scene in network_m.get_connected_sessions(): + # Each session gets disabled + scene_container.get_node(_scene.id).visible = false + _set_camera_active_state(_scene.id, false) + _set_player_authority_state(_scene.id, false) + + # session_id gets enabled. + active_session = session_id + _set_camera_active_state(session_id, true) + scene_container.get_node(session_id).visible = true + _set_player_authority_state(session_id, true) + return + +func _set_camera_active_state(session_id, state: bool = false) -> void: + # TODO: check if session exists. + var my_id: String = str(network_m._database.sessions_api[session_id].get_unique_id()) + var master_scene: Node3D = get_master_scene(session_id) + # HACK: If my_id = 0, we get the desired result. This is not safe though. + if my_id == "0": + GlobalLogger.logs("Could not set active state for session '%s', is session open?" % [session_id], Enum.LogLevel.WARNING) + return + var player_manager: Node = master_scene.get_node("PlayerManager") + var player_database = player_manager.players + var my_database_entry = player_database.get(my_id) + var camera = my_database_entry.get("node").get_node("Head/Camera3D") + + camera.current = state + return + +func _set_player_authority_state(session_id, is_active: bool = false) -> void: + var my_id: String = str(network_m._database.sessions_api[session_id].get_unique_id()) + var master_scene: Node3D = get_master_scene(session_id) + # HACK: If my_id = 0, we get the desired result. This is not safe though. + if my_id == "0": + GlobalLogger.logs("Could not set player authority for session '%s', is session open?" % [session_id], Enum.LogLevel.WARNING) + return + var player_manager: Node = master_scene.get_node("PlayerManager") + var player_database = player_manager.players + var my_database_entry = player_database.get(my_id) + var player = my_database_entry.get("node") + + if is_active: + player.set_multiplayer_authority(int(my_id)) + return + + player.set_multiplayer_authority(0) + return 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.tscn b/src/scenes/managers/app/scene.tscn new file mode 100644 index 0000000..d382583 --- /dev/null +++ b/src/scenes/managers/app/scene.tscn @@ -0,0 +1,6 @@ +[gd_scene format=3 uid="uid://cmknpdx5ba15o"] + +[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/managers/scene/entity.gd b/src/scenes/managers/scene/entity.gd new file mode 100644 index 0000000..61510e1 --- /dev/null +++ b/src/scenes/managers/scene/entity.gd @@ -0,0 +1 @@ +extends Node diff --git a/src/scenes/managers/scene/entity.gd.uid b/src/scenes/managers/scene/entity.gd.uid new file mode 100644 index 0000000..23fc5bf --- /dev/null +++ b/src/scenes/managers/scene/entity.gd.uid @@ -0,0 +1 @@ +uid://bx6abcqetw04c diff --git a/src/scenes/managers/scene/network.gd b/src/scenes/managers/scene/network.gd new file mode 100644 index 0000000..d3b2f5a --- /dev/null +++ b/src/scenes/managers/scene/network.gd @@ -0,0 +1,92 @@ +extends Node + +var _specific_api: SceneMultiplayer = null +var _my_id = 0 +var _server_id: String = "" + +@onready var player_m = get_node("../PlayerManager") +@onready var scene_m = get_tree().current_scene.get_node("SceneManager") +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") + +func _process(_delta): + if multiplayer: + multiplayer.poll() + return + +func setup_connection(api: SceneMultiplayer, id: String): + _specific_api = api + _server_id = id + + _specific_api.connected_to_server.connect(_on_connected_to_server) + _specific_api.peer_connected.connect(_on_peer_connected) + _specific_api.peer_disconnected.connect(_on_peer_disconnected) + _specific_api.server_disconnected.connect(_on_server_disconnected) + + _my_id = multiplayer.get_unique_id() + +func _on_connected_to_server(): + GlobalLogger.logs("[%s] I am connected to a server." % _my_id) + +func _on_server_disconnected(): + network_m.leave_server(_server_id) + return + +@rpc("authority", "unreliable") +func ban_player(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) + return + +func on_kicked(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) + return + +func on_banned(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) + return + +func _on_peer_connected(peer_id: int): + if is_multiplayer_authority() == false: + return + + player_m.add_player(peer_id) + player_m.add_player.rpc(peer_id) + + GlobalLogger.logs("[%s] Peer '%s' connected to our server." % [_my_id, peer_id]) + rpc_id(peer_id, "set_root", Enum.BaseLevel.GRID) + rpc_id(peer_id, "add_players", player_m.players) + return + +func _on_peer_disconnected(peer_id: int) -> void: + if is_multiplayer_authority() == false: + return + + player_m.remove_player(peer_id) + player_m.remove_player.rpc(peer_id) + + GlobalLogger.logs("[%s] Peer '%s' disconnected to our server." % [_my_id, peer_id]) + return + +@rpc("authority", "reliable") +func set_root(scene_type: Enum.BaseLevel): + GlobalLogger.logs("[%s] Received root base scene." % [_my_id]) + scene_m.set_master_root_from_program(_server_id, scene_type) + +@rpc("authority", "reliable") +func add_players(players: Dictionary): + GlobalLogger.logs("[%s] Playerlist received." % [_my_id]) + for _player in players.keys(): + player_m.add_player(int(_player)) + +@rpc("authority", "reliable") +func spawn_entity(): + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) + +@rpc("any_peer", "unreliable") +func entity_position(entity_id: int, position): + var caller_id = multiplayer.get_remote_sender_id() + if caller_id != entity_id: + return + var target_node = get_parent().get_node("root").get_node_or_null(str(entity_id)) + if target_node: + target_node.position = NetworkCompression.d_16_pos(position) + target_node.rotation = NetworkCompression.d_16_vec3(position.slice(12)) diff --git a/src/scenes/managers/scene/network.gd.uid b/src/scenes/managers/scene/network.gd.uid new file mode 100644 index 0000000..b4b7e6a --- /dev/null +++ b/src/scenes/managers/scene/network.gd.uid @@ -0,0 +1 @@ +uid://ckxuws4l4alod diff --git a/src/scenes/managers/scene/player.gd b/src/scenes/managers/scene/player.gd new file mode 100644 index 0000000..d91892a --- /dev/null +++ b/src/scenes/managers/scene/player.gd @@ -0,0 +1,69 @@ +# --- 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 module_active: bool = false + +var players = {} + +const PLAYER_TEMPLATE = { + "peer_id": 0, + "has_spawned": false, + "node": null +} + +@rpc("authority", "reliable") +func add_player(peer_id: int) -> void: + var caller_id = multiplayer.get_remote_sender_id() + var database_template = PLAYER_TEMPLATE.duplicate() + + GlobalLogger.logs("[%s] Adding peer '%s' to the player list" % [caller_id, peer_id]) + database_template.set("peer_id", peer_id) + players.set(str(peer_id), database_template) + + spawn_player(str(peer_id)) + +@rpc("authority", "reliable") +func remove_player(peer_id: int) -> void: + var caller_id = multiplayer.get_remote_sender_id() + + GlobalLogger.logs("[%s] Removing peer '%s' from the player list" % [caller_id, peer_id]) + players[str(peer_id)].get("node").queue_free() + players.erase(str(peer_id)) + +@rpc("authority", "unreliable") +func spawn_player(peer_id: String) -> void: + var caller_id = multiplayer.get_remote_sender_id() + + if players[peer_id].get("has_spawned") == true: + GlobalLogger.logs("[%s] Did not spawn peer '%s', already exists!" % [caller_id, peer_id], Enum.LogLevel.WARNING) + return + + GlobalLogger.logs("[%s] Spawning peer '%s'" % [caller_id, peer_id]) + + if module_active == false: + GlobalLogger.logs("[%s] Could not spawn peer '%s', module inactive." % [caller_id, peer_id]) + return + + 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) + _new_player.set_multiplayer_authority(int(peer_id)) + get_node("../root").add_child( _new_player) + var player_node = get_node("../root").get_node(str(peer_id)) + GlobalLogger.logs("[%s] Spawned peer '%s'." % [caller_id, peer_id]) + players[peer_id].set("node", player_node) + players[peer_id].set("has_spawned", true) + return + +func kill_player() -> void: + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) + return 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..b89b375 --- /dev/null +++ b/src/scenes/managers/scene/signalbus.gd @@ -0,0 +1,12 @@ +# --- 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 + +var module_active = false 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 diff --git a/src/scenes/master.tscn b/src/scenes/master.tscn new file mode 100644 index 0000000..01ce876 --- /dev/null +++ b/src/scenes/master.tscn @@ -0,0 +1,16 @@ +[gd_scene format=3 uid="uid://cxk6c0uipjjpo"] + +[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="PackedScene" uid="uid://ckl5gw0xbduiv" path="res://userinterface/dash/hud.tscn" id="5_q3f5g"] + +[node name="Master" type="Node3D" unique_id=420526444] + +[node name="NetworkManager" parent="." unique_id=1960146969 instance=ExtResource("1_jooxx")] + +[node name="SceneManager" parent="." unique_id=5477810 instance=ExtResource("2_h2qy3")] + +[node name="Hud" parent="." unique_id=1053137144 instance=ExtResource("5_q3f5g")] +visible = false + +[node name="Scenes" type="Node3D" parent="." unique_id=308239834] diff --git a/src/scenes/players/player.gd b/src/scenes/players/player.gd new file mode 100644 index 0000000..bcdfd26 --- /dev/null +++ b/src/scenes/players/player.gd @@ -0,0 +1,140 @@ +extends CharacterBody3D + +var speed = 5.0 + +@onready var hud = get_tree().current_scene.get_node("Hud") +@onready var scene_m = get_tree().current_scene.get_node("SceneManager") + +# TODO: Mouse sensitivity from settings +# TODO: Replace interaction ray +# TODO: Add skeleton controller +# TODO: Mouse captured from HUD, not player controller +# TODO: Rotate climbing collider as you move WASD + +@onready var body = $"." +@onready var head = $Head +@onready var camera = $Head/Camera3D +@export var mouse_sensitivity: float = 1.5 + +const base_fov = 90.0 +const fov_change = 1.1 + +# Player speed +const SPRINT_SPEED = 6.0 +const WALK_SPEED = 3.0 +const CROUCH_SPEED = 1.5 +const PRONE_SPEED = 0.5 + +const JUMP_VELOCITY = 4.5 +const SENSITIVITY = 1.5 + +# Player statuses +var mouse_captured: bool = false + +# Get the gravity from the project settings to be synced with RigidBody nodes. +var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") + +func _enter_tree(): + set_multiplayer_authority(0) + +func _ready(): + camera.fov = base_fov + camera.current = false + +func _input(event): + if is_multiplayer_authority() == false: + return + + if event is InputEventMouseMotion && mouse_captured: + body.rotate_y(-event.relative.x * mouse_sensitivity * 0.001) + camera.rotate_x(-event.relative.y * mouse_sensitivity * 0.001) + camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-85), deg_to_rad(89)) + +func _unhandled_input(event): + if is_multiplayer_authority() == false: + return + + if event.is_action_pressed("escape"): + if mouse_captured: + capture_mouse(false) + Events.emit_signal("dash_set_state", true) + else: + capture_mouse(true) + Events.emit_signal("dash_set_state", false) + + get_viewport().set_input_as_handled() + +func _physics_process(delta): + # TODO: Simplify focus detection code from "mouse_captured". + if is_multiplayer_authority() == false: + return + # Add the gravity. + + if not is_on_floor(): + velocity.y -= gravity * delta + 0.05 + + if Input.is_action_pressed("sprint") && mouse_captured == true: + speed = lerp(speed, SPRINT_SPEED, delta * 7.0) + var pos = Vector3.ZERO + pos.y = 1.7 + pos.z = -0.15 + else: + speed = lerp(speed, WALK_SPEED, delta * 7.0) + var pos = Vector3.ZERO + pos.y = 1.7 + pos.z = -0.15 + head.transform.origin = lerp(head.transform.origin, pos, delta * 7.0) + + if !is_on_floor(): + speed = speed / 1.1 + + # Get the input direction and handle the movement/deceleration. + var input_dir: Vector2 = Vector2(0, 0) + + if mouse_captured == true: + input_dir = Input.get_vector("left", "right", "forward", "backward") + + var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() + if direction: + velocity.x = direction.x * speed + velocity.z = direction.z * speed + else: + velocity.x = lerp(velocity.x, direction.x * speed, delta * 20.0) + velocity.z = lerp(velocity.z, direction.z * speed, delta * 20.0) + + if Input.is_action_just_pressed("jump") && is_on_floor() && mouse_captured == true: + velocity.y = JUMP_VELOCITY + + move_and_slide() + _send_player_synchronization_info() + +func round_to_dec(num, digit): + return round(num * pow(10.0, digit)) / pow(10.0, digit) + +func get_subscene_root(node: Node) -> Node: + var current_node = node + if current_node.get_parent() != null: + return current_node + else: + return null + +func capture_mouse(to_capture: bool): + if to_capture == false: + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + mouse_captured = false + else: + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + mouse_captured = true + return + +func _send_player_synchronization_info(): + if is_multiplayer_authority() == false: + return + + var compressed_position = NetworkCompression.c_16_pos(position) + var compressed_rotation = NetworkCompression.c_16_vec3(rotation) + + # HACK: We are just appending the rotation bits at the end here. It should probably be more efficient somewhere else. + compressed_position.append_array(compressed_rotation) + + scene_m.get_master_scene(scene_m.active_session).get_node("NetworkManager").entity_position.rpc(int(name), compressed_position) diff --git a/src/scenes/players/player.gd.uid b/src/scenes/players/player.gd.uid new file mode 100644 index 0000000..b427d2c --- /dev/null +++ b/src/scenes/players/player.gd.uid @@ -0,0 +1 @@ +uid://dxa60xi5uelay diff --git a/src/scenes/players/player.tscn b/src/scenes/players/player.tscn new file mode 100644 index 0000000..5f1219a --- /dev/null +++ b/src/scenes/players/player.tscn @@ -0,0 +1,44 @@ +[gd_scene format=3 uid="uid://dvx1vs2ig7st4"] + +[ext_resource type="Script" uid="uid://dxa60xi5uelay" path="res://scenes/players/player.gd" id="1_plyga"] + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_p1mvi"] + +[sub_resource type="SeparationRayShape3D" id="SeparationRayShape3D_xrm3l"] +length = 0.25 + +[node name="Player" type="CharacterBody3D" unique_id=823362684 groups=["Players"]] +script = ExtResource("1_plyga") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1660137540] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +mesh = SubResource("CapsuleMesh_p1mvi") + +[node name="Head" type="Node3D" parent="." unique_id=401421687] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.7, -0.15) + +[node name="Camera3D" type="Camera3D" parent="Head" unique_id=1141552766] +fov = 40.0 + +[node name="InteractionRay" type="RayCast3D" parent="Head/Camera3D" unique_id=1005810842] +target_position = Vector3(0, 0, -2) +collision_mask = 2 +hit_back_faces = false + +[node name="AimSight" type="RayCast3D" parent="Head/Camera3D" unique_id=1914541974] +transform = Transform3D(1, 0, 0, 0, -1, -8.74228e-08, 0, 8.74228e-08, -1, 0, 0, 0) +target_position = Vector3(0, 0, 3000) +hit_back_faces = false + +[node name="Hands" type="Node3D" parent="Head/Camera3D" unique_id=553484413] +transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0, 0, 0) + +[node name="StairStep" type="CollisionShape3D" parent="." unique_id=1633399245] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0.3, -0.5194253) +shape = SubResource("SeparationRayShape3D_xrm3l") + +[node name="PlayerMovement" type="Node" parent="." unique_id=1713886755] + +[node name="PlayerInput" type="Node" parent="." unique_id=903642472] + +[node name="PlayerInventory" type="Node" parent="." unique_id=1423667068] diff --git a/src/scripts/crypto/keys.gd b/src/scripts/crypto/keys.gd new file mode 100644 index 0000000..507bb66 --- /dev/null +++ b/src/scripts/crypto/keys.gd @@ -0,0 +1,54 @@ +extends Node + +var keys = { + "public": "", + "private": "" +} + +# TODO: Move this to Files.gd script. +func read_keys_from_disk(username: String) -> PackedStringArray: + var pubKeyPath = "user://accounts/%s/keys/pubKey.pem" % username + var privKeyPath = "user://accounts/%s/keys/privKey.pem" % username + + var pubKey = "" + var privKey = "" + + var keys_exist = FileAccess.file_exists(pubKeyPath) && FileAccess.file_exists(privKeyPath) + + if keys_exist: + GlobalLogger.logs("Using saved account key.") + pubKey = FileAccess.open(pubKeyPath, FileAccess.READ).get_as_text() + privKey = FileAccess.open(privKeyPath, FileAccess.READ).get_as_text() + + return [pubKey, privKey] + + GlobalLogger.logs("No key available. Generating a new one!") + _generate_keys() + _write_keys_to_disk(username) + + return [keys.public, keys.private] + +func _write_keys_to_disk(username): + # Make sure directory exists + var dir = DirAccess.open("user://") + dir.make_dir_recursive("user://accounts/%s/keys" % username) + + # Write keys to disk + var pubKeyPath = "user://accounts/%s/keys/pubKey.pem" % username + var privKeyPath = "user://accounts/%s/keys/privKey.pem" % username + + var pubKeyFile = FileAccess.open(pubKeyPath, FileAccess.WRITE) + pubKeyFile.store_string(keys.public) + + var privKeyFile = FileAccess.open(privKeyPath, FileAccess.WRITE) + privKeyFile.store_string(keys.private) + return + +func _generate_keys(): + var crypto = Crypto.new() + + var generated_keys = crypto.generate_rsa(2048) + + keys.private = generated_keys.save_to_string(false) + keys.public = generated_keys.save_to_string(true) + return diff --git a/src/scripts/crypto/keys.gd.uid b/src/scripts/crypto/keys.gd.uid new file mode 100644 index 0000000..3c9bf4e --- /dev/null +++ b/src/scripts/crypto/keys.gd.uid @@ -0,0 +1 @@ +uid://beb6jfgnx0gyc diff --git a/src/scripts/crypto/rsa.gd b/src/scripts/crypto/rsa.gd new file mode 100644 index 0000000..11b0fa3 --- /dev/null +++ b/src/scripts/crypto/rsa.gd @@ -0,0 +1,41 @@ +# --- License +# File: /client/src/scripts/crypto/rsa.gd +# Project: OpenMinerva +# Created Date: 05 February 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +## Generates a RSA keypair at a specific bit length +## @returns Dictionary +func generate_keypair(level: int = 0) -> Dictionary: + # TODO: Error checks + var return_dictionary = {"public": "", "private": ""} + var _target_bits = 0 + + match level: + 0: + _target_bits = 2048 + _: + _target_bits = 4096 + + var crypto = Crypto.new() + var generated_keys = crypto.generate_rsa(_target_bits) + + return_dictionary.private = generated_keys.save_to_string(false) + return_dictionary.public = generated_keys.save_to_string(true) + return return_dictionary + +## Turns a pem into a CryptoKey +## @returns CryptoKey +func pem_to_cryptokey(pem: String = "") -> CryptoKey: + # TODO: Error checks + var public_key := CryptoKey.new() + if public_key.load_from_string(pem, true) != OK: + GlobalLogger.logs("Failed to load public key", Enum.LogLevel.ERROR) + return null + + return public_key diff --git a/src/scripts/crypto/rsa.gd.uid b/src/scripts/crypto/rsa.gd.uid new file mode 100644 index 0000000..eb71e2c --- /dev/null +++ b/src/scripts/crypto/rsa.gd.uid @@ -0,0 +1 @@ +uid://dkgyyc7tlbvln diff --git a/src/scripts/enum.gd b/src/scripts/enum.gd new file mode 100644 index 0000000..347eb13 --- /dev/null +++ b/src/scripts/enum.gd @@ -0,0 +1,42 @@ +# --- 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, + EMPTY = 1, + GRID = 2, +} + +enum PrivacyLevel { + INVITE = 0, + PUBLIC = 1, + CONTACTS_PLUS = 2, + CONTACTS = 3, + FRIENDS_PLUS = 4, + FRIENDS = 5 +} + +const Settings = { + Graphics = { + DisplayMode = { + FULLSCREEN = 0, + WINDOWED = 1, + BORDERLESS = 2 + } + } +} + +enum LogLevel { + DEBUG = 0, + INFO = 1, + WARNING = 2, + ERROR = 3, +} diff --git a/src/scripts/enum.gd.uid b/src/scripts/enum.gd.uid new file mode 100644 index 0000000..26a0b4d --- /dev/null +++ b/src/scripts/enum.gd.uid @@ -0,0 +1 @@ +uid://dgpvcem71xdbn diff --git a/src/scripts/libs/account.gd b/src/scripts/libs/account.gd new file mode 100644 index 0000000..0a2ba71 --- /dev/null +++ b/src/scripts/libs/account.gd @@ -0,0 +1,234 @@ +# --- License +# File: /client/src/scripts/libs/account.gd +# Project: OpenMinerva +# Created Date: 26 February 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +var time_lib = preload("res://scripts/libs/time.gd").new() +var random_lib = preload("res://scripts/utils/random.gd").new() +var rsa_lib = preload("res://scripts/crypto/rsa.gd").new() + +const ACCOUNT_DATABASE_DIRECTORY: String = "user://database/accounts.bin" + +# TODO: Create proper encryption of the account database +# https://github.com/OpenMinerva/client/issues/59 +var stop_connection_timer = false + +var active_account = {} +var _database = [] +var dev_session_server_api_key = "" + +func _ready(): + _load_account_database() + +## Get a list of all accounts and return their information. +func get_all() -> Array: + return _database + +## Adds an account to the account database. +func create(account: Dictionary, type: String) -> Dictionary: + # Make sure we are only recording data we are intending on. + var account_formatted: Dictionary = {} + + if type == "oauth": + account_formatted = _create_oauth(account) + + if len(account_formatted.keys()) == 0: + GlobalLogger.logs("Tried to create an account, but there was nothing to save.", Enum.LogLevel.ERROR) + return {"ok": false, "error": "No account formatted.", "id": null} + + _database.append(account_formatted) + + _save_account_database() + + Events.emit_signal("dash_account_list_loaded") + return {"ok": true, "id": account_formatted.id} + +func _create_oauth(account) -> Dictionary: + var _account_keys = rsa_lib.generate_keypair() + + var _clean_account = {} + _clean_account.id = random_lib.random_string(6, true) + _clean_account.display_name = account.get("display_name", null) + _clean_account.account_server = account.get("account_server", null) + _clean_account.private_device_key = _account_keys.private + _clean_account.public_device_key = _account_keys.public + + _clean_account.access_token = "" + _clean_account.refresh_token = "" + _clean_account.id_token = "" + _clean_account.access_token_expiry = 0 + + _clean_account.type = "oauth" + return _clean_account + +## Removes an account from the account database. +func remove(id: String) -> Dictionary: + GlobalLogger.logs("Attempting to remove account '%s'" % id) + var target_entry = _database.find_custom(func(entry): return entry.get("id") == id) + _database.remove_at(target_entry) + _save_account_database() + + Events.emit_signal("dash_account_list_loaded") + + return {"ok": true} + +## Sets an account as the active account. +func use(id: String) -> void: + GlobalLogger.logs("Setting active account to '%s'." % id, Enum.LogLevel.INFO) + + var _account = _get_account_by_id(id) + + # TODO: Error checking + var url = UrlParser.deconstruct(_account.account_server) + url = url.data + + var _oauth = OAuth2Client.new( + url.host, + url.port, + "OpenMinerva-Game-Client", + 54000, + GlobalLogger, + HTTP, + true + ) + + if _account.type == "oauth": + var _oauth_valid: Dictionary = await _oauth.validate(_account) + + if _oauth_valid.ok == OAuth2Client.OAUTH2_CLIENT_RESULT.OK && _oauth_valid.data == false: + await authenticate_oauth(id) + + active_account = _account + Events.emit_signal("dash_active_account_changed", active_account) + return + +## Signs out of the active account. +func clear() -> void: + active_account = {} + return + +func update(id: String, data: Dictionary) -> void: + var target_entry = _database.find_custom(func(entry): return entry.get("id") == id) + var account = _get_account_by_id(id) + + var _database_keys = account.keys() + var _data_keys = data.keys() + + for key in _data_keys: + if key not in _database_keys: + GlobalLogger.logs("Tried to update an invalid key in an account, '%s'." % key, Enum.LogLevel.WARNING) + continue + + account[key] = data[key] + + _database[target_entry] = account + _save_account_database() + return + +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) + + # TODO: Check if account is still valid without trying to sign in. + var url = UrlParser.deconstruct(account.account_server) + url = url.data + + var _oauth = OAuth2Client.new( + url.host, + url.port, + "OpenMinerva-Game-Client", + 54000, + GlobalLogger, + HTTP, + true + ) + + var oauth_tokens = await _oauth.authenticate() + if oauth_tokens.ok == OAuth2Client.OAUTH2_CLIENT_RESULT.OK: + update(id, oauth_tokens.data) + + return + +## Save the current account database we have in memory to the disk. +func _save_account_database() -> void: + GlobalLogger.logs("Saving account database to disk.") + DirAccess.open("user://").make_dir_recursive("user://database") + + var file = FileAccess.open(ACCOUNT_DATABASE_DIRECTORY, FileAccess.WRITE) + if file: + file.store_var(_database) # Serializes variable to binary + file.close() + return + +## Read the account database from the config file on our disk. +func _load_account_database() -> Array: + GlobalLogger.logs("Loading the local account database.", Enum.LogLevel.INFO) + + var account_file_exists = FileAccess.file_exists(ACCOUNT_DATABASE_DIRECTORY) + if account_file_exists == false: + GlobalLogger.logs("Account database does not exist, creating one now.", Enum.LogLevel.INFO) + _save_account_database() + + var file = FileAccess.open(ACCOUNT_DATABASE_DIRECTORY, FileAccess.READ) + + var account_data + + if file: + account_data = file.get_var() # Deserializes variable back + file.close() + + _database = account_data + return account_data + +func _get_account_by_id(id: String) -> Dictionary: + var index = _database.find_custom(func(entry): return entry.get("id") == id) + + if index > -1: + return _database[index] + + return {} + +func _update_account_by_key(id: String, key: String, value: Variant) -> void: + var index = _database.find_custom(func(entry): return entry.get("id") == id) + # TODO: Check if key is a valid key. + _database[index][key] = value + _save_account_database() + return + +func get_account_authentication_status(id) -> Dictionary: + var status = { + "valid_passport": false, + "valid_private_jwt": false + } + + var _account = _get_account_by_id(id) + + status.valid_passport = _account.get("public_account_server_passport", {"expires": 0}).get("expires", 0) > int(Time.get_unix_time_from_system()) + status.valid_private_jwt = _account.get("private_account_server_jwt", {"expires": 0}).get("expires", 0) > int(Time.get_unix_time_from_system()) + + return status + +func _handle_response(response: Dictionary) -> Dictionary: + var response_data = {"ok": false, "error": "", "body": {}} + + if response.get("ok", false) == false: + response_data.error = "Request failed for unknown reason." + GlobalLogger.logs(response_data.error, Enum.LogLevel.ERROR) + return response_data + + if response.get("body", null) == null: + response_data.error = "No body provided from the request." + GlobalLogger.logs(response_data.error, Enum.LogLevel.ERROR) + return response_data + + response_data.ok = true + response_data.body = JSON.parse_string(response.get("body")) + + return response_data diff --git a/src/scripts/libs/account.gd.uid b/src/scripts/libs/account.gd.uid new file mode 100644 index 0000000..51e0263 --- /dev/null +++ b/src/scripts/libs/account.gd.uid @@ -0,0 +1 @@ +uid://dtlb70kxvbtvn diff --git a/src/scripts/libs/bootstrap.gd b/src/scripts/libs/bootstrap.gd new file mode 100644 index 0000000..0b88636 --- /dev/null +++ b/src/scripts/libs/bootstrap.gd @@ -0,0 +1,27 @@ +# --- License +# File: /client/src/scripts/libs/bootstrap.gd +# Project: OpenMinerva +# Created Date: 21 April 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") + +func _notification(what: int) -> void: + if what == NOTIFICATION_WM_CLOSE_REQUEST: + GlobalLogger.logs("Shutting down") + + # Shutdown / leave all servers. + for server in network_m.get_connected_sessions(): + if server.type == "host": + network_m.stop_server(server.id) + continue + if server.type == "client": + network_m.leave_server(server.id) + continue + + get_tree().quit() diff --git a/src/scripts/libs/bootstrap.gd.uid b/src/scripts/libs/bootstrap.gd.uid new file mode 100644 index 0000000..0343796 --- /dev/null +++ b/src/scripts/libs/bootstrap.gd.uid @@ -0,0 +1 @@ +uid://cvj10vdw6hlqw diff --git a/src/scripts/libs/jwt.gd b/src/scripts/libs/jwt.gd new file mode 100644 index 0000000..495416b --- /dev/null +++ b/src/scripts/libs/jwt.gd @@ -0,0 +1,112 @@ +# --- License +# File: /client/src/scripts/libs/jwt.gd +# Project: OpenMinerva +# Created Date: 24 February 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +# NOTE: To keep things consistent, please keep the signature always in base64. Only convert it where it will be used. +# NOTE: Currently the account server provides JWT that are Base64url encoded. We want to change this to Base64 in this application. + +func decode(jwt_string: String = ""): + var return_dict = {"ok": false, "data": {"head": "", "payload": "", "signature": ""}} + var jwt_parts = _get_parts(jwt_string) + + return_dict.data.head = JSON.parse_string(Marshalls.base64_to_utf8(base64url_to_base64(jwt_parts.head))) + return_dict.data.payload = JSON.parse_string(Marshalls.base64_to_utf8(base64url_to_base64(jwt_parts.payload))) + return_dict.data.signature = base64url_to_base64(jwt_parts.head) + return_dict.ok = true + + return return_dict + +func encode(): + return + +func validate(jwt_string: String, public_spki: String): + var crypto: Crypto = Crypto.new() + var jwt_parts: Dictionary = _get_parts(jwt_string) + var public_key: CryptoKey = pem_to_cryptokey(public_spki) + if jwt_parts.ok != true: + GlobalLogger.logs("Failed to deconstruct jwt when verifying jwt.", Enum.LogLevel.WARNING) + GlobalLogger.logs(str(jwt_string)) + return false + + var formatted_payload: Dictionary = _format_payload_for_verification(jwt_parts.head, jwt_parts.payload) + if formatted_payload.ok != true: + GlobalLogger.logs("Failed to format the jwt payload when verifying jwt.", Enum.LogLevel.WARNING) + GlobalLogger.logs(str(jwt_parts)) + return false + + return crypto.verify( + HashingContext.HASH_SHA256, + formatted_payload.payload_bytes, + Marshalls.base64_to_raw(jwt_parts.signature), + public_key + ) + +func base64url_to_base64(input_value: String): + var fixed: String = input_value + + fixed = fixed.replace("_", "/").replace("-", "+") + var padding = 4 - (fixed.length() % 4) + + if padding < 4: + fixed += "=".repeat(padding) + + return fixed + +func base64_to_base64url(input_value: String): + var base64_str = input_value.replace("+", "-") + base64_str = base64_str.replace("/", "_") + + while base64_str.ends_with("="): + base64_str = base64_str.substr(0, base64_str.length() - 1) + + return base64_str + +func _get_parts(input_value: String): + # TODO: Error checks + var return_dict = {"ok": false, "error": "", "head": "", "payload": "", "signature": ""} + + var jwt_split = input_value.split(".") + + if len(jwt_split) != 3: + GlobalLogger.logs("JWT is not formatted correctly.", Enum.LogLevel.WARNING) + return_dict.error = "JWT is not formatted correctly." + return return_dict + + return_dict.head = jwt_split[0] + return_dict.payload = jwt_split[1] + return_dict.signature = base64url_to_base64(jwt_split[2]) + return_dict.ok = true + + return return_dict + +func _format_payload_for_verification(head: String, payload: String) -> Dictionary: + # TODO: Error checks + var return_dict = {"ok": false, "payload_bytes": []} + + var formatted_payload = head + "." + payload + var payload_bytes = formatted_payload.to_utf8_buffer() + + var hasher: HashingContext = HashingContext.new() + hasher.start(HashingContext.HASH_SHA256) + hasher.update(payload_bytes) + + return_dict.payload_bytes = hasher.finish() + return_dict.ok = true + + return return_dict + +func pem_to_cryptokey(pem: String = "") -> CryptoKey: + # TODO: Error checks + var public_key := CryptoKey.new() + if public_key.load_from_string(pem, true) != OK: + GlobalLogger.logs("Failed to load public key", Enum.LogLevel.ERROR) + return null + + return public_key diff --git a/src/scripts/libs/jwt.gd.uid b/src/scripts/libs/jwt.gd.uid new file mode 100644 index 0000000..3f3604e --- /dev/null +++ b/src/scripts/libs/jwt.gd.uid @@ -0,0 +1 @@ +uid://owde4o55acn2 diff --git a/src/scripts/libs/time.gd b/src/scripts/libs/time.gd new file mode 100644 index 0000000..b5cf25d --- /dev/null +++ b/src/scripts/libs/time.gd @@ -0,0 +1,32 @@ +extends Node + +const MONTH_MAP = { + "Jan": 1, + "Feb": 2, + "Mar": 3, + "Apr": 4, + "May": 5, + "Jun": 6, + "Jul": 7, + "Aug": 8, + "Sep": 9, + "Oct": 10, + "Nov": 11, + "Dec": 12 +} + +func convert_jwt_timestamp_to_unix(timestamp: String) -> int: + var _split = timestamp.split(" ") + var _time_split = _split[4].split(":") + + var datetime_dict = { + "year": _split[3].to_int(), + "month": MONTH_MAP.get(_split[2], 1), + "day": _split[1].to_int(), + "hour": _time_split[0].to_int(), + "minute": _time_split[1].to_int(), + "second": _time_split[2].to_int() + } + + var unix_timestamp = Time.get_unix_time_from_datetime_dict(datetime_dict) + return unix_timestamp \ No newline at end of file diff --git a/src/scripts/libs/time.gd.uid b/src/scripts/libs/time.gd.uid new file mode 100644 index 0000000..5125d2f --- /dev/null +++ b/src/scripts/libs/time.gd.uid @@ -0,0 +1 @@ +uid://cfdqhdynesvon diff --git a/src/scripts/logger.gd b/src/scripts/logger.gd new file mode 100644 index 0000000..c399bbe --- /dev/null +++ b/src/scripts/logger.gd @@ -0,0 +1,83 @@ +extends Node + +var console_logging_enabled: bool = true +var file_logging_enabled: bool = true +var log_file: FileAccess +var log_file_initialized = false +var log_file_path = "" + +var log_level_colors = { + 0: "lightblue", + 1: "green", + 2: "yellow", + 3: "red" +} +var log_level_names = { + 0: "Debug", + 1: "Info", + 2: "Warning", + 3: "Error" +} + +func _ready(): + var launch_arguments: Dictionary = LaunchArguments.get_command_line_args() + console_logging_enabled = !launch_arguments.has("console_log") || launch_arguments.console_log == "True" + file_logging_enabled = !launch_arguments.has("file_log") || launch_arguments.file_log == "True" + _initialize_log_file() + set_console_logging(true) + set_file_logging(true) + logs("Logger initialized") + +func _initialize_log_file(): + log_file_path = FileManager.create_log_file() + log_file = FileAccess.open(log_file_path, FileAccess.WRITE) + log_file_initialized = true + logs("Opened log file at %s" % log_file_path) + +## Logs a message to both file and console (if enabled). +## @param message: The message string to log. If omitted, defaults to an empty string. +## @param level: The log level indicating the severity. Must be an integer: +func logs(message: String = "", level: Enum.LogLevel = Enum.LogLevel.DEBUG): + _log_to_file(message, level) + + if console_logging_enabled: + 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. + for i in range(1, stack.size()): + var frame = stack[i] + print( + "%s:%d @ %s()" % [ + frame.source, + frame.line, + frame.function + ] + ) + pass + +# FIXME: Replace "logs" with "log" +func log(message: String = "", level: Enum.LogLevel = Enum.LogLevel.DEBUG) -> void: + logs(message, level) + +func _log_to_file(message: String = "", level: int = 0): + if file_logging_enabled && log_file: + var formatted_log = "[%s] %s" % [log_level_names[level], message] + log_file.store_line(formatted_log) + log_file.flush() + +func set_console_logging(enabled: bool): + if enabled: + console_logging_enabled = true + logs("Console logging enabled for this session.", Enum.LogLevel.INFO) + else: + console_logging_enabled = false + logs("Console logging disabled for this session.", Enum.LogLevel.INFO) + +func set_file_logging(enabled: bool): + if enabled: + file_logging_enabled = true + logs("File logging enabled for this session.", Enum.LogLevel.INFO) + else: + file_logging_enabled = false + logs("File logging disabled for this session.", Enum.LogLevel.INFO) diff --git a/src/scripts/logger.gd.uid b/src/scripts/logger.gd.uid new file mode 100644 index 0000000..ac95df2 --- /dev/null +++ b/src/scripts/logger.gd.uid @@ -0,0 +1 @@ +uid://dgmfafi41y1nk diff --git a/src/scripts/managers/settings.gd b/src/scripts/managers/settings.gd new file mode 100644 index 0000000..842450c --- /dev/null +++ b/src/scripts/managers/settings.gd @@ -0,0 +1,93 @@ +# --- 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 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) + _file.store_string(_settings_string) + _file.close() + GlobalLogger.logs("Saved settings file.", Enum.LogLevel.INFO) + 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.", Enum.LogLevel.INFO) + return + + # TODO: Check if backup settings exist. + GlobalLogger.logs("Settings file does not exist, creating new settings file.", Enum.LogLevel.INFO) + FileManager.create_file("user://settings/", "current.json") + _settings = _templates.settings_file + _save_settings() + GlobalLogger.logs("Blank settings have been loaded.", Enum.LogLevel.INFO) + 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/network/account_servers.gd b/src/scripts/network/account_servers.gd new file mode 100644 index 0000000..4227491 --- /dev/null +++ b/src/scripts/network/account_servers.gd @@ -0,0 +1,26 @@ +extends Node + +var _database = {} + +# TODO: Save public account server keys to disk + +func get_public_key(host: String, port: int) -> String: + if host in _database: + return _database[host] + + var response: Dictionary = await _request_server_pem(host, port) + if response.ok == true: + _database[host] = response.data + return response.data + + return "" + +func _request_server_pem(host: String, port: int = 443) -> Dictionary: + GlobalLogger.logs("Requesting server '%s:%s'." % [host, port]) + var return_dict = {"ok": false, "data": ""} + var key = await HTTP.req(HTTPClient.METHOD_GET, host, "/public_key", port) + if key.ok == true: + return_dict.data = key.body + return_dict.ok = true + return return_dict + return return_dict diff --git a/src/scripts/network/account_servers.gd.uid b/src/scripts/network/account_servers.gd.uid new file mode 100644 index 0000000..1fbfc67 --- /dev/null +++ b/src/scripts/network/account_servers.gd.uid @@ -0,0 +1 @@ +uid://bpysjoq7n0ytu diff --git a/src/scripts/network/http.gd b/src/scripts/network/http.gd new file mode 100644 index 0000000..c804400 --- /dev/null +++ b/src/scripts/network/http.gd @@ -0,0 +1,73 @@ +extends Node + +signal _completed(result: Dictionary) + +# TODO: When the http client fails to connect to server, no error appears. + +func req(method: HTTPClient.Method, host: String, path: String = "/", port: int = 443, headers: PackedStringArray = [], body: String = "") -> Dictionary: + var thread: Thread = Thread.new() + var params: Dictionary = { + "method": method, + "host": host, + "path": path, + "port": port, + "headers": headers, + "body": body, + "thread": thread + } + thread.start(_thread_main.bind(params)) + + return await _completed + +func _thread_main(params: Dictionary) -> void: + var client: HTTPClient = HTTPClient.new() + var return_dict: Dictionary = {"ok": false, "body": ""} + + var err: int = client.connect_to_host(params.host, params.port) + + # Could not connect to host + if err != OK: + return_dict.error = "Connection failed" + _finish(params, return_dict) + return + + # Wait for connection + while client.get_status() == HTTPClient.STATUS_CONNECTING: + client.poll() + OS.delay_msec(10) + + # TODO: Change the retry attempts? + while client.get_status() != HTTPClient.STATUS_CONNECTED: + client.poll() + OS.delay_msec(10) + + client.request(params.method, params.path, params.headers, params.body) + + while not client.has_response(): + client.poll() + OS.delay_msec(10) + + return_dict.status_code = client.get_response_code() + return_dict.response_headers = client.get_response_headers_as_dictionary() + + var response_body: String = "" + while client.get_status() == HTTPClient.STATUS_BODY: + client.poll() + var chunk: PackedByteArray = client.read_response_body_chunk() + if chunk.size() > 0: + response_body += chunk.get_string_from_utf8() + OS.delay_msec(10) + + client.close() + + return_dict.ok = true + return_dict.body = response_body + + _finish(params, return_dict) + +func _finish(params: Dictionary, result: Dictionary) -> void: + call_deferred("_emit_completed", params.thread, result) + +func _emit_completed(thread: Thread, result: Dictionary) -> void: + emit_signal("_completed", result) + thread.wait_to_finish() diff --git a/src/scripts/network/http.gd.uid b/src/scripts/network/http.gd.uid new file mode 100644 index 0000000..cce1109 --- /dev/null +++ b/src/scripts/network/http.gd.uid @@ -0,0 +1 @@ +uid://d3cnfdwjxopsx diff --git a/src/scripts/network/network_compression.gd b/src/scripts/network/network_compression.gd new file mode 100644 index 0000000..5fa51cf --- /dev/null +++ b/src/scripts/network/network_compression.gd @@ -0,0 +1,165 @@ +extends Node + +# TODO: Positional data can further be reduced by using raw bytes instead of the given 16 bits. Maybe shoot for 10 bytes? + +## Compresses a Vector3 to a PackedByteArray using 32 bit precision +## @returns PackedByteArray +func c_32_vec3(provided_data: Vector3) -> PackedByteArray: + var data = PackedByteArray() + data.resize(12) + data.encode_s32(0, _float_to_int(provided_data.x)) + data.encode_s32(4, _float_to_int(provided_data.y)) + data.encode_s32(8, _float_to_int(provided_data.z)) + + return data + +## Decompress a PackedByteArray to a Vector3 using 32 bit precision +## @returns Vector3 +func d_32_vec3(provided_data: PackedByteArray) -> Vector3: + # Validate array size + if provided_data.size() < 12: + GlobalLogger.logs("'%s' contained invalid PackedByteArray size. Can not decode value.", Enum.LogLevel.WARNING) + return Vector3() + + var x = _int_to_float(provided_data.decode_s32(0)) + var y = _int_to_float(provided_data.decode_s32(4)) + var z = _int_to_float(provided_data.decode_s32(8)) + + return Vector3(x, y, z) + +## Compresses a Vector3 to a PackedByteArray using 16 bit precision +## @returns PackedByteArray +func c_16_vec3(provided_data: Vector3) -> PackedByteArray: + var data = PackedByteArray() + data.resize(6) + data.encode_s16(0, _float_to_int(provided_data.x)) + data.encode_s16(2, _float_to_int(provided_data.y)) + data.encode_s16(4, _float_to_int(provided_data.z)) + + return data + +## Decompress a PackedByteArray to a Vector3 using 16 bit precision +## @returns Vector3 +func d_16_vec3(provided_data: PackedByteArray) -> Vector3: + # Validate array size + if provided_data.size() < 6: + GlobalLogger.logs("'%s' contained invalid PackedByteArray size. Can not decode value.", Enum.LogLevel.WARNING) + return Vector3() + + var x = _int_to_float(provided_data.decode_s16(0)) + var y = _int_to_float(provided_data.decode_s16(2)) + var z = _int_to_float(provided_data.decode_s16(4)) + + return Vector3(x, y, z) + +## Compresses a user position with octree position to a PackedByteArray using 32 bit precision +## @returns PackedByteArray +func c_32_pos(provided_data: Vector3) -> PackedByteArray: + const OCTREE_OCTANT_SIZE: int = 1000 + + # Get the octree position + # HINT: o_x = octree_x_position + # FIXME: Unsure if we even need to round, or if this is the correct operation + var o_x = int(provided_data.x / OCTREE_OCTANT_SIZE) + var o_y = int(provided_data.y / OCTREE_OCTANT_SIZE) + var o_z = int(provided_data.z / OCTREE_OCTANT_SIZE) + + # HINT: octree_compressed_position + # var o_c_pos = c_32_vec3(Vector3(o_x, o_y, o_z)) + + # Get the position in that octree + # HINT: i_x = internal_x_position + var i_x = _float_to_int(fmod(provided_data.x, OCTREE_OCTANT_SIZE)) + var i_y = _float_to_int(fmod(provided_data.y, OCTREE_OCTANT_SIZE)) + var i_z = _float_to_int(fmod(provided_data.z, OCTREE_OCTANT_SIZE)) + + # var i_c_pos = c_32_vec3(Vector3(i_x, i_y, i_z)) + + # HINT: packed_compressed_position + # FIXME: Appending the array does not work for some reason. Try and figure that out + var p_c_pos = PackedByteArray() + p_c_pos.resize(24) + p_c_pos.encode_s32(0, o_x) + p_c_pos.encode_s32(4, o_y) + p_c_pos.encode_s32(8, o_z) + p_c_pos.encode_s32(12, i_x) + p_c_pos.encode_s32(16, i_y) + p_c_pos.encode_s32(20, i_z) + + return p_c_pos + +## Decompress a user position with octree position to a Vector3 using 32 bit precision +## @returns Vector3 +func d_32_pos(provided_data: PackedByteArray) -> Vector3: + # NOTE: See compression function for variable name hints. + const OCTREE_OCTANT_SIZE: int = 1000 + + var o_x = provided_data.decode_s32(0) + var o_y = provided_data.decode_s32(4) + var o_z = provided_data.decode_s32(8) + + var i_x = _int_to_float(provided_data.decode_s32(12)) + var i_y = _int_to_float(provided_data.decode_s32(16)) + var i_z = _int_to_float(provided_data.decode_s32(20)) + + var g_pos_x = (float(o_x) * OCTREE_OCTANT_SIZE) + i_x + var g_pos_y = (float(o_y) * OCTREE_OCTANT_SIZE) + i_y + var g_pos_z = (float(o_z) * OCTREE_OCTANT_SIZE) + i_z + + var global_position = Vector3(g_pos_x, g_pos_y, g_pos_z) + + return global_position + +## Compresses a user position with octree position to a PackedByteArray using 16 bit precision +## @returns PackedByteArray +func c_16_pos(provided_data: Vector3) -> PackedByteArray: + const OCTREE_OCTANT_SIZE: int = 1000 + + var o_x = int(provided_data.x / OCTREE_OCTANT_SIZE) + var o_y = int(provided_data.y / OCTREE_OCTANT_SIZE) + var o_z = int(provided_data.z / OCTREE_OCTANT_SIZE) + + var i_x = _float_to_int(fmod(provided_data.x, OCTREE_OCTANT_SIZE)) + var i_y = _float_to_int(fmod(provided_data.y, OCTREE_OCTANT_SIZE)) + var i_z = _float_to_int(fmod(provided_data.z, OCTREE_OCTANT_SIZE)) + + var p_c_pos = PackedByteArray() + p_c_pos.resize(12) + p_c_pos.encode_s16(0, o_x) + p_c_pos.encode_s16(2, o_y) + p_c_pos.encode_s16(4, o_z) + p_c_pos.encode_s16(6, i_x) + p_c_pos.encode_s16(8, i_y) + p_c_pos.encode_s16(10, i_z) + + return p_c_pos + +## Decompress a user position with octree position to a Vector3 using 16 bit precision +## @returns Vector3 +func d_16_pos(provided_data: PackedByteArray) -> Vector3: + # NOTE: See compression function for variable name hints. + const OCTREE_OCTANT_SIZE: int = 1000 + + var o_x = provided_data.decode_s16(0) + var o_y = provided_data.decode_s16(2) + var o_z = provided_data.decode_s16(4) + + var i_x = _int_to_float(provided_data.decode_s16(6)) + var i_y = _int_to_float(provided_data.decode_s16(8)) + var i_z = _int_to_float(provided_data.decode_s16(10)) + + var g_pos_x = (float(o_x) * OCTREE_OCTANT_SIZE) + i_x + var g_pos_y = (float(o_y) * OCTREE_OCTANT_SIZE) + i_y + var g_pos_z = (float(o_z) * OCTREE_OCTANT_SIZE) + i_z + + var global_position = Vector3(g_pos_x, g_pos_y, g_pos_z) + + return global_position + +func _float_to_int(val: float) -> int: + const FLOAT_PRECISION: int = 1000 + return int(val * FLOAT_PRECISION) + +func _int_to_float(val: int) -> float: + const FLOAT_PRECISION: int = 1000 + return float(val) / FLOAT_PRECISION diff --git a/src/scripts/network/network_compression.gd.uid b/src/scripts/network/network_compression.gd.uid new file mode 100644 index 0000000..53bf2f3 --- /dev/null +++ b/src/scripts/network/network_compression.gd.uid @@ -0,0 +1 @@ +uid://b2iq75uom64x2 diff --git a/src/scripts/signal_bus.gd b/src/scripts/signal_bus.gd new file mode 100644 index 0000000..aa5b93b --- /dev/null +++ b/src/scripts/signal_bus.gd @@ -0,0 +1,40 @@ +# --- License +# File: /client/src/scripts/signal_bus.gd +# Project: OpenMinerva +# Created Date: 28 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- 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) + +# Instance +@warning_ignore("unused_signal") +signal instance_updated(instance: Dictionary) +@warning_ignore("unused_signal") +signal instance_root_changed(instance: Dictionary) + +# Multiplayer +@warning_ignore("unused_signal") +signal session_joined(instance: Dictionary) +@warning_ignore("unused_signal") +signal session_left(instance: Dictionary) \ No newline at end of file diff --git a/src/scripts/signal_bus.gd.uid b/src/scripts/signal_bus.gd.uid new file mode 100644 index 0000000..55a5805 --- /dev/null +++ b/src/scripts/signal_bus.gd.uid @@ -0,0 +1 @@ +uid://c656spc3ppdlw diff --git a/src/scripts/utils/files.gd b/src/scripts/utils/files.gd new file mode 100644 index 0000000..b1d64df --- /dev/null +++ b/src/scripts/utils/files.gd @@ -0,0 +1,42 @@ +extends Node + +## Creates a log file following the internal format. +func create_log_file() -> String: + GlobalLogger.logs("Creating a log file for this session.") + _maybe_make_directory("user://logs/") + # TODO: Sanataize param + # TODO: Error checks + # TODO: Try to create a different file if one already exists with that name. + var log_file_name = _get_today_log_file_name() + var log_file_path = "user://logs/%s.%s" % [ProjectSettings.get_setting("application/config/name"), log_file_name] + var file = FileAccess.open(log_file_path, FileAccess.WRITE) + file.close() + GlobalLogger.logs("Log file '%s' created." % log_file_path) + return log_file_path + +func create_file(dir: String, file_name: String) -> void: + _maybe_make_directory(dir) + # TODO: Sanitize name + var file = FileAccess.open("%s/%s" % [dir, file_name], FileAccess.WRITE) + GlobalLogger.logs("File '%s' created at '%s'." % [file_name, dir]) + file.close() + +func _maybe_make_directory(dir: String): + var dir_access = DirAccess.open("user://") + dir_access.make_dir_recursive(dir) + +func _parse_log_file_name(file_name: String) -> Dictionary: + var date = file_name.split(".")[1].split("-") + var year = date[0].split("_")[0] + var month = date[0].split("_")[1] + var day = date[0].split("_")[2] + var hour = date[1].split("_")[0] + var minute = date[1].split("_")[1] + var second = date[1].split("_")[2] + var time_dictionary = Time.get_datetime_dict_from_datetime_string("%s-%s-%sT%s:%s:%s" % [year, month, day, hour, minute, second], true) + return time_dictionary + +func _get_today_log_file_name() -> String: + var current_timestring = Time.get_datetime_string_from_system() + var file_name = current_timestring.replace("-", "_").replace("T", "-").replace(":", "_") + return file_name diff --git a/src/scripts/utils/files.gd.uid b/src/scripts/utils/files.gd.uid new file mode 100644 index 0000000..805a1b2 --- /dev/null +++ b/src/scripts/utils/files.gd.uid @@ -0,0 +1 @@ +uid://d2s50p717g3n diff --git a/src/scripts/utils/launch_arguments.gd b/src/scripts/utils/launch_arguments.gd new file mode 100644 index 0000000..05235d3 --- /dev/null +++ b/src/scripts/utils/launch_arguments.gd @@ -0,0 +1,17 @@ +extends Node + +var arguments = {} + +func get_command_line_args() -> Dictionary: + for argument in OS.get_cmdline_args(): + if argument.contains("="): + var key_value = argument.split("=") + arguments[key_value[0].trim_prefix("--")] = key_value[1] + else: + # Options without an argument will be present in the dictionary, + # with the value set to an empty string. + arguments[argument.trim_prefix("--")] = "" + return arguments + +func _ready(): + get_command_line_args() \ No newline at end of file diff --git a/src/scripts/utils/launch_arguments.gd.uid b/src/scripts/utils/launch_arguments.gd.uid new file mode 100644 index 0000000..4dda50b --- /dev/null +++ b/src/scripts/utils/launch_arguments.gd.uid @@ -0,0 +1 @@ +uid://c45jrfmrjtnyn diff --git a/src/scripts/utils/random.gd b/src/scripts/utils/random.gd new file mode 100644 index 0000000..5efafee --- /dev/null +++ b/src/scripts/utils/random.gd @@ -0,0 +1,10 @@ +extends Node + +func random_string(length: int = 6, hexa_encoding: bool = false): + var rng = RandomNumberGenerator.new() + rng.randomize() + var chars = "0123456789abcdef" if hexa_encoding else "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + var out = "" + for i in length: + out += chars[rng.randi_range(0, chars.length() - 1)] + return out diff --git a/src/scripts/utils/random.gd.uid b/src/scripts/utils/random.gd.uid new file mode 100644 index 0000000..72994f6 --- /dev/null +++ b/src/scripts/utils/random.gd.uid @@ -0,0 +1 @@ +uid://1js68qt8w0mv diff --git a/src/userinterface/dash/account_create.gd b/src/userinterface/dash/account_create.gd new file mode 100644 index 0000000..81b7f4b --- /dev/null +++ b/src/userinterface/dash/account_create.gd @@ -0,0 +1,60 @@ +# --- License +# File: /client/src/userinterface/dash/account_create.gd +# Project: OpenMinerva +# Created Date: 28 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control + +var _page_names = [] +@onready var create = get_node("Create") +@onready var select_oauth_btn = get_node("Create/SelectMethod/Container/OAuth") +@onready var create_oauth_btn = get_node("Create/OAuth/Create/HBoxContainer/ConfirmCreateAccount") +@onready var create_oauth_back_btn = get_node("Create/OAuth/Create/HBoxContainer/CreateAccountBack") + +func _ready(): + _get_pages() + select_oauth_btn.pressed.connect(_display_login_route.bind("OAuth")) + + create_oauth_btn.pressed.connect(_create_oauth) + create_oauth_back_btn.pressed.connect(_display_login_route.bind("SelectMethod")) + return + +func _display_oauth(): + return + +func _display_login_route(page_name: String): + if page_name not in _page_names: + GlobalLogger.logs("Tried to display an invalid login route.", Enum.LogLevel.WARNING) + return + + for page in create.get_children(): + if page.name == page_name: + page.visible = true + continue + + page.visible = false + return + +func _get_pages() -> void: + for page in create.get_children(): + _page_names.append(page.name) + return + +func _create_oauth() -> void: + var display_name = get_node("Create/OAuth/Create/VBoxContainer3/CADisplayName").text + var account_server = get_node("Create/OAuth/Create/VBoxContainer3/CAAccountServer").text + + var account = { + "display_name": display_name, + "account_server": account_server + } + + GlobalAccount.create(account, "oauth") + + Events.emit_signal("dash_switch_tab", "AccountDisplay") + + return diff --git a/src/userinterface/dash/account_create.gd.uid b/src/userinterface/dash/account_create.gd.uid new file mode 100644 index 0000000..1866a83 --- /dev/null +++ b/src/userinterface/dash/account_create.gd.uid @@ -0,0 +1 @@ +uid://cchqs4c1p1prd diff --git a/src/userinterface/dash/account_list.gd b/src/userinterface/dash/account_list.gd new file mode 100644 index 0000000..14e47aa --- /dev/null +++ b/src/userinterface/dash/account_list.gd @@ -0,0 +1,69 @@ +# --- License +# File: /client/src/userinterface/dash/account_list.gd +# Project: OpenMinerva +# Created Date: 28 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control + +@onready var _account_template = get_node("Templates/Account") +@onready var _account_list = get_node("List/ScrollContainer/AccountList") +@onready var _create_account_button = get_node("List/HBoxContainer/NewAccount") + +func _ready(): + _display_account_lists() + _create_account_button.pressed.connect(Events.emit_signal.bind("dash_switch_tab", "AccountCreate")) + + Events.dash_set_state.connect(_handle_dash_set_state) + Events.dash_account_list_loaded.connect(_handle_account_list_loaded) + return + +func _clear_account_listings() -> void: + for child in _account_list.get_children(): + child.queue_free() + return + +func _handle_dash_set_state(state: bool) -> void: + if state == false: + return + + _clear_account_listings() + _display_account_lists() + return + +func _handle_account_list_loaded() -> void: + _clear_account_listings() + _display_account_lists() + return + +func _display_account_lists() -> void: + var _list = GlobalAccount.get_all() + + if len(_list) == 0: + GlobalLogger.logs("No accounts to display.") + return + + for account in _list: + var _account_listing = _account_template.duplicate() + + var _username_node = _account_listing.get_node("MarginContainer/HBoxContainer/VBoxContainer/Username") + var _account_server_node = _account_listing.get_node("MarginContainer/HBoxContainer/VBoxContainer/AccountServer") + var _login_button = _account_listing.get_node("MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/Login") + var _configure_button = _account_listing.get_node("MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/Configure") + var _remove_button = _account_listing.get_node("MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/Remove") + + # Display + if account.type == "oauth": + _username_node.text = account.display_name + _account_server_node.text = account.account_server + + # Event listeners + _login_button.pressed.connect(GlobalAccount.use.bind(account.id)) + _remove_button.pressed.connect(GlobalAccount.remove.bind(account.id)) + + _account_list.add_child(_account_listing) + GlobalLogger.logs("Added account '%s' to the login list." % account.id) + return diff --git a/src/userinterface/dash/account_list.gd.uid b/src/userinterface/dash/account_list.gd.uid new file mode 100644 index 0000000..e8d0fca --- /dev/null +++ b/src/userinterface/dash/account_list.gd.uid @@ -0,0 +1 @@ +uid://ftk8qksc10qg diff --git a/src/userinterface/dash/apps.gd b/src/userinterface/dash/apps.gd new file mode 100644 index 0000000..2f8863f --- /dev/null +++ b/src/userinterface/dash/apps.gd @@ -0,0 +1,10 @@ +# --- License +# File: /client/src/userinterface/dash/apps.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control \ No newline at end of file diff --git a/src/userinterface/dash/apps.gd.uid b/src/userinterface/dash/apps.gd.uid new file mode 100644 index 0000000..9526fc4 --- /dev/null +++ b/src/userinterface/dash/apps.gd.uid @@ -0,0 +1 @@ +uid://di7li0digjpa8 diff --git a/src/userinterface/dash/contacts.gd b/src/userinterface/dash/contacts.gd new file mode 100644 index 0000000..c54ac2a --- /dev/null +++ b/src/userinterface/dash/contacts.gd @@ -0,0 +1,10 @@ +# --- License +# File: /client/src/userinterface/dash/contacts.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control diff --git a/src/userinterface/dash/contacts.gd.uid b/src/userinterface/dash/contacts.gd.uid new file mode 100644 index 0000000..5468a3a --- /dev/null +++ b/src/userinterface/dash/contacts.gd.uid @@ -0,0 +1 @@ +uid://crlujtfv2bh1w diff --git a/src/userinterface/dash/debug.gd b/src/userinterface/dash/debug.gd new file mode 100644 index 0000000..3c4ba87 --- /dev/null +++ b/src/userinterface/dash/debug.gd @@ -0,0 +1,10 @@ +# --- License +# File: /client/src/userinterface/dash/debug.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control \ No newline at end of file diff --git a/src/userinterface/dash/debug.gd.uid b/src/userinterface/dash/debug.gd.uid new file mode 100644 index 0000000..669c34b --- /dev/null +++ b/src/userinterface/dash/debug.gd.uid @@ -0,0 +1 @@ +uid://bwcvodv6u21jm diff --git a/src/userinterface/dash/exit.gd b/src/userinterface/dash/exit.gd new file mode 100644 index 0000000..75b8331 --- /dev/null +++ b/src/userinterface/dash/exit.gd @@ -0,0 +1,29 @@ +# --- License +# File: /client/src/userinterface/dash/exit.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control +@onready var _cancel_button = get_node("VBoxContainer/HBoxContainer/Cancel") +@onready var _exit_button = get_node("VBoxContainer/HBoxContainer/Exit") + +func _ready(): + _cancel_button.pressed.connect(_handle_cancel_pressed) + _exit_button.pressed.connect(_handle_exit_pressed) + return + +func _handle_cancel_pressed(): + Events.emit_signal("dash_switch_tab", "Home") + return + +func _handle_exit_pressed(): + # TODO: Save? + # TODO: Sync? + # TODO: Validate database? + # TODO: Prune cache? + get_tree().root.propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST) + return diff --git a/src/userinterface/dash/exit.gd.uid b/src/userinterface/dash/exit.gd.uid new file mode 100644 index 0000000..6bf2d79 --- /dev/null +++ b/src/userinterface/dash/exit.gd.uid @@ -0,0 +1 @@ +uid://e1djdo6st2a7 diff --git a/src/userinterface/dash/home.gd b/src/userinterface/dash/home.gd new file mode 100644 index 0000000..9166e48 --- /dev/null +++ b/src/userinterface/dash/home.gd @@ -0,0 +1,69 @@ +# --- License +# File: /client/src/userinterface/dash/home.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control + +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") +@onready var scene_m = get_tree().current_scene.get_node("SceneManager") + +@onready var account_card_container = get_node("HBoxContainer/VBoxContainer/AccountDisplay") +@onready var storage_card_container = get_node("HBoxContainer/VBoxContainer/StorageDisplay") +@onready var active_sessions_container = get_node("HBoxContainer/VBoxContainer/ActiveSessions") +@onready var session_card_container = get_node("HBoxContainer/VBoxContainer3/SessionDisplay") + +@onready var active_session_template = get_node("Templates/ActiveSessionButton") + +func _ready(): + account_card_container.get_node("Button").pressed.connect(Events.emit_signal.bind("dash_switch_tab", "AccountDisplay")) + + Events.connect("dash_active_account_changed", _handle_active_account_changed) + Events.connect("dash_storage_changed", _handle_storage_changed) + Events.connect("dash_session_changed", _handle_session_changed) + + Events.connect("session_joined", _display_active_sessions) + Events.connect("session_left", _display_active_sessions) + + _display_active_sessions() + + return + +func _handle_active_account_changed(account: Dictionary) -> void: + account_card_container.get_node("MarginContainer/HBoxContainer/VBoxContainer/Username").text = account.get("username") if account.get("username") else account.get("display_name") + account_card_container.get_node("MarginContainer/HBoxContainer/VBoxContainer/AccountServer").text = account.account_server + return + +func _handle_storage_changed(storage_data: Dictionary) -> void: + storage_card_container.get_node("MarginContainer/VBoxContainer/ProgressBar").value = storage_data.used_percent + storage_card_container.get_node("MarginContainer/VBoxContainer/Label2").text = "%s GiB used of %s GiB" % [storage_data.used_gigs, storage_data.total_gigs] + return + +func _handle_session_changed(session_data: Dictionary) -> void: + session_card_container.get_node("MarginContainer/HBoxContainer/VBoxContainer/SessionName").text = session_data.session_name + return + +func _display_active_sessions() -> void: + for node in active_sessions_container.get_node("MarginContainer/VBoxContainer").get_children(): + if node is not Label: + node.queue_free() + + var _sessions = network_m.get_connected_sessions() + + for session in _sessions: + var _entry = active_session_template.duplicate() + var _entry_label = _entry.get_node("HBoxContainer/Join") + var _entry_close = _entry.get_node("HBoxContainer/Close") + + _entry_label.text = session.id + _entry_label.pressed.connect(scene_m.set_active_session.bind(session.id)) + _entry_close.pressed.connect(network_m.leave_server.bind(session.id)) + + active_sessions_container.get_node("MarginContainer/VBoxContainer").add_child(_entry) + + # TODO: When button is pressed, focus that session + return diff --git a/src/userinterface/dash/home.gd.uid b/src/userinterface/dash/home.gd.uid new file mode 100644 index 0000000..b2c4911 --- /dev/null +++ b/src/userinterface/dash/home.gd.uid @@ -0,0 +1 @@ +uid://bwcgqn33pn62o diff --git a/src/userinterface/dash/hud.tscn b/src/userinterface/dash/hud.tscn new file mode 100644 index 0000000..415a470 --- /dev/null +++ b/src/userinterface/dash/hud.tscn @@ -0,0 +1,2729 @@ +[gd_scene format=3 uid="uid://ckl5gw0xbduiv"] + +[ext_resource type="Texture2D" uid="uid://7mgxvhy58nhp" path="res://resources/icons/home.svg" id="1_3b2ci"] +[ext_resource type="Theme" uid="uid://bg2nbganyysst" path="res://openminerva_default.tres" id="1_cx7w0"] +[ext_resource type="Script" uid="uid://cbl7rmnjxarba" path="res://userinterface/dash/master.gd" id="1_ugu7k"] +[ext_resource type="Texture2D" uid="uid://coqi7w7inqyv1" path="res://resources/icons/account.svg" id="2_bucoy"] +[ext_resource type="Texture2D" uid="uid://crrjk7c2g4q55" path="res://resources/icons/art.svg" id="3_6vaiq"] +[ext_resource type="Script" uid="uid://bwcgqn33pn62o" path="res://userinterface/dash/home.gd" id="3_bnkjl"] +[ext_resource type="Texture2D" uid="uid://c7ofnclcof0ud" path="res://resources/icons/world.svg" id="3_v4ccx"] +[ext_resource type="StyleBox" uid="uid://cxx1q037xaswi" path="res://openminerva_darkpanel.tres" id="4_6rlgq"] +[ext_resource type="Texture2D" uid="uid://dfckge3u00boi" path="res://resources/icons/contacts.svg" id="4_cyduv"] +[ext_resource type="Texture2D" uid="uid://cnhy47i7saehq" path="res://resources/icons/environment.svg" id="4_tkvwg"] +[ext_resource type="Texture2D" uid="uid://rdp5i1i18jus" path="res://resources/icons/search.svg" id="4_wfski"] +[ext_resource type="Texture2D" uid="uid://fkfiymss8p57" path="res://resources/icons/science.svg" id="5_hu6eh"] +[ext_resource type="Texture2D" uid="uid://br6hpysqvuhek" path="res://resources/icons/inventory.svg" id="5_r82dk"] +[ext_resource type="Script" uid="uid://ftk8qksc10qg" path="res://userinterface/dash/account_list.gd" id="5_upsw6"] +[ext_resource type="Script" uid="uid://cchqs4c1p1prd" path="res://userinterface/dash/account_create.gd" id="6_3rk53"] +[ext_resource type="Texture2D" uid="uid://dp5u16rwxd0bp" path="res://resources/icons/apps.svg" id="6_6ybpt"] +[ext_resource type="Texture2D" uid="uid://b0qo67fgifqe8" path="res://resources/icons/circus.svg" id="6_hu6eh"] +[ext_resource type="Texture2D" uid="uid://basvqob80u3l6" path="res://resources/icons/settings.svg" id="7_tixj4"] +[ext_resource type="Script" uid="uid://ccd2fw88a52mp" path="res://userinterface/dash/sessions.gd" id="8_4ahgm"] +[ext_resource type="Texture2D" uid="uid://b8fed1h3aqw1s" path="res://resources/icons/debug.svg" id="8_fgqsp"] +[ext_resource type="Texture2D" uid="uid://8fwxg1ipf0fp" path="res://resources/icons/exit.svg" id="9_amc1p"] +[ext_resource type="Texture2D" uid="uid://ckoajckje5mq2" path="res://resources/icons/edit.svg" id="9_xdslj"] +[ext_resource type="Texture2D" uid="uid://ddchrocw45muh" path="res://resources/icons/flowchart.svg" id="12_hq11n"] +[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 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6vaiq"] +bg_color = Color(0, 0, 0, 1) +expand_margin_left = 2.0 +expand_margin_top = 2.0 +expand_margin_right = 2.0 +expand_margin_bottom = 2.0 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wfski"] +bg_color = Color(0, 0.73333335, 1, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bucoy"] +bg_color = Color(1, 0, 0, 1) +corner_radius_top_left = 100 +corner_radius_top_right = 100 +corner_radius_bottom_right = 100 +corner_radius_bottom_left = 100 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dr1q3"] +bg_color = Color(0, 0.73333335, 1, 1) +corner_radius_top_left = 100 +corner_radius_top_right = 100 +corner_radius_bottom_right = 100 +corner_radius_bottom_left = 100 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_shwvw"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_3ff87"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xdslj"] +bg_color = Color(2.466701e-07, 0.13600668, 0.20061767, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6rlgq"] +bg_color = Color(0.099985994, 0.09998601, 0.099985965, 1) + +[sub_resource type="Theme" id="Theme_q6jcb"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hq11n"] +bg_color = Color(0.60038817, 0.60038817, 0.60038817, 0) +draw_center = false + +[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_hq11n"] + +[node name="Hud" type="Control" unique_id=1053137144] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_ugu7k") + +[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1988156212] +layout_mode = 1 +anchors_preset = 15 +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 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=1376103708] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Master" type="Panel" parent="MarginContainer/VBoxContainer" unique_id=911098732] +layout_mode = 2 +size_flags_vertical = 3 +theme = ExtResource("1_cx7w0") + +[node name="Home" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=675278383] +layout_mode = 1 +anchors_preset = 15 +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("3_bnkjl") + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home" unique_id=581087957] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer" unique_id=2138031361] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 20 + +[node name="AccountDisplay" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer" unique_id=383858349] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay" unique_id=2024761038] +layout_mode = 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 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer" unique_id=133275778] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="Container" type="CenterContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer/HBoxContainer" unique_id=539177141] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer/HBoxContainer/Container" unique_id=1941945009] +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer/HBoxContainer/Container/AspectRatioContainer" unique_id=1382039916] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 +stretch_mode = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer/HBoxContainer" unique_id=1966375622] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Username" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer/HBoxContainer/VBoxContainer" unique_id=321531249] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_font_sizes/normal_font_size = 20 +bbcode_enabled = true +text = "[color=gray]No Account[/color]" +fit_content = true + +[node name="AccountServer" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay/MarginContainer/HBoxContainer/VBoxContainer" unique_id=881790720] +layout_mode = 2 +theme_override_colors/font_color = Color(0.7977378, 0.7977378, 0.7977378, 1) +theme_override_font_sizes/font_size = 14 + +[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/AccountDisplay" unique_id=58113697] +layout_mode = 2 +theme_override_styles/normal = SubResource("StyleBoxFlat_kgckq") + +[node name="StorageDisplay" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer" unique_id=1169784328] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/StorageDisplay" unique_id=1944750319] +layout_mode = 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 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/StorageDisplay/MarginContainer" unique_id=1039592827] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/StorageDisplay/MarginContainer/VBoxContainer" unique_id=21628856] +layout_mode = 2 +text = "Used Storage" +horizontal_alignment = 1 + +[node name="ProgressBar" type="ProgressBar" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/StorageDisplay/MarginContainer/VBoxContainer" unique_id=1535856991] +layout_mode = 2 +theme_override_styles/background = SubResource("StyleBoxFlat_6vaiq") +theme_override_styles/fill = SubResource("StyleBoxFlat_wfski") +value = 25.0 + +[node name="Label2" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/StorageDisplay/MarginContainer/VBoxContainer" unique_id=749951536] +layout_mode = 2 +text = "2.5 GiB / 5 GiB" +horizontal_alignment = 1 + +[node name="ActiveSessions" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer" unique_id=1874646451] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/ActiveSessions" unique_id=591364507] +layout_mode = 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 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/ActiveSessions/MarginContainer" unique_id=1770756004] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer/ActiveSessions/MarginContainer/VBoxContainer" unique_id=66943091] +layout_mode = 2 +text = "Active Sessions" +horizontal_alignment = 1 + +[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer" unique_id=1873874176] +custom_minimum_size = Vector2(1000, 0) +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VBoxContainer3" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer" unique_id=1863434109] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="SessionDisplay" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3" unique_id=368133605] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay" unique_id=993828895] +layout_mode = 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 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer" unique_id=1881644944] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer" unique_id=2054814657] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="SessionName" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1586628075] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Session Name" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1575315261] +layout_mode = 2 +theme_override_constants/separation = 25 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer" unique_id=408001748] +layout_mode = 2 + +[node name="Panel" type="Panel" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer" unique_id=770415475] +custom_minimum_size = Vector2(20, 20) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_bucoy") + +[node name="AccountServer2" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer" unique_id=415378036] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Spectator" + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer" unique_id=1848382474] +layout_mode = 2 + +[node name="Panel" type="Panel" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer2" unique_id=1133926083] +custom_minimum_size = Vector2(20, 20) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_dr1q3") + +[node name="AccountServer2" type="Label" parent="MarginContainer/VBoxContainer/Master/Home/HBoxContainer/VBoxContainer3/SessionDisplay/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer/HBoxContainer2" unique_id=36948057] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "157 ms" + +[node name="Templates" type="Control" parent="MarginContainer/VBoxContainer/Master/Home" unique_id=725523853] +visible = false +layout_mode = 2 + +[node name="ActiveSessionButton" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Home/Templates" unique_id=1090596649] +layout_mode = 0 +offset_left = 20.0 +offset_top = 274.0 +offset_right = 436.0 +offset_bottom = 297.0 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Home/Templates/ActiveSessionButton" unique_id=1736548450] +layout_mode = 2 + +[node name="Join" type="Button" parent="MarginContainer/VBoxContainer/Master/Home/Templates/ActiveSessionButton/HBoxContainer" unique_id=375184485] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Close" type="Button" parent="MarginContainer/VBoxContainer/Master/Home/Templates/ActiveSessionButton/HBoxContainer" unique_id=797785460] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "X" + +[node name="AccountDisplay" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=1554068867] +visible = false +layout_mode = 1 +anchors_preset = 15 +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("5_upsw6") + +[node name="List" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay" unique_id=1462685937] +custom_minimum_size = Vector2(500, 0) +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/List" unique_id=1122975855] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="AccountList" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/List/ScrollContainer" unique_id=156940286] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/List" unique_id=1849303399] +layout_mode = 2 + +[node name="NewAccount" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/List/HBoxContainer" unique_id=1573695902] +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +size_flags_horizontal = 3 +text = "New Account" + +[node name="Templates" type="Control" parent="MarginContainer/VBoxContainer/Master/AccountDisplay" unique_id=1889394597] +visible = false +layout_mode = 2 + +[node name="Account" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates" unique_id=1270586344] +layout_mode = 0 +offset_left = 690.0 +offset_right = 1190.0 +offset_bottom = 106.0 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account" unique_id=1691936720] +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/AccountDisplay/Templates/Account/MarginContainer" unique_id=877904178] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="AspectRatioContainer2" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer" unique_id=905758712] +custom_minimum_size = Vector2(75, 50) +layout_mode = 2 + +[node name="ProfilePicture" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/AspectRatioContainer2" unique_id=1092421664] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer" unique_id=1076341750] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Username" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1381439920] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Account username" + +[node name="AccountServer" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1458428898] +layout_mode = 2 +theme_override_colors/font_color = Color(0.54, 0.54, 0.54, 1) +theme_override_font_sizes/font_size = 14 +text = "https://accounts.openminerva.org" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1566413473] +layout_mode = 2 + +[node name="Login" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer" unique_id=249423624] +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Login" + +[node name="Configure" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer" unique_id=1395695382] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Configure" + +[node name="Remove" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountDisplay/Templates/Account/MarginContainer/HBoxContainer/VBoxContainer/HBoxContainer" unique_id=1778567672] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Remove" + +[node name="AccountCreate" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=476222424] +visible = false +layout_mode = 1 +anchors_preset = 15 +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("6_3rk53") + +[node name="Create" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate" unique_id=1338768232] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_styles/panel = ExtResource("4_6rlgq") + +[node name="UsernamePassword" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create" unique_id=83231436] +visible = false +layout_mode = 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 + +[node name="Create" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword" unique_id=1027080501] +custom_minimum_size = Vector2(500, 0) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_constants/separation = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create" unique_id=44400410] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/VBoxContainer" unique_id=1919638633] +layout_mode = 2 +text = "Username" + +[node name="CAUsername" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/VBoxContainer" unique_id=925907901] +layout_mode = 2 +text = "u" +placeholder_text = "Username" + +[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create" unique_id=1696617473] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/VBoxContainer2" unique_id=1480122353] +layout_mode = 2 +text = "Password" + +[node name="CAPassword" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/VBoxContainer2" unique_id=8617011] +layout_mode = 2 +text = "i" +placeholder_text = "Password" +secret = true + +[node name="VBoxContainer3" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create" unique_id=2026306086] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/VBoxContainer3" unique_id=252202476] +layout_mode = 2 +text = "Account Server" + +[node name="CAAccountServer" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/VBoxContainer3" unique_id=783276815] +layout_mode = 2 +text = "http://localhost" +placeholder_text = "https://accounts.openminerva.org" + +[node name="CARememberMe" type="CheckButton" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create" unique_id=1627203869] +layout_mode = 2 +button_pressed = true +text = "Remember Me" +flat = true + +[node name="CALocalAccount" type="CheckButton" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create" unique_id=869022482] +layout_mode = 2 +text = "Local Account" +flat = true + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create" unique_id=700361537] +layout_mode = 2 + +[node name="CreateAccountBack" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/HBoxContainer" unique_id=2040294501] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Back" + +[node name="ConfirmCreateAccount" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/UsernamePassword/Create/HBoxContainer" unique_id=1706811869] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Create" + +[node name="OAuth" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create" unique_id=1033385490] +visible = false +layout_mode = 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 + +[node name="Create" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth" unique_id=1431764238] +custom_minimum_size = Vector2(500, 0) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_constants/separation = 10 + +[node name="VBoxContainer3" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create" unique_id=1971820391] +layout_mode = 2 + +[node name="DisplayName" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create/VBoxContainer3" unique_id=1545576882] +layout_mode = 2 +text = "Display Name" + +[node name="CADisplayName" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create/VBoxContainer3" unique_id=1868732970] +layout_mode = 2 +text = "Anonymous" +placeholder_text = "Display Name" + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create/VBoxContainer3" unique_id=1010954308] +layout_mode = 2 +text = "Account Server" + +[node name="CAAccountServer" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create/VBoxContainer3" unique_id=404007920] +layout_mode = 2 +text = "http://localhost:40400" +placeholder_text = "https://accounts.openminerva.org" + +[node name="CARememberMe" type="CheckButton" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create" unique_id=1759228467] +layout_mode = 2 +button_pressed = true +text = "Remember Me" +flat = true + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create" unique_id=162707587] +layout_mode = 2 + +[node name="CreateAccountBack" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create/HBoxContainer" unique_id=729130015] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Back" + +[node name="ConfirmCreateAccount" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/OAuth/Create/HBoxContainer" unique_id=840318543] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Sign In" + +[node name="SelectMethod" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create" unique_id=1416964800] +layout_mode = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 + +[node name="Container" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/SelectMethod" unique_id=1882060538] +custom_minimum_size = Vector2(500, 0) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +theme_override_constants/separation = 10 + +[node name="OAuth" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/SelectMethod/Container" unique_id=2044513720] +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +text = "OAuth2" + +[node name="UsernamePassword" type="Button" parent="MarginContainer/VBoxContainer/Master/AccountCreate/Create/SelectMethod/Container" unique_id=1669994196] +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +disabled = true +text = "Username + Password" + +[node name="Sessions" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=2135749463] +visible = false +layout_mode = 1 +anchors_preset = 15 +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("8_4ahgm") + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Sessions" unique_id=1830116526] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer" unique_id=687228975] +custom_minimum_size = Vector2(400, 0) +layout_mode = 2 +theme_override_constants/separation = 5 + +[node name="Art" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer" unique_id=8787651] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Art" unique_id=1704752065] +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/Sessions/HBoxContainer/VBoxContainer/Art/MarginContainer" unique_id=723755098] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Art/MarginContainer/HBoxContainer" unique_id=1631358950] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Art/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=421186792] +layout_mode = 2 +texture = ExtResource("3_6vaiq") +expand_mode = 1 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Art/MarginContainer/HBoxContainer" unique_id=1515682350] +layout_mode = 2 +text = "Art" + +[node name="Environment" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer" unique_id=1460467440] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Environment" unique_id=1960994802] +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/Sessions/HBoxContainer/VBoxContainer/Environment/MarginContainer" unique_id=1078127539] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Environment/MarginContainer/HBoxContainer" unique_id=43665461] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Environment/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1826253390] +layout_mode = 2 +texture = ExtResource("4_tkvwg") +expand_mode = 1 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Environment/MarginContainer/HBoxContainer" unique_id=1155183315] +layout_mode = 2 +text = "Environment" + +[node name="Science" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer" unique_id=508195085] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Science" unique_id=1375241018] +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/Sessions/HBoxContainer/VBoxContainer/Science/MarginContainer" unique_id=1476810827] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Science/MarginContainer/HBoxContainer" unique_id=588549540] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Science/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1091425181] +layout_mode = 2 +texture = ExtResource("5_hu6eh") +expand_mode = 1 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Science/MarginContainer/HBoxContainer" unique_id=1662831340] +layout_mode = 2 +text = "Science" + +[node name="Games" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer" unique_id=369382614] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Games" unique_id=1896102711] +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/Sessions/HBoxContainer/VBoxContainer/Games/MarginContainer" unique_id=1907169258] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Games/MarginContainer/HBoxContainer" unique_id=2118476409] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Games/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1606136766] +layout_mode = 2 +texture = ExtResource("6_hu6eh") +expand_mode = 1 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer/Games/MarginContainer/HBoxContainer" unique_id=1728001482] +layout_mode = 2 +text = "Games" + +[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer" unique_id=275712777] +custom_minimum_size = Vector2(1000, 0) +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="Search" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2" unique_id=917704848] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2/Search" unique_id=726470197] +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/Sessions/HBoxContainer/VBoxContainer2/Search/MarginContainer" unique_id=65014822] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2/Search/MarginContainer/HBoxContainer" unique_id=903609424] +custom_minimum_size = Vector2(30, 0) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2/Search/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1429839469] +layout_mode = 2 +texture = ExtResource("4_wfski") +expand_mode = 4 + +[node name="LineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2/Search/MarginContainer/HBoxContainer" unique_id=670617857] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Search..." +emoji_menu_enabled = false +clear_button_enabled = true +middle_mouse_paste_enabled = false +flat = true +caret_blink = true +icon_expand_mode = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2" unique_id=242318771] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="GridContainer" type="GridContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/HBoxContainer/VBoxContainer2/ScrollContainer" unique_id=164218999] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/h_separation = 15 +theme_override_constants/v_separation = 15 +columns = 4 + +[node name="Templates" type="Control" parent="MarginContainer/VBoxContainer/Master/Sessions" unique_id=2135764135] +visible = false +layout_mode = 2 + +[node name="WorldListing" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates" unique_id=1301965468] +custom_minimum_size = Vector2(350, 270) +layout_mode = 0 +offset_left = 420.0 +offset_top = 56.0 +offset_right = 770.0 +offset_bottom = 326.0 + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing" unique_id=265222407] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing/PanelContainer" unique_id=2080859872] +layout_mode = 2 +mouse_behavior_recursive = 2 + +[node name="MarginContainer2" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing/PanelContainer/VBoxContainer" unique_id=490931647] +custom_minimum_size = Vector2(0, 200) +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 2 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 2 +theme_override_constants/margin_bottom = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing/PanelContainer/VBoxContainer/MarginContainer2" unique_id=1546006054] +layout_mode = 2 +size_flags_vertical = 3 +ratio = 1.7778 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing/PanelContainer/VBoxContainer/MarginContainer2/AspectRatioContainer" unique_id=1678639094] +layout_mode = 2 +expand_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing/PanelContainer/VBoxContainer" unique_id=1720674229] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 15 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing/PanelContainer/VBoxContainer/MarginContainer" unique_id=2066703758] +custom_minimum_size = Vector2(200, 20) +layout_mode = 2 +size_flags_vertical = 1 +mouse_filter = 1 +theme_override_font_sizes/font_size = 18 +autowrap_mode = 3 +text_overrun_behavior = 3 +max_lines_visible = 2 + +[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/Master/Sessions/Templates/WorldListing" unique_id=1779962200] +layout_mode = 2 +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 +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("14_3ff87") + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance" unique_id=413032466] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer" unique_id=1137764538] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer" unique_id=90422462] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2" unique_id=1857065201] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer" unique_id=193436950] +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="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/InstanceName" unique_id=1002302148] +layout_mode = 2 +text = "Instance Name" + +[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" + +[node name="PanelContainer2" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2" unique_id=1338599011] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer2/PanelContainer2" unique_id=372944118] +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="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/InstanceDescriptionContainer" unique_id=1778162350] +layout_mode = 2 +text = "Instance Description" + +[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" +placeholder_text = "Instance Description" + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer" unique_id=725794580] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="InstanceSettings" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer" unique_id=1058028038] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceSettings" unique_id=1068744384] +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/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceSettings/MarginContainer" unique_id=91242726] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceSettings/MarginContainer/HBoxContainer" unique_id=1980940677] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Max connected users" + +[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 +value = 1.0 +suffix = "Users" + +[node name="InstancePrivacy" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer" unique_id=418692061] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstancePrivacy" unique_id=520825558] +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="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/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/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/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/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/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/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/InstancePrivacyContainer" unique_id=1625370876] +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +toggle_mode = true +text = "Invite" + +[node name="InstanceAdvertisement" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer" unique_id=1453042159] +visible = false +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceAdvertisement" unique_id=710014992] +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="Instance Name" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceAdvertisement/MarginContainer" unique_id=339732048] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceAdvertisement/MarginContainer/Instance Name" unique_id=924196938] +layout_mode = 2 +text = "Instance Advertisement" + +[node name="CheckButton" type="CheckButton" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceAdvertisement/MarginContainer/Instance Name" unique_id=476328324] +layout_mode = 2 +theme = ExtResource("1_cx7w0") +text = "Enable instance advertisement" + +[node name="ItemList" type="ItemList" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer/VBoxContainer/InstanceAdvertisement/MarginContainer/Instance Name" unique_id=645896803] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/cursor_unfocused = SubResource("StyleBoxEmpty_shwvw") +theme_override_styles/cursor = SubResource("StyleBoxEmpty_3ff87") +select_mode = 2 +item_count = 3 +item_0/text = "sessions.openminerva.org" +item_1/text = "sessions.otherservice1.com" +item_2/text = "sessions.otherservice2.com" + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer" unique_id=1107704271] +custom_minimum_size = Vector2(0, 50) +layout_mode = 2 + +[node name="SaveChanges" type="Button" parent="MarginContainer/VBoxContainer/Master/Instance/VBoxContainer/HBoxContainer2" unique_id=175903443] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Save Changes" + +[node name="Contacts" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=1935681612] +visible = false +layout_mode = 1 +anchors_preset = 15 +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("15_gll50") + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts" unique_id=609852715] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="PeopleList" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer" unique_id=495048038] +custom_minimum_size = Vector2(400, 0) +layout_mode = 2 + +[node name="Search" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList" unique_id=1778670362] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/Search" unique_id=1330638996] +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/Contacts/HBoxContainer/PeopleList/Search/MarginContainer" unique_id=552152395] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/Search/MarginContainer/HBoxContainer" unique_id=1034644707] +custom_minimum_size = Vector2(30, 0) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/Search/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1362549755] +layout_mode = 2 +texture = ExtResource("4_wfski") +expand_mode = 4 + +[node name="LineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/Search/MarginContainer/HBoxContainer" unique_id=532774583] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Search..." +emoji_menu_enabled = false +clear_button_enabled = true +middle_mouse_paste_enabled = false +flat = true +caret_blink = true +icon_expand_mode = 1 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList" unique_id=1230683167] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="PeopleContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer" unique_id=1138665112] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Contact" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1376762730] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact" unique_id=1957164778] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact/MarginContainer" unique_id=530564555] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact/MarginContainer/HBoxContainer" unique_id=2037557771] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=213812101] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact/MarginContainer/HBoxContainer" unique_id=123418873] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1963379470] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1790589234] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact2" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1917384234] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2" unique_id=248931094] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2/MarginContainer" unique_id=1105136801] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2/MarginContainer/HBoxContainer" unique_id=1042937322] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=945263631] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2/MarginContainer/HBoxContainer" unique_id=72060891] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1790458992] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact2/MarginContainer/HBoxContainer/VBoxContainer" unique_id=655308943] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact3" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1002559792] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3" unique_id=2110329168] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3/MarginContainer" unique_id=587970321] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3/MarginContainer/HBoxContainer" unique_id=1155569374] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=363334467] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3/MarginContainer/HBoxContainer" unique_id=1291298320] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3/MarginContainer/HBoxContainer/VBoxContainer" unique_id=58329549] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact3/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1652527413] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact4" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1117539841] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4" unique_id=882936545] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4/MarginContainer" unique_id=1983831238] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4/MarginContainer/HBoxContainer" unique_id=1797575450] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1904659656] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4/MarginContainer/HBoxContainer" unique_id=1195268824] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1911785678] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact4/MarginContainer/HBoxContainer/VBoxContainer" unique_id=673745301] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact5" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=785320451] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5" unique_id=488007093] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5/MarginContainer" unique_id=1702189485] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5/MarginContainer/HBoxContainer" unique_id=181066723] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=858326702] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5/MarginContainer/HBoxContainer" unique_id=1622164735] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1525652223] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact5/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1764447998] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact6" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1223741083] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6" unique_id=1886365158] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6/MarginContainer" unique_id=1000938433] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6/MarginContainer/HBoxContainer" unique_id=1412410708] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1838408715] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6/MarginContainer/HBoxContainer" unique_id=602696783] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6/MarginContainer/HBoxContainer/VBoxContainer" unique_id=266752489] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact6/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1455339639] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact7" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=285355074] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7" unique_id=496124961] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7/MarginContainer" unique_id=2125957011] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7/MarginContainer/HBoxContainer" unique_id=1679355086] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1263285591] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7/MarginContainer/HBoxContainer" unique_id=1944026472] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1929228588] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact7/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1247873814] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact8" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1645072851] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8" unique_id=778625893] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8/MarginContainer" unique_id=2038217778] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8/MarginContainer/HBoxContainer" unique_id=1122556879] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1092189046] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8/MarginContainer/HBoxContainer" unique_id=1922829072] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8/MarginContainer/HBoxContainer/VBoxContainer" unique_id=584730902] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact8/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1752589710] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact9" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=2101167709] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9" unique_id=618543179] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9/MarginContainer" unique_id=1966106535] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9/MarginContainer/HBoxContainer" unique_id=2145573781] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1400837203] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9/MarginContainer/HBoxContainer" unique_id=300879442] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1887730979] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact9/MarginContainer/HBoxContainer/VBoxContainer" unique_id=591472536] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact10" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1338233000] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10" unique_id=1193164329] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10/MarginContainer" unique_id=1302175277] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10/MarginContainer/HBoxContainer" unique_id=547714522] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=207327638] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10/MarginContainer/HBoxContainer" unique_id=292040170] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10/MarginContainer/HBoxContainer/VBoxContainer" unique_id=800144392] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact10/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1643413008] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact11" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1413470422] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11" unique_id=2134138405] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11/MarginContainer" unique_id=1392258180] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11/MarginContainer/HBoxContainer" unique_id=643752280] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1015674972] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11/MarginContainer/HBoxContainer" unique_id=157488074] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1826758548] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact11/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1006838147] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact12" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=758373763] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12" unique_id=182319459] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12/MarginContainer" unique_id=1225511965] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12/MarginContainer/HBoxContainer" unique_id=833329707] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=288160845] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12/MarginContainer/HBoxContainer" unique_id=956311638] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12/MarginContainer/HBoxContainer/VBoxContainer" unique_id=291815739] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact12/MarginContainer/HBoxContainer/VBoxContainer" unique_id=142605013] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact13" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=110651033] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13" unique_id=722964887] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13/MarginContainer" unique_id=1681556310] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13/MarginContainer/HBoxContainer" unique_id=1064996853] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=273745914] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13/MarginContainer/HBoxContainer" unique_id=4372347] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1378094667] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact13/MarginContainer/HBoxContainer/VBoxContainer" unique_id=959498741] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact14" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=1765333158] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14" unique_id=1834888416] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14/MarginContainer" unique_id=1667692749] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14/MarginContainer/HBoxContainer" unique_id=296732179] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=246164239] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14/MarginContainer/HBoxContainer" unique_id=1729440618] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1697000745] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact14/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1424300167] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact15" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=608987246] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15" unique_id=68254041] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15/MarginContainer" unique_id=799897191] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15/MarginContainer/HBoxContainer" unique_id=1562246438] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1995651580] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15/MarginContainer/HBoxContainer" unique_id=1071607904] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1418212751] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact15/MarginContainer/HBoxContainer/VBoxContainer" unique_id=345858069] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Contact16" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer" unique_id=976707612] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16" unique_id=1155713213] +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/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16/MarginContainer" unique_id=1444756375] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16/MarginContainer/HBoxContainer" unique_id=1973316902] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=235764742] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16/MarginContainer/HBoxContainer" unique_id=550050191] +layout_mode = 2 + +[node name="Name" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1145402858] +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +text = "Contact Username" + +[node name="OnlineStatus" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/PeopleList/ScrollContainer/PeopleContainer/Contact16/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1572016901] +layout_mode = 2 +theme_override_colors/font_color = Color(0.48570347, 0.48570353, 0.48570347, 1) +theme_override_font_sizes/font_size = 14 +text = "Offline" + +[node name="Messages" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer" unique_id=847433634] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 10 + +[node name="ColorRect" type="ColorRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages" unique_id=478337139] +layout_mode = 2 +size_flags_vertical = 3 +color = Color(0, 0, 0, 0.1) + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect" unique_id=2057620318] +process_mode = 3 +process_thread_group = 2 +process_thread_group_order = 0 +process_thread_messages = 0 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer" unique_id=385628332] +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/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer" unique_id=87127683] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="OurMessage" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=2017134431] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/panel = SubResource("StyleBoxFlat_xdslj") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage" unique_id=227036190] +layout_mode = 2 +size_flags_horizontal = 0 +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage/MarginContainer" unique_id=1039151222] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="OurMessage2" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=890209952] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/panel = SubResource("StyleBoxFlat_xdslj") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage2" unique_id=1387435291] +layout_mode = 2 +size_flags_horizontal = 0 +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage2/MarginContainer" unique_id=309283686] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="ContactMessage3" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1175570338] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_styles/panel = SubResource("StyleBoxFlat_6rlgq") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/ContactMessage3" unique_id=1197627479] +layout_mode = 2 +size_flags_horizontal = 0 +theme = SubResource("Theme_q6jcb") +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/ContactMessage3/MarginContainer" unique_id=1326683365] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="ContactMessage4" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=374331530] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_styles/panel = SubResource("StyleBoxFlat_6rlgq") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/ContactMessage4" unique_id=624597713] +layout_mode = 2 +size_flags_horizontal = 0 +theme = SubResource("Theme_q6jcb") +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/ContactMessage4/MarginContainer" unique_id=1957773675] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="OurMessage3" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1739574120] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/panel = SubResource("StyleBoxFlat_xdslj") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage3" unique_id=1666305266] +layout_mode = 2 +size_flags_horizontal = 0 +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage3/MarginContainer" unique_id=1972401865] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="OurMessage4" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=851557100] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/panel = SubResource("StyleBoxFlat_xdslj") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage4" unique_id=16272845] +layout_mode = 2 +size_flags_horizontal = 0 +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage4/MarginContainer" unique_id=1788643846] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="ContactMessage5" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1504758173] +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_styles/panel = SubResource("StyleBoxFlat_6rlgq") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/ContactMessage5" unique_id=1387589042] +layout_mode = 2 +size_flags_horizontal = 0 +theme = SubResource("Theme_q6jcb") +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/ContactMessage5/MarginContainer" unique_id=493655798] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="OurMessage5" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=1120772320] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/panel = SubResource("StyleBoxFlat_xdslj") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage5" unique_id=1684659846] +layout_mode = 2 +size_flags_horizontal = 0 +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage5/MarginContainer" unique_id=303214310] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="OurMessage6" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer" unique_id=903773050] +layout_mode = 2 +size_flags_horizontal = 8 +theme_override_styles/panel = SubResource("StyleBoxFlat_xdslj") + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage6" unique_id=200480297] +layout_mode = 2 +size_flags_horizontal = 0 +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="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/ColorRect/ScrollContainer/MarginContainer/VBoxContainer/OurMessage6/MarginContainer" unique_id=1921158391] +custom_minimum_size = Vector2(800, 0) +layout_mode = 2 +size_flags_horizontal = 0 +focus_mode = 2 +bbcode_enabled = true +text = "This is a dummy text message made to simulate an actual message... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula." +fit_content = true +scroll_active = false +selection_enabled = true + +[node name="Entry" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages" unique_id=657008232] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/Entry" unique_id=1730343636] +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/Contacts/HBoxContainer/Messages/Entry/MarginContainer" unique_id=2107826533] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/Entry/MarginContainer/HBoxContainer" unique_id=1490241753] +custom_minimum_size = Vector2(30, 0) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/Entry/MarginContainer/HBoxContainer/AspectRatioContainer" unique_id=1577549022] +layout_mode = 2 +texture = ExtResource("9_xdslj") +expand_mode = 4 + +[node name="LineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Messages/Entry/MarginContainer/HBoxContainer" unique_id=1536400606] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/focus = SubResource("StyleBoxFlat_hq11n") +placeholder_text = "Send a message..." +emoji_menu_enabled = false +clear_button_enabled = true +middle_mouse_paste_enabled = false +flat = true +caret_blink = true +icon_expand_mode = 1 + +[node name="Bio" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer" unique_id=1343094501] +custom_minimum_size = Vector2(400, 0) +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio" unique_id=501129495] +layout_mode = 2 + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/HBoxContainer" unique_id=584912787] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/HBoxContainer/AspectRatioContainer" unique_id=917984378] +layout_mode = 2 +texture = ExtResource("2_bucoy") +expand_mode = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/HBoxContainer" unique_id=1940231142] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/HBoxContainer/VBoxContainer" unique_id=1117926921] +layout_mode = 2 +theme_override_font_sizes/font_size = 22 +text = "My contact" + +[node name="Label2" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/HBoxContainer/VBoxContainer" unique_id=1343091322] +layout_mode = 2 +theme_override_colors/font_color = Color(0.6651851, 0.6651851, 0.6651851, 1) +text = "https://accounts.openminerva.org" + +[node name="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio" unique_id=526473658] +layout_mode = 2 +size_flags_vertical = 3 +text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vulputate enim at scelerisque accumsan. Ut aliquam sapien sem, venenatis ullamcorper felis pellentesque vitae. Nulla blandit dictum turpis, id viverra metus fermentum quis. Nulla dictum eros at lorem porta mattis. Nam rutrum et lacus vitae vehicula. Quisque dictum diam nec felis aliquet auctor. Morbi placerat enim at finibus convallis. Curabitur non convallis mi, interdum ultricies augue. + +Ut eget varius libero, vitae porta ipsum. Integer sapien dui, ornare et semper et, sagittis sed libero. Nullam volutpat tempus nunc, id semper orci. Maecenas egestas tincidunt justo at ultricies. Vivamus ut nisi vestibulum, cursus dui non, ullamcorper sem. Cras sed ligula ante. Morbi eget pretium mi. Nullam iaculis sed ligula ut fermentum. Aenean cursus felis nibh, nec malesuada ante bibendum sed. Sed sed congue nunc. Duis purus odio, efficitur in eros sit amet, aliquam laoreet est. Interdum et malesuada fames ac ante ipsum primis in faucibus. Morbi fringilla mattis pharetra. Suspendisse ornare sagittis velit, sit amet rhoncus velit. " +fit_content = true + +[node name="GridContainer" type="GridContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio" unique_id=485642944] +layout_mode = 2 +theme_override_constants/h_separation = 10 +theme_override_constants/v_separation = 10 +columns = 2 + +[node name="Button" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer" unique_id=1404765838] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button" unique_id=1507499146] +layout_mode = 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 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button/MarginContainer" unique_id=89047822] +layout_mode = 2 +text = "Add Contact" +horizontal_alignment = 1 + +[node name="Button2" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer" unique_id=873002479] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button2" unique_id=1360585472] +layout_mode = 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 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button2/MarginContainer" unique_id=155640440] +layout_mode = 2 +text = "Add Friend" +horizontal_alignment = 1 + +[node name="Button3" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer" unique_id=343372836] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button3" unique_id=1968315918] +layout_mode = 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 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button3/MarginContainer" unique_id=1104766121] +layout_mode = 2 +text = "Block User" +horizontal_alignment = 1 + +[node name="Button4" type="PanelContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer" unique_id=2134122873] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button4" unique_id=193076607] +layout_mode = 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 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/Master/Contacts/HBoxContainer/Bio/GridContainer/Button4/MarginContainer" unique_id=1437723678] +layout_mode = 2 +text = "Block Avatar" +horizontal_alignment = 1 + +[node name="Settings" type="MarginContainer" parent="MarginContainer/VBoxContainer/Master" unique_id=362656680] +visible = false +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] +visible = false +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="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Settings/HBoxContainer/Container/Config/SessionServers/VBoxContainer/PanelContainer/MarginContainer2" unique_id=90574433] +layout_mode = 2 + +[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 +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/VBoxContainer/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="Remove" 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 +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_3rk53") + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/Master/Exit" unique_id=143476327] +layout_mode = 2 + +[node name="RichTextLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/Master/Exit/VBoxContainer" unique_id=1484282659] +layout_mode = 2 +size_flags_vertical = 3 +bbcode_enabled = true +text = "[font_size=40]Thank you for using OpenMinerva![/font_size] + +[font_size=30]Come again soon![/font_size]" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/Master/Exit/VBoxContainer" unique_id=611012192] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 + +[node name="Cancel" type="Button" parent="MarginContainer/VBoxContainer/Master/Exit/VBoxContainer/HBoxContainer" unique_id=1332748794] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 26 +text = "Cancel" + +[node name="Exit" type="Button" parent="MarginContainer/VBoxContainer/Master/Exit/VBoxContainer/HBoxContainer" unique_id=1558278195] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 26 +text = "Exit" + +[node name="NavBar" type="Panel" parent="MarginContainer/VBoxContainer" unique_id=1428960291] +custom_minimum_size = Vector2(0, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/NavBar" unique_id=1728990786] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -37.5 +offset_top = -37.5 +offset_right = 37.5 +offset_bottom = 37.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 10 + +[node name="Home" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=344464958] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home" unique_id=2077541755] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home/CenterContainer" unique_id=2146637607] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home/CenterContainer/VBoxContainer" unique_id=95810640] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home/CenterContainer/VBoxContainer/CenterContainer" unique_id=1070773413] +layout_mode = 2 +texture = ExtResource("1_3b2ci") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home/CenterContainer/VBoxContainer" unique_id=1142003763] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home/CenterContainer/VBoxContainer/CenterContainer2" unique_id=862944763] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Home/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1897372055] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Home" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Sessions" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=1779312894] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions" unique_id=1414128034] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions/CenterContainer" unique_id=1222935685] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions/CenterContainer/VBoxContainer" unique_id=1794611439] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions/CenterContainer/VBoxContainer/CenterContainer" unique_id=1479063914] +layout_mode = 2 +texture = ExtResource("12_hq11n") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions/CenterContainer/VBoxContainer" unique_id=739110456] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1283313696] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Sessions/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1726576248] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Sessions" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Instance" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=1199197758] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance" unique_id=1968696261] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance/CenterContainer" unique_id=524033554] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance/CenterContainer/VBoxContainer" unique_id=1551725689] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance/CenterContainer/VBoxContainer/CenterContainer" unique_id=1325848905] +layout_mode = 2 +texture = ExtResource("3_v4ccx") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance/CenterContainer/VBoxContainer" unique_id=1007113927] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1992088652] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Instance/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1993490483] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Instance" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Contacts" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=2052926333] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts" unique_id=247880075] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts/CenterContainer" unique_id=1493676986] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts/CenterContainer/VBoxContainer" unique_id=993865409] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts/CenterContainer/VBoxContainer/CenterContainer" unique_id=939575854] +layout_mode = 2 +texture = ExtResource("4_cyduv") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts/CenterContainer/VBoxContainer" unique_id=327218435] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1753921793] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Contacts/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1239993332] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Contacts" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Inventory" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=1154242515] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory" unique_id=477611878] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory/CenterContainer" unique_id=1029784273] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory/CenterContainer/VBoxContainer" unique_id=1434684623] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory/CenterContainer/VBoxContainer/CenterContainer" unique_id=355063981] +layout_mode = 2 +texture = ExtResource("5_r82dk") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory/CenterContainer/VBoxContainer" unique_id=150216954] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory/CenterContainer/VBoxContainer/CenterContainer2" unique_id=2129474918] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Inventory/CenterContainer/VBoxContainer/CenterContainer2" unique_id=436784442] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Inventory" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Apps" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=2035455386] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps" unique_id=1234174942] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps/CenterContainer" unique_id=2137480982] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps/CenterContainer/VBoxContainer" unique_id=1172100987] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps/CenterContainer/VBoxContainer/CenterContainer" unique_id=1104376878] +layout_mode = 2 +texture = ExtResource("6_6ybpt") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps/CenterContainer/VBoxContainer" unique_id=1849886021] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps/CenterContainer/VBoxContainer/CenterContainer2" unique_id=573515185] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Apps/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1090534310] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Apps" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Settings" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=326520049] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings" unique_id=803794329] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings/CenterContainer" unique_id=639771866] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings/CenterContainer/VBoxContainer" unique_id=42256113] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings/CenterContainer/VBoxContainer/CenterContainer" unique_id=121107602] +layout_mode = 2 +texture = ExtResource("7_tixj4") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings/CenterContainer/VBoxContainer" unique_id=301796775] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1389137151] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Settings/CenterContainer/VBoxContainer/CenterContainer2" unique_id=463454723] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Settings" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Debug" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=239205402] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug" unique_id=2128143430] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug/CenterContainer" unique_id=189425462] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug/CenterContainer/VBoxContainer" unique_id=1205603038] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug/CenterContainer/VBoxContainer/CenterContainer" unique_id=1576187531] +layout_mode = 2 +texture = ExtResource("8_fgqsp") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug/CenterContainer/VBoxContainer" unique_id=1369920063] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug/CenterContainer/VBoxContainer/CenterContainer2" unique_id=229165988] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Debug/CenterContainer/VBoxContainer/CenterContainer2" unique_id=305966204] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Debug" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Exit" type="Button" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer" unique_id=1604895221] +custom_minimum_size = Vector2(75, 75) +layout_mode = 2 +theme = ExtResource("1_cx7w0") +toggle_mode = true + +[node name="CenterContainer" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit" unique_id=1519067308] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit/CenterContainer" unique_id=36849769] +layout_mode = 2 +theme_override_constants/separation = 2 + +[node name="CenterContainer" type="AspectRatioContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit/CenterContainer/VBoxContainer" unique_id=1840012224] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit/CenterContainer/VBoxContainer/CenterContainer" unique_id=806908628] +layout_mode = 2 +texture = ExtResource("9_amc1p") +expand_mode = 1 + +[node name="CenterContainer2" type="CenterContainer" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit/CenterContainer/VBoxContainer" unique_id=817841049] +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit/CenterContainer/VBoxContainer/CenterContainer2" unique_id=71500259] +layout_mode = 2 +texture = SubResource("CompressedTexture2D_hq11n") +expand_mode = 5 +stretch_mode = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/NavBar/HBoxContainer/Exit/CenterContainer/VBoxContainer/CenterContainer2" unique_id=1611628376] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "Exit" +horizontal_alignment = 1 +vertical_alignment = 1 diff --git a/src/userinterface/dash/instance.gd b/src/userinterface/dash/instance.gd new file mode 100644 index 0000000..8b593dc --- /dev/null +++ b/src/userinterface/dash/instance.gd @@ -0,0 +1,106 @@ +# --- License +# File: /client/src/userinterface/dash/instance.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control + +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") + +@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") +@onready var instance_privacy_invite_btn = instance_privacy_container.get_node("InviteOnly") + +@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_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_visual(Enum.PrivacyLevel.INVITE) + return + + +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) + match level: + Enum.PrivacyLevel.INVITE: + _privacy_button_enable(instance_privacy_invite_btn) + Enum.PrivacyLevel.PUBLIC: + _privacy_button_enable(instance_privacy_public_btn) + Enum.PrivacyLevel.CONTACTS: + _privacy_button_enable(instance_privacy_contacts_btn) + Enum.PrivacyLevel.FRIENDS: + _privacy_button_enable(instance_privacy_friends_btn) + + return + +func _privacy_button_disable(node) -> void: + node.button_pressed = false + node.custom_minimum_size = Vector2(0, 40) + return + +func _privacy_button_enable(node) -> void: + node.button_pressed = true + node.custom_minimum_size = Vector2(0, 50) + return + +func update_instance(_instance: Dictionary) -> void: + GlobalLogger.logs("'%s' is not implemented." % get_stack()[0]["function"], Enum.LogLevel.WARNING) + # TODO Session Permissions: Admins can change instance settings. + # Publish changes to the session server. + # Update running server + return + +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 + +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, + } diff --git a/src/userinterface/dash/instance.gd.uid b/src/userinterface/dash/instance.gd.uid new file mode 100644 index 0000000..1f83515 --- /dev/null +++ b/src/userinterface/dash/instance.gd.uid @@ -0,0 +1 @@ +uid://dpo2x4ddv3ftt diff --git a/src/userinterface/dash/inventory.gd b/src/userinterface/dash/inventory.gd new file mode 100644 index 0000000..25fef36 --- /dev/null +++ b/src/userinterface/dash/inventory.gd @@ -0,0 +1,10 @@ +# --- License +# File: /client/src/userinterface/dash/inventory.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control \ No newline at end of file diff --git a/src/userinterface/dash/inventory.gd.uid b/src/userinterface/dash/inventory.gd.uid new file mode 100644 index 0000000..68f6055 --- /dev/null +++ b/src/userinterface/dash/inventory.gd.uid @@ -0,0 +1 @@ +uid://c25o2e0f3y6yx diff --git a/src/userinterface/dash/master.gd b/src/userinterface/dash/master.gd new file mode 100644 index 0000000..c904a18 --- /dev/null +++ b/src/userinterface/dash/master.gd @@ -0,0 +1,59 @@ +# --- License +# File: /client/src/userinterface/dash/master.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control + +var dashboard_tabs = [] +var dashboard_tab_names = [] + +@onready var dash_tab_master_container = get_node("MarginContainer/VBoxContainer/Master") +@onready var dash_nav_master_container = get_node("MarginContainer/VBoxContainer/NavBar/HBoxContainer") + +func _ready(): + _build_page_list() + + Events.connect("dash_set_state", _handle_set_dash_state) + Events.connect("dash_switch_tab", _handle_switch_tab) + + Events.emit_signal("dash_switch_tab", "Home") + +func _build_page_list(): + for child in get_node("MarginContainer/VBoxContainer/Master").get_children(): + child.add_to_group("_dashboard_pages") + dashboard_tab_names.append(child.name) + + for button in get_node("MarginContainer/VBoxContainer/NavBar/HBoxContainer").get_children(): + if button.name not in dashboard_tab_names: + button.disabled = true + continue + button.pressed.connect(Events.emit_signal.bind("dash_switch_tab", button.name)) + +func _handle_set_dash_state(is_open: bool) -> void: + GlobalLogger.logs("Changing dashboard state: '%s'" % is_open) + visible = is_open + +func _handle_switch_tab(target_name: String) -> void: + GlobalLogger.logs("Switching dashboard to page '%s'" % target_name) + + for dash_tab in dash_tab_master_container.get_children(): + dash_tab.visible = false + + for dash_nav_button in dash_nav_master_container.get_children(): + dash_nav_button.button_pressed = false + + if target_name not in dashboard_tab_names: + GlobalLogger.logs("Tried to switch to an invalid dashboard page: '%s'" % target_name, Enum.LogLevel.WARNING) + return + + dash_tab_master_container.get_node(target_name).visible = true + + if dash_nav_master_container.get_node(target_name): + dash_nav_master_container.get_node(target_name).button_pressed = true + + return diff --git a/src/userinterface/dash/master.gd.uid b/src/userinterface/dash/master.gd.uid new file mode 100644 index 0000000..73601b6 --- /dev/null +++ b/src/userinterface/dash/master.gd.uid @@ -0,0 +1 @@ +uid://cbl7rmnjxarba diff --git a/src/userinterface/dash/sessions.gd b/src/userinterface/dash/sessions.gd new file mode 100644 index 0000000..d2dca93 --- /dev/null +++ b/src/userinterface/dash/sessions.gd @@ -0,0 +1,70 @@ +# --- License +# File: /client/src/userinterface/dash/sessions.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Control + +@onready var _template_world_listing = get_node("Templates/WorldListing") +@onready var _world_listing_grid = get_node("HBoxContainer/VBoxContainer2/ScrollContainer/GridContainer") +@onready var network_m = get_tree().current_scene.get_node("NetworkManager") + +# TODO: Keep track of what is different from the current live settings +# When something changes, show icon or indicator of a change. + +func _ready(): + Events.dash_switch_tab.connect(_handle_page_opened) + return + +func _handle_page_opened(page_name: String) -> void: + if page_name != "Sessions": + return + + # TODO: Make a more robust active_account detection mechanism. + if GlobalAccount.active_account == {}: + return + + # TODO: Check if we need to authenticate, if so + 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() + + # Remove all entries in the list + _remove_all_listings() + + # In our flat array, add all sessions to the view + for session in session_list: + insert_world_into_session_listing(session) + return + +func insert_world_into_session_listing(world_data: Dictionary) -> void: + var _world = _template_world_listing.duplicate() + + var world_title = _world.get_node("PanelContainer/VBoxContainer/MarginContainer/Label") + var world_thumbnail = _world.get_node("PanelContainer/VBoxContainer/MarginContainer2/AspectRatioContainer/TextureRect") + + world_title.text = world_data.get("sessionName", "Unknown session name.") + world_thumbnail.set_texture(load(world_data.get("sessionThumbnail", "res://resources/icons/dummy16-9.webp"))) + + _world_listing_grid.add_child(_world) + + # Buttons + var _button = _world.get_node("Button") + + _button.pressed.connect(network_m.join_server.bind(world_data.url, world_data.port)) + + GlobalLogger.logs("Added a session to the session list.") + return + +func _remove_all_listings() -> void: + GlobalLogger.logs("Removed all listings from the session list.", Enum.LogLevel.INFO) + + for session_listing in _world_listing_grid.get_children(): + session_listing.queue_free() + return diff --git a/src/userinterface/dash/sessions.gd.uid b/src/userinterface/dash/sessions.gd.uid new file mode 100644 index 0000000..5cf606c --- /dev/null +++ b/src/userinterface/dash/sessions.gd.uid @@ -0,0 +1 @@ +uid://ccd2fw88a52mp diff --git a/src/userinterface/dash/settings.gd b/src/userinterface/dash/settings.gd new file mode 100644 index 0000000..79d6583 --- /dev/null +++ b/src/userinterface/dash/settings.gd @@ -0,0 +1,74 @@ +# --- License +# File: /client/src/userinterface/dash/settings.gd +# Project: OpenMinerva +# Created Date: 27 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +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") + +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) + 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 + _template.get_node("Remove").pressed.connect(_remove_session_server.bind(server.url)) + _session_server_container.add_child(_template) + return + +func _remove_session_server(url: String) -> void: + SettingsManager.remove_session_server(url) + _load_session_servers() + +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 = "" + _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 diff --git a/src/userinterface/dash/settings.gd.uid b/src/userinterface/dash/settings.gd.uid new file mode 100644 index 0000000..238acd9 --- /dev/null +++ b/src/userinterface/dash/settings.gd.uid @@ -0,0 +1 @@ +uid://cq26hbvdqb4sf diff --git a/src/userinterface/session_query.gd b/src/userinterface/session_query.gd new file mode 100644 index 0000000..9b3cdd1 --- /dev/null +++ b/src/userinterface/session_query.gd @@ -0,0 +1,95 @@ +# --- License +# File: /client/src/userinterface/session_query.gd +# Project: OpenMinerva +# Created Date: 31 March 2026 +# Copyright (c) 2026 OpenMinerva +# License: MIT License +# Authors: Armored Dragon +# --- License + +extends Node + +func authenticate(url: String) -> Dictionary: + var _return_dict: Dictionary = {"ok": false, "error": ""} + + # TODO: Do we need a new authentication key? + if GlobalAccount.dev_session_server_api_key == "": + var url_deconstructed = UrlParser.deconstruct(url) + if url_deconstructed.ok == false: + var ERROR_MESSAGE = "Failed to deconstruct the url '%s'" % url + GlobalLogger.logs(ERROR_MESSAGE, Enum.LogLevel.WARNING) + _return_dict.error = ERROR_MESSAGE + return _return_dict + url_deconstructed = url_deconstructed.data + + var body: Dictionary = { + "id_token": GlobalAccount.active_account.id_token, + "challenge": "challenge value" + } + var authentication_response = await HTTP.req(HTTPClient.Method.METHOD_POST, url_deconstructed.host, "/api/v1/getAuthenticationKey", url_deconstructed.port, ["Accept: application/json", "Content-Type: application/json"], JSON.stringify(body)) + + _authentication_request_received(url_deconstructed.host, authentication_response) + + # Get id_token of currently logged in user + # Sign challenge using private key + # Send request {id_token, challenge} + + # ... Server does its thing ... + + # Response contains api key, or error + + return _return_dict + +func get_sessions() -> Array: + var _return_arr = [] + var _session_servers = SettingsManager.get_session_servers() + + for server in _session_servers: + var search = "" + var tags = "" + + var url_deconstructed = UrlParser.deconstruct(server.url) + if url_deconstructed.ok == false: + GlobalLogger.logs("Failed to parse the session server URL.", Enum.LogLevel.INFO) + continue + url_deconstructed = url_deconstructed.data + + var form_parts := [ + "search=%s" % search, + "tags=%s" % tags, + ] + var form_string: String = "&".join(form_parts) + + var sessions_response = await HTTP.req(HTTPClient.Method.METHOD_GET, url_deconstructed.host, "/api/v1/getSessions?%s" % form_string, url_deconstructed.port, ["Accept: application/json", "Content-Type: application/x-www-form-urlencoded", "x-api-key: %s" % GlobalAccount.dev_session_server_api_key]) + + var sessions_in_server = _session_request_received(url_deconstructed.host, sessions_response) + # TODO: Validate request health + _return_arr.append_array(sessions_in_server.data) + + return _return_arr + +func _session_request_received(_host: String, response: Dictionary) -> Dictionary: + var _return_arr = {"ok": false, "error": "", "data": null} + + # TODO: If response.ok + # TODO: Validate is valid JSON + # TODO: Validate key exists + var response_parsed = JSON.parse_string(response.body) + + _return_arr.data = response_parsed.data + _return_arr.ok = true + + return _return_arr + +func _authentication_request_received(_host: String, response: Dictionary) -> Dictionary: + var _return_arr = {"ok": false, "error": "", "data": null} + + # TODO: If response.ok + # TODO: Validate is valid JSON + # TODO: Validate key exists + + GlobalAccount.dev_session_server_api_key = JSON.parse_string(response.body).key + + # If authentication succeeded, record data + # Else report error. + return _return_arr diff --git a/src/userinterface/session_query.gd.uid b/src/userinterface/session_query.gd.uid new file mode 100644 index 0000000..6002c34 --- /dev/null +++ b/src/userinterface/session_query.gd.uid @@ -0,0 +1 @@ +uid://dbrabs53sf0x5