diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..6248c16
Binary files /dev/null and b/.DS_Store differ
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f28239b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+root = true
+
+[*]
+charset = utf-8
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..ae13eeb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "godotTools.editorPath.godot4": "c:\\Users\\user\\Desktop\\Godot_v4.4.1-stable_win64.exe\\Godot_v4.4.1-stable_win64.exe"
+}
\ No newline at end of file
diff --git a/Assets/Shaders/outline.gdshader b/Assets/Shaders/outline.gdshader
new file mode 100644
index 0000000..6d9b0be
--- /dev/null
+++ b/Assets/Shaders/outline.gdshader
@@ -0,0 +1,112 @@
+shader_type spatial;
+render_mode unshaded;
+
+/*
+ Normal/Depth outline shader. Apply to nodes as a next pass shader texture.
+ Inspired by Yui Kinomoto @arlez80, lukky_nl (YT), Robin Seibold (YT)
+ Uses Sobel Edge detection on a normal and depth texture
+ Written by William Li (LoudFlameLava)
+
+ MIT License
+*/
+
+// Might create an outline at the edge of the viewport
+
+uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
+uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;
+uniform sampler2D NORMAL_TEXTURE : hint_normal_roughness_texture, filter_linear_mipmap;
+
+uniform float normal_threshold = 0.1;
+uniform float depth_threshold = 0.05;
+uniform float depth_artifact_correction_coef = 2;
+uniform vec3 outline_color: source_color;
+
+const mat3 sobel_y = mat3(
+ vec3(1.0, 0.0, -1.0),
+ vec3(2.0, 0.0, -2.0),
+ vec3(1.0, 0.0, -1.0)
+);
+
+const mat3 sobel_x = mat3(
+ vec3(1.0, 2.0, 1.0),
+ vec3(0.0, 0.0, 0.0),
+ vec3(-1.0, -2.0, -1.0)
+);
+
+float edge_value_normal(sampler2D normal_tex, vec2 uv, vec2 pixel_size, mat3 sobel) {
+ float output = 0.0;
+ vec3 normal = texture(normal_tex, uv).rgb;
+ vec3 n = texture(NORMAL_TEXTURE, uv + vec2(0.0, -pixel_size.y)).rgb;
+ vec3 s = texture(NORMAL_TEXTURE, uv + vec2(0.0, pixel_size.y)).rgb;
+ vec3 e = texture(NORMAL_TEXTURE, uv + vec2(pixel_size.x, 0.0)).rgb;
+ vec3 w = texture(NORMAL_TEXTURE, uv + vec2(-pixel_size.x, 0.0)).rgb;
+ vec3 nw = texture(NORMAL_TEXTURE, uv + vec2(-pixel_size.x, -pixel_size.y)).rgb;
+ vec3 ne = texture(NORMAL_TEXTURE, uv + vec2(pixel_size.x, -pixel_size.y)).rgb;
+ vec3 sw = texture(NORMAL_TEXTURE, uv + vec2(-pixel_size.x, pixel_size.y)).rgb;
+ vec3 se = texture(NORMAL_TEXTURE, uv + vec2(pixel_size.x, pixel_size.y)).rgb;
+
+ mat3 error_mat = mat3(
+ vec3(length(normal - nw), length(normal - n), length(normal - ne)),
+ vec3(length(normal - w), 0.0, length(normal - e)),
+ vec3(length(normal - sw), length(normal - s), length(normal - se))
+ );
+
+ output += dot(sobel[0], error_mat[0]);
+ output += dot(sobel[1], error_mat[1]);
+ output += dot(sobel[2], error_mat[2]);
+ return abs(output);
+}
+
+float get_depth(sampler2D depth_tex, vec2 uv, mat4 inv_projection_matrix) {
+ float depth_raw = texture(depth_tex, uv).x;
+ vec3 ndc = vec3(uv * 2.0 - 1.0, depth_raw);
+ vec4 view = inv_projection_matrix * vec4(ndc, 1.0);
+ view.xyz /= view.w;
+ float depth_linear = -view.z;
+ return depth_linear;
+}
+
+float edge_value_depth(sampler2D depth_tex, vec2 uv, vec2 pixel_size, mat3 sobel, mat4 inv_projection_matrix){
+ float output = 0.0;
+ float depth = get_depth(depth_tex, uv, inv_projection_matrix);
+ float n = get_depth(depth_tex, uv + vec2(0.0, -pixel_size.y), inv_projection_matrix);
+ float s = get_depth(depth_tex, uv + vec2(0.0, pixel_size.y), inv_projection_matrix);
+ float e = get_depth(depth_tex, uv + vec2(pixel_size.x, 0.0), inv_projection_matrix);
+ float w = get_depth(depth_tex, uv + vec2(-pixel_size.x, 0.0), inv_projection_matrix);
+ float ne = get_depth(depth_tex, uv + vec2(pixel_size.x, -pixel_size.y), inv_projection_matrix);
+ float nw = get_depth(depth_tex, uv + vec2(-pixel_size.x, -pixel_size.y), inv_projection_matrix);
+ float se = get_depth(depth_tex, uv + vec2(pixel_size.x, pixel_size.y), inv_projection_matrix);
+ float sw = get_depth(depth_tex, uv + vec2(-pixel_size.x, pixel_size.y), inv_projection_matrix);
+
+ mat3 error_mat = mat3(
+ vec3((depth - nw)/depth, (depth - n)/depth, (depth - ne)/depth),
+ vec3((depth - w)/depth, 0.0, (depth - e)/depth),
+ vec3((depth - sw)/depth, (depth - s)/depth, (depth - se)/depth)
+ );
+
+ output += dot(sobel[0], error_mat[0]);
+ output += dot(sobel[1], error_mat[1]);
+ output += dot(sobel[2], error_mat[2]);
+ return abs(output);
+}
+
+
+void fragment() {
+ float has_outline = 0.0;
+ vec2 pixel_size = vec2(1.0) / VIEWPORT_SIZE;
+ ALBEDO = texture(SCREEN_TEXTURE, SCREEN_UV).xyz;
+ //ALBEDO = vec3(get_depth(DEPTH_TEXTURE, SCREEN_UV, INV_PROJECTION_MATRIX));
+ if (edge_value_normal(NORMAL_TEXTURE, SCREEN_UV, pixel_size, sobel_x) + edge_value_normal(NORMAL_TEXTURE, SCREEN_UV, pixel_size, sobel_y) > normal_threshold){
+ ALBEDO = outline_color;
+ has_outline += 1.0;
+ }
+ vec3 normal = texture(NORMAL_TEXTURE, SCREEN_UV).rgb;
+ float angle = 1.0 - dot(normalize(normal-vec3(0.5)), vec3(0.0,0.0,1.0));
+ if (edge_value_depth(DEPTH_TEXTURE, SCREEN_UV, pixel_size, sobel_x, INV_PROJECTION_MATRIX) + edge_value_depth(DEPTH_TEXTURE, SCREEN_UV, pixel_size, sobel_y, INV_PROJECTION_MATRIX) > depth_threshold + angle * depth_artifact_correction_coef){
+ ALBEDO = outline_color;
+ has_outline += 1.0;
+ }
+ if (has_outline < 0.1){
+ ALPHA = 0.0;
+ }
+}
diff --git a/Assets/Shaders/outline.gdshader.uid b/Assets/Shaders/outline.gdshader.uid
new file mode 100644
index 0000000..8dc51d4
--- /dev/null
+++ b/Assets/Shaders/outline.gdshader.uid
@@ -0,0 +1 @@
+uid://c0chn7ll6p7yk
diff --git a/Assets/Textures/black_board.png b/Assets/Textures/black_board.png
new file mode 100644
index 0000000..e063f8a
Binary files /dev/null and b/Assets/Textures/black_board.png differ
diff --git a/Assets/Textures/black_board.png.import b/Assets/Textures/black_board.png.import
new file mode 100644
index 0000000..c5d6778
--- /dev/null
+++ b/Assets/Textures/black_board.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ct5kq6psirv1"
+path="res://.godot/imported/black_board.png-d2b5afa20c384e2e4b3e7a80827129fd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/black_board.png"
+dest_files=["res://.godot/imported/black_board.png-d2b5afa20c384e2e4b3e7a80827129fd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_01.png b/Assets/Textures/kenny/Dark/texture_01.png
new file mode 100644
index 0000000..69be211
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_01.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_01.png.import b/Assets/Textures/kenny/Dark/texture_01.png.import
new file mode 100644
index 0000000..3a72ae1
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_01.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bvarfgw4r5181"
+path.s3tc="res://.godot/imported/texture_01.png-6071662e99660834e61c6b1e09bbab86.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_01.png"
+dest_files=["res://.godot/imported/texture_01.png-6071662e99660834e61c6b1e09bbab86.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Dark/texture_02.png b/Assets/Textures/kenny/Dark/texture_02.png
new file mode 100644
index 0000000..6fb471b
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_02.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_02.png.import b/Assets/Textures/kenny/Dark/texture_02.png.import
new file mode 100644
index 0000000..107ec0a
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_02.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://youvyib1feqp"
+path.s3tc="res://.godot/imported/texture_02.png-50bb9d4d4ebbae05fd29423b1eebdf02.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_02.png"
+dest_files=["res://.godot/imported/texture_02.png-50bb9d4d4ebbae05fd29423b1eebdf02.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Dark/texture_03.png b/Assets/Textures/kenny/Dark/texture_03.png
new file mode 100644
index 0000000..3f8b186
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_03.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_03.png.import b/Assets/Textures/kenny/Dark/texture_03.png.import
new file mode 100644
index 0000000..5d942fa
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_03.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dtr5bvxymd50e"
+path="res://.godot/imported/texture_03.png-09f6510aeb06a66b61241151fd45cc12.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_03.png"
+dest_files=["res://.godot/imported/texture_03.png-09f6510aeb06a66b61241151fd45cc12.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_04.png b/Assets/Textures/kenny/Dark/texture_04.png
new file mode 100644
index 0000000..e2bc22b
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_04.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_04.png.import b/Assets/Textures/kenny/Dark/texture_04.png.import
new file mode 100644
index 0000000..d695f31
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gebarmhhtmbt"
+path="res://.godot/imported/texture_04.png-778404077545d0988503bb93ed90a85c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_04.png"
+dest_files=["res://.godot/imported/texture_04.png-778404077545d0988503bb93ed90a85c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_05.png b/Assets/Textures/kenny/Dark/texture_05.png
new file mode 100644
index 0000000..3fd2e56
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_05.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_05.png.import b/Assets/Textures/kenny/Dark/texture_05.png.import
new file mode 100644
index 0000000..6655741
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_05.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ca0qmdc4cv8uh"
+path.s3tc="res://.godot/imported/texture_05.png-0310eec4e5c94e9c6cb4c0e3fd371255.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_05.png"
+dest_files=["res://.godot/imported/texture_05.png-0310eec4e5c94e9c6cb4c0e3fd371255.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Dark/texture_06.png b/Assets/Textures/kenny/Dark/texture_06.png
new file mode 100644
index 0000000..45d4a34
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_06.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_06.png.import b/Assets/Textures/kenny/Dark/texture_06.png.import
new file mode 100644
index 0000000..bbe7174
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://carepjxbifxnk"
+path="res://.godot/imported/texture_06.png-2b3f2645aa79305efcc877465d8ddcb1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_06.png"
+dest_files=["res://.godot/imported/texture_06.png-2b3f2645aa79305efcc877465d8ddcb1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_07.png b/Assets/Textures/kenny/Dark/texture_07.png
new file mode 100644
index 0000000..adf5e6f
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_07.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_07.png.import b/Assets/Textures/kenny/Dark/texture_07.png.import
new file mode 100644
index 0000000..abdbb75
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_07.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://kjr70h6gg01b"
+path="res://.godot/imported/texture_07.png-ba02094d10277610cee39722359a74f8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_07.png"
+dest_files=["res://.godot/imported/texture_07.png-ba02094d10277610cee39722359a74f8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_08.png b/Assets/Textures/kenny/Dark/texture_08.png
new file mode 100644
index 0000000..a5a9f24
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_08.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_08.png.import b/Assets/Textures/kenny/Dark/texture_08.png.import
new file mode 100644
index 0000000..837ec38
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_08.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bhy0xbgh23y4d"
+path.s3tc="res://.godot/imported/texture_08.png-66ed16371ee09d695d3da92281e2484c.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-66ed16371ee09d695d3da92281e2484c.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Dark/texture_09.png b/Assets/Textures/kenny/Dark/texture_09.png
new file mode 100644
index 0000000..57cc607
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_09.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_09.png.import b/Assets/Textures/kenny/Dark/texture_09.png.import
new file mode 100644
index 0000000..4748cd6
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_09.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ci677phv85dbo"
+path="res://.godot/imported/texture_09.png-a339e9ab2c228fa9a3d4ef6e1db33ba6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_09.png"
+dest_files=["res://.godot/imported/texture_09.png-a339e9ab2c228fa9a3d4ef6e1db33ba6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_10.png b/Assets/Textures/kenny/Dark/texture_10.png
new file mode 100644
index 0000000..4c737d0
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_10.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_10.png.import b/Assets/Textures/kenny/Dark/texture_10.png.import
new file mode 100644
index 0000000..b094f99
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_10.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://beeg4qas2fsac"
+path="res://.godot/imported/texture_10.png-ff641a8ee3894ad52a58711b2e06c0bf.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_10.png"
+dest_files=["res://.godot/imported/texture_10.png-ff641a8ee3894ad52a58711b2e06c0bf.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_11.png b/Assets/Textures/kenny/Dark/texture_11.png
new file mode 100644
index 0000000..424ba71
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_11.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_11.png.import b/Assets/Textures/kenny/Dark/texture_11.png.import
new file mode 100644
index 0000000..6653d85
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_11.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cqht8ngfdmuwj"
+path="res://.godot/imported/texture_11.png-355f894fc0cda8a832f22297cc09266e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_11.png"
+dest_files=["res://.godot/imported/texture_11.png-355f894fc0cda8a832f22297cc09266e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_12.png b/Assets/Textures/kenny/Dark/texture_12.png
new file mode 100644
index 0000000..32169db
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_12.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_12.png.import b/Assets/Textures/kenny/Dark/texture_12.png.import
new file mode 100644
index 0000000..3a1e07f
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_12.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bfh6mw4in04i4"
+path="res://.godot/imported/texture_12.png-6e528b01bab6560b8cf1c4de1f66ee17.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_12.png"
+dest_files=["res://.godot/imported/texture_12.png-6e528b01bab6560b8cf1c4de1f66ee17.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Dark/texture_13.png b/Assets/Textures/kenny/Dark/texture_13.png
new file mode 100644
index 0000000..13c4388
Binary files /dev/null and b/Assets/Textures/kenny/Dark/texture_13.png differ
diff --git a/Assets/Textures/kenny/Dark/texture_13.png.import b/Assets/Textures/kenny/Dark/texture_13.png.import
new file mode 100644
index 0000000..2c5dca2
--- /dev/null
+++ b/Assets/Textures/kenny/Dark/texture_13.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://e43jdimtr6vs"
+path="res://.godot/imported/texture_13.png-00e2369d26e06bdd9f32f6d062d5ae9f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Dark/texture_13.png"
+dest_files=["res://.godot/imported/texture_13.png-00e2369d26e06bdd9f32f6d062d5ae9f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_01.png b/Assets/Textures/kenny/Green/texture_01.png
new file mode 100644
index 0000000..d576514
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_01.png differ
diff --git a/Assets/Textures/kenny/Green/texture_01.png.import b/Assets/Textures/kenny/Green/texture_01.png.import
new file mode 100644
index 0000000..2e610a0
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_01.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b0e6e1xjl18aa"
+path.s3tc="res://.godot/imported/texture_01.png-e84a1fc48ff8aea2304817d95826f7d1.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_01.png"
+dest_files=["res://.godot/imported/texture_01.png-e84a1fc48ff8aea2304817d95826f7d1.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Green/texture_02.png b/Assets/Textures/kenny/Green/texture_02.png
new file mode 100644
index 0000000..7bc7cf8
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_02.png differ
diff --git a/Assets/Textures/kenny/Green/texture_02.png.import b/Assets/Textures/kenny/Green/texture_02.png.import
new file mode 100644
index 0000000..17bdc5f
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d2r1dqvx1h5vk"
+path="res://.godot/imported/texture_02.png-c0b2f3ead5d1423b3fb0f2462b41f12c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_02.png"
+dest_files=["res://.godot/imported/texture_02.png-c0b2f3ead5d1423b3fb0f2462b41f12c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_03.png b/Assets/Textures/kenny/Green/texture_03.png
new file mode 100644
index 0000000..e2a3889
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_03.png differ
diff --git a/Assets/Textures/kenny/Green/texture_03.png.import b/Assets/Textures/kenny/Green/texture_03.png.import
new file mode 100644
index 0000000..82b3a49
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_03.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bnnes2g3lmmqc"
+path="res://.godot/imported/texture_03.png-2ea7d5642901462925769bc9b4821182.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_03.png"
+dest_files=["res://.godot/imported/texture_03.png-2ea7d5642901462925769bc9b4821182.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_04.png b/Assets/Textures/kenny/Green/texture_04.png
new file mode 100644
index 0000000..3952bef
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_04.png differ
diff --git a/Assets/Textures/kenny/Green/texture_04.png.import b/Assets/Textures/kenny/Green/texture_04.png.import
new file mode 100644
index 0000000..dd8072d
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djti0r1txl6fi"
+path="res://.godot/imported/texture_04.png-4da8cab1d5443c2be1a0905e87be7e41.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_04.png"
+dest_files=["res://.godot/imported/texture_04.png-4da8cab1d5443c2be1a0905e87be7e41.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_05.png b/Assets/Textures/kenny/Green/texture_05.png
new file mode 100644
index 0000000..84976ba
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_05.png differ
diff --git a/Assets/Textures/kenny/Green/texture_05.png.import b/Assets/Textures/kenny/Green/texture_05.png.import
new file mode 100644
index 0000000..9c84906
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_05.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bbkkd0r1324rf"
+path="res://.godot/imported/texture_05.png-6112786d3ad5fc49141106fa07e460a6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_05.png"
+dest_files=["res://.godot/imported/texture_05.png-6112786d3ad5fc49141106fa07e460a6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_06.png b/Assets/Textures/kenny/Green/texture_06.png
new file mode 100644
index 0000000..1128198
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_06.png differ
diff --git a/Assets/Textures/kenny/Green/texture_06.png.import b/Assets/Textures/kenny/Green/texture_06.png.import
new file mode 100644
index 0000000..0c10369
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vcey0liprcku"
+path="res://.godot/imported/texture_06.png-6ed4f201eb81a7ded2fcf27afbb76cd3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_06.png"
+dest_files=["res://.godot/imported/texture_06.png-6ed4f201eb81a7ded2fcf27afbb76cd3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_07.png b/Assets/Textures/kenny/Green/texture_07.png
new file mode 100644
index 0000000..3160860
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_07.png differ
diff --git a/Assets/Textures/kenny/Green/texture_07.png.import b/Assets/Textures/kenny/Green/texture_07.png.import
new file mode 100644
index 0000000..105f5d7
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_07.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://4fue3416xajr"
+path="res://.godot/imported/texture_07.png-d690cfc57b2d71d6535bcf632bd98326.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_07.png"
+dest_files=["res://.godot/imported/texture_07.png-d690cfc57b2d71d6535bcf632bd98326.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_08.png b/Assets/Textures/kenny/Green/texture_08.png
new file mode 100644
index 0000000..386293d
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_08.png differ
diff --git a/Assets/Textures/kenny/Green/texture_08.png.import b/Assets/Textures/kenny/Green/texture_08.png.import
new file mode 100644
index 0000000..e697347
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_08.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://poho7bpcb6ii"
+path="res://.godot/imported/texture_08.png-388bcc8f4ed5730d30ef519aed5712c3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-388bcc8f4ed5730d30ef519aed5712c3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_09.png b/Assets/Textures/kenny/Green/texture_09.png
new file mode 100644
index 0000000..48234f6
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_09.png differ
diff --git a/Assets/Textures/kenny/Green/texture_09.png.import b/Assets/Textures/kenny/Green/texture_09.png.import
new file mode 100644
index 0000000..8a42689
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_09.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c4fay2858qj0l"
+path="res://.godot/imported/texture_09.png-680667567c1aaff0ba907ed98e080d69.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_09.png"
+dest_files=["res://.godot/imported/texture_09.png-680667567c1aaff0ba907ed98e080d69.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_10.png b/Assets/Textures/kenny/Green/texture_10.png
new file mode 100644
index 0000000..e4ab057
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_10.png differ
diff --git a/Assets/Textures/kenny/Green/texture_10.png.import b/Assets/Textures/kenny/Green/texture_10.png.import
new file mode 100644
index 0000000..506afb1
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_10.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ba1f44g54l45m"
+path="res://.godot/imported/texture_10.png-f4e6f4f72511743e87c731dc1a545bb8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_10.png"
+dest_files=["res://.godot/imported/texture_10.png-f4e6f4f72511743e87c731dc1a545bb8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_11.png b/Assets/Textures/kenny/Green/texture_11.png
new file mode 100644
index 0000000..82ad458
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_11.png differ
diff --git a/Assets/Textures/kenny/Green/texture_11.png.import b/Assets/Textures/kenny/Green/texture_11.png.import
new file mode 100644
index 0000000..c0282d3
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_11.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://hncjy0d5tnxg"
+path="res://.godot/imported/texture_11.png-b7efbbf02e7aaebe01a73a52889e2db2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_11.png"
+dest_files=["res://.godot/imported/texture_11.png-b7efbbf02e7aaebe01a73a52889e2db2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_12.png b/Assets/Textures/kenny/Green/texture_12.png
new file mode 100644
index 0000000..a15000d
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_12.png differ
diff --git a/Assets/Textures/kenny/Green/texture_12.png.import b/Assets/Textures/kenny/Green/texture_12.png.import
new file mode 100644
index 0000000..49af588
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_12.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://kdrttluayi20"
+path="res://.godot/imported/texture_12.png-d600a604f3c865d458be124764f1815a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_12.png"
+dest_files=["res://.godot/imported/texture_12.png-d600a604f3c865d458be124764f1815a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Green/texture_13.png b/Assets/Textures/kenny/Green/texture_13.png
new file mode 100644
index 0000000..930fdf0
Binary files /dev/null and b/Assets/Textures/kenny/Green/texture_13.png differ
diff --git a/Assets/Textures/kenny/Green/texture_13.png.import b/Assets/Textures/kenny/Green/texture_13.png.import
new file mode 100644
index 0000000..9c4c191
--- /dev/null
+++ b/Assets/Textures/kenny/Green/texture_13.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cbynwfgx1udap"
+path="res://.godot/imported/texture_13.png-017338333d71c41496ec3c6b0c3f4cf5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Green/texture_13.png"
+dest_files=["res://.godot/imported/texture_13.png-017338333d71c41496ec3c6b0c3f4cf5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_01.png b/Assets/Textures/kenny/Light/texture_01.png
new file mode 100644
index 0000000..60b632b
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_01.png differ
diff --git a/Assets/Textures/kenny/Light/texture_01.png.import b/Assets/Textures/kenny/Light/texture_01.png.import
new file mode 100644
index 0000000..f5dbae7
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_01.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://7dus2yvf2ug"
+path.s3tc="res://.godot/imported/texture_01.png-4e666ff013442f29d41e355995ff019c.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_01.png"
+dest_files=["res://.godot/imported/texture_01.png-4e666ff013442f29d41e355995ff019c.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Light/texture_02.png b/Assets/Textures/kenny/Light/texture_02.png
new file mode 100644
index 0000000..19aad62
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_02.png differ
diff --git a/Assets/Textures/kenny/Light/texture_02.png.import b/Assets/Textures/kenny/Light/texture_02.png.import
new file mode 100644
index 0000000..b99e7b4
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ct2nwmb0g0c11"
+path="res://.godot/imported/texture_02.png-15f671c63a751d171fa712ed4f70138a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_02.png"
+dest_files=["res://.godot/imported/texture_02.png-15f671c63a751d171fa712ed4f70138a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_03.png b/Assets/Textures/kenny/Light/texture_03.png
new file mode 100644
index 0000000..a8a6c06
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_03.png differ
diff --git a/Assets/Textures/kenny/Light/texture_03.png.import b/Assets/Textures/kenny/Light/texture_03.png.import
new file mode 100644
index 0000000..e7fd3f3
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_03.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dvxbvy41idpif"
+path="res://.godot/imported/texture_03.png-dabf35d6925641754119ee2126742474.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_03.png"
+dest_files=["res://.godot/imported/texture_03.png-dabf35d6925641754119ee2126742474.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_04.png b/Assets/Textures/kenny/Light/texture_04.png
new file mode 100644
index 0000000..b8270e1
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_04.png differ
diff --git a/Assets/Textures/kenny/Light/texture_04.png.import b/Assets/Textures/kenny/Light/texture_04.png.import
new file mode 100644
index 0000000..89b95d0
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://nsrg41iyucwk"
+path="res://.godot/imported/texture_04.png-ed7440cc8ef63f2eb61e3a9c42d921cc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_04.png"
+dest_files=["res://.godot/imported/texture_04.png-ed7440cc8ef63f2eb61e3a9c42d921cc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_05.png b/Assets/Textures/kenny/Light/texture_05.png
new file mode 100644
index 0000000..5b985bf
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_05.png differ
diff --git a/Assets/Textures/kenny/Light/texture_05.png.import b/Assets/Textures/kenny/Light/texture_05.png.import
new file mode 100644
index 0000000..6aead33
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_05.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3j4vtf2aq4to"
+path="res://.godot/imported/texture_05.png-3395530d01752dcf8e27fd6718497c9f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_05.png"
+dest_files=["res://.godot/imported/texture_05.png-3395530d01752dcf8e27fd6718497c9f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_06.png b/Assets/Textures/kenny/Light/texture_06.png
new file mode 100644
index 0000000..195ad77
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_06.png differ
diff --git a/Assets/Textures/kenny/Light/texture_06.png.import b/Assets/Textures/kenny/Light/texture_06.png.import
new file mode 100644
index 0000000..d8390d2
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dgl8yfvnfdw45"
+path="res://.godot/imported/texture_06.png-487ab5f48eb725fca4e729bebdf72d2f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_06.png"
+dest_files=["res://.godot/imported/texture_06.png-487ab5f48eb725fca4e729bebdf72d2f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_07.png b/Assets/Textures/kenny/Light/texture_07.png
new file mode 100644
index 0000000..fbf92e0
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_07.png differ
diff --git a/Assets/Textures/kenny/Light/texture_07.png.import b/Assets/Textures/kenny/Light/texture_07.png.import
new file mode 100644
index 0000000..901cb43
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_07.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://p7b7cxvbb1cp"
+path="res://.godot/imported/texture_07.png-5bc9abc0e209a8ac3f44e3b8278713fe.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_07.png"
+dest_files=["res://.godot/imported/texture_07.png-5bc9abc0e209a8ac3f44e3b8278713fe.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_08.png b/Assets/Textures/kenny/Light/texture_08.png
new file mode 100644
index 0000000..72e1a07
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_08.png differ
diff --git a/Assets/Textures/kenny/Light/texture_08.png.import b/Assets/Textures/kenny/Light/texture_08.png.import
new file mode 100644
index 0000000..1de110c
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_08.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ube7pfqljvyf"
+path="res://.godot/imported/texture_08.png-25589eded306385399b275c6db3775cd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-25589eded306385399b275c6db3775cd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_09.png b/Assets/Textures/kenny/Light/texture_09.png
new file mode 100644
index 0000000..9ada5dd
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_09.png differ
diff --git a/Assets/Textures/kenny/Light/texture_09.png.import b/Assets/Textures/kenny/Light/texture_09.png.import
new file mode 100644
index 0000000..4e6d36f
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_09.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djv5pftub125"
+path="res://.godot/imported/texture_09.png-d03fadc65f007e08b56362a923e9de00.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_09.png"
+dest_files=["res://.godot/imported/texture_09.png-d03fadc65f007e08b56362a923e9de00.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_10.png b/Assets/Textures/kenny/Light/texture_10.png
new file mode 100644
index 0000000..4fbcc80
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_10.png differ
diff --git a/Assets/Textures/kenny/Light/texture_10.png.import b/Assets/Textures/kenny/Light/texture_10.png.import
new file mode 100644
index 0000000..e679b4a
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_10.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vbio6gqo77y6"
+path="res://.godot/imported/texture_10.png-77bfe99d787ea2ef61f6f02a876f509e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_10.png"
+dest_files=["res://.godot/imported/texture_10.png-77bfe99d787ea2ef61f6f02a876f509e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_11.png b/Assets/Textures/kenny/Light/texture_11.png
new file mode 100644
index 0000000..840ecec
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_11.png differ
diff --git a/Assets/Textures/kenny/Light/texture_11.png.import b/Assets/Textures/kenny/Light/texture_11.png.import
new file mode 100644
index 0000000..32ba719
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_11.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cb0mt02evssa1"
+path="res://.godot/imported/texture_11.png-656e2ce8c617ee8b506ce297ee5013b7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_11.png"
+dest_files=["res://.godot/imported/texture_11.png-656e2ce8c617ee8b506ce297ee5013b7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_12.png b/Assets/Textures/kenny/Light/texture_12.png
new file mode 100644
index 0000000..25c2e78
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_12.png differ
diff --git a/Assets/Textures/kenny/Light/texture_12.png.import b/Assets/Textures/kenny/Light/texture_12.png.import
new file mode 100644
index 0000000..fd6e6b6
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_12.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c7h0g54nsd4en"
+path="res://.godot/imported/texture_12.png-c779d9476a97c8c6f8344fc8c0acf05e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_12.png"
+dest_files=["res://.godot/imported/texture_12.png-c779d9476a97c8c6f8344fc8c0acf05e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Light/texture_13.png b/Assets/Textures/kenny/Light/texture_13.png
new file mode 100644
index 0000000..a235965
Binary files /dev/null and b/Assets/Textures/kenny/Light/texture_13.png differ
diff --git a/Assets/Textures/kenny/Light/texture_13.png.import b/Assets/Textures/kenny/Light/texture_13.png.import
new file mode 100644
index 0000000..bed6c50
--- /dev/null
+++ b/Assets/Textures/kenny/Light/texture_13.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c150y2i0o0n4b"
+path="res://.godot/imported/texture_13.png-e3613fd71c8fd004170df7272cdae77e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Light/texture_13.png"
+dest_files=["res://.godot/imported/texture_13.png-e3613fd71c8fd004170df7272cdae77e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_01.png b/Assets/Textures/kenny/Orange/texture_01.png
new file mode 100644
index 0000000..4f5bf92
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_01.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_01.png.import b/Assets/Textures/kenny/Orange/texture_01.png.import
new file mode 100644
index 0000000..966270c
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_01.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://1eu6ubigu64i"
+path.s3tc="res://.godot/imported/texture_01.png-21904ba99712468e66077bdbdb508efb.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_01.png"
+dest_files=["res://.godot/imported/texture_01.png-21904ba99712468e66077bdbdb508efb.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Orange/texture_02.png b/Assets/Textures/kenny/Orange/texture_02.png
new file mode 100644
index 0000000..dec5b59
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_02.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_02.png.import b/Assets/Textures/kenny/Orange/texture_02.png.import
new file mode 100644
index 0000000..aae1419
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b8iefcauntlfr"
+path="res://.godot/imported/texture_02.png-6ba53c85eb016038e9eb03ca867ef46d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_02.png"
+dest_files=["res://.godot/imported/texture_02.png-6ba53c85eb016038e9eb03ca867ef46d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_03.png b/Assets/Textures/kenny/Orange/texture_03.png
new file mode 100644
index 0000000..666197f
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_03.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_03.png.import b/Assets/Textures/kenny/Orange/texture_03.png.import
new file mode 100644
index 0000000..1688015
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_03.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ck8bb11fqwytj"
+path.s3tc="res://.godot/imported/texture_03.png-20f820c35e04dcdc607ba2061af190d1.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_03.png"
+dest_files=["res://.godot/imported/texture_03.png-20f820c35e04dcdc607ba2061af190d1.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Orange/texture_04.png b/Assets/Textures/kenny/Orange/texture_04.png
new file mode 100644
index 0000000..23d6fc4
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_04.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_04.png.import b/Assets/Textures/kenny/Orange/texture_04.png.import
new file mode 100644
index 0000000..7d674e3
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dhcpwn5wpsl83"
+path="res://.godot/imported/texture_04.png-5d656289d155d17452e89f79a68dd096.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_04.png"
+dest_files=["res://.godot/imported/texture_04.png-5d656289d155d17452e89f79a68dd096.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_05.png b/Assets/Textures/kenny/Orange/texture_05.png
new file mode 100644
index 0000000..1e0448a
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_05.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_05.png.import b/Assets/Textures/kenny/Orange/texture_05.png.import
new file mode 100644
index 0000000..b001350
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_05.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdabm8p32rpuh"
+path="res://.godot/imported/texture_05.png-4173da2dfb92ee8e5cf0c0793d0919e0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_05.png"
+dest_files=["res://.godot/imported/texture_05.png-4173da2dfb92ee8e5cf0c0793d0919e0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_06.png b/Assets/Textures/kenny/Orange/texture_06.png
new file mode 100644
index 0000000..5486825
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_06.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_06.png.import b/Assets/Textures/kenny/Orange/texture_06.png.import
new file mode 100644
index 0000000..5d0b9c7
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://10al17kdi8dt"
+path="res://.godot/imported/texture_06.png-901615c60be874edc11fe06ec267294b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_06.png"
+dest_files=["res://.godot/imported/texture_06.png-901615c60be874edc11fe06ec267294b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_07.png b/Assets/Textures/kenny/Orange/texture_07.png
new file mode 100644
index 0000000..95f2790
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_07.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_07.png.import b/Assets/Textures/kenny/Orange/texture_07.png.import
new file mode 100644
index 0000000..a5bffc2
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_07.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b22jirj6yj7jn"
+path="res://.godot/imported/texture_07.png-ce29603f5e3b6487062ce4696276fe21.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_07.png"
+dest_files=["res://.godot/imported/texture_07.png-ce29603f5e3b6487062ce4696276fe21.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_08.png b/Assets/Textures/kenny/Orange/texture_08.png
new file mode 100644
index 0000000..5a500d9
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_08.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_08.png.import b/Assets/Textures/kenny/Orange/texture_08.png.import
new file mode 100644
index 0000000..fc8dcce
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_08.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ccfes61033s35"
+path="res://.godot/imported/texture_08.png-23f17f2a358608a6ba99ba64d634384d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-23f17f2a358608a6ba99ba64d634384d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_09.png b/Assets/Textures/kenny/Orange/texture_09.png
new file mode 100644
index 0000000..adcfa33
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_09.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_09.png.import b/Assets/Textures/kenny/Orange/texture_09.png.import
new file mode 100644
index 0000000..6929bf5
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_09.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bepcwfu5gwa0r"
+path="res://.godot/imported/texture_09.png-28bb6eb8e1b5d72fc500acf10edb425c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_09.png"
+dest_files=["res://.godot/imported/texture_09.png-28bb6eb8e1b5d72fc500acf10edb425c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_10.png b/Assets/Textures/kenny/Orange/texture_10.png
new file mode 100644
index 0000000..aa227e5
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_10.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_10.png.import b/Assets/Textures/kenny/Orange/texture_10.png.import
new file mode 100644
index 0000000..6b41ec9
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_10.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ds034otg5ppmx"
+path="res://.godot/imported/texture_10.png-d5c32d7ba6a93e4d9448016f7d97b06a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_10.png"
+dest_files=["res://.godot/imported/texture_10.png-d5c32d7ba6a93e4d9448016f7d97b06a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_11.png b/Assets/Textures/kenny/Orange/texture_11.png
new file mode 100644
index 0000000..dc94567
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_11.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_11.png.import b/Assets/Textures/kenny/Orange/texture_11.png.import
new file mode 100644
index 0000000..eb0c21f
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_11.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://o7y5ldmo52gb"
+path="res://.godot/imported/texture_11.png-0db65240315324be71fbaf9d0ba7f26f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_11.png"
+dest_files=["res://.godot/imported/texture_11.png-0db65240315324be71fbaf9d0ba7f26f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_12.png b/Assets/Textures/kenny/Orange/texture_12.png
new file mode 100644
index 0000000..b730544
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_12.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_12.png.import b/Assets/Textures/kenny/Orange/texture_12.png.import
new file mode 100644
index 0000000..6aee8c3
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_12.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcvo8pyt3dob2"
+path="res://.godot/imported/texture_12.png-a3fe18fff68d7047e511ab031d0ed2de.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_12.png"
+dest_files=["res://.godot/imported/texture_12.png-a3fe18fff68d7047e511ab031d0ed2de.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Orange/texture_13.png b/Assets/Textures/kenny/Orange/texture_13.png
new file mode 100644
index 0000000..01f4aa8
Binary files /dev/null and b/Assets/Textures/kenny/Orange/texture_13.png differ
diff --git a/Assets/Textures/kenny/Orange/texture_13.png.import b/Assets/Textures/kenny/Orange/texture_13.png.import
new file mode 100644
index 0000000..24c96ab
--- /dev/null
+++ b/Assets/Textures/kenny/Orange/texture_13.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cseg1slj8ywc2"
+path="res://.godot/imported/texture_13.png-54559e1053242f28de103e8bcd7b6c9a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Orange/texture_13.png"
+dest_files=["res://.godot/imported/texture_13.png-54559e1053242f28de103e8bcd7b6c9a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_01.png b/Assets/Textures/kenny/Purple/texture_01.png
new file mode 100644
index 0000000..d501875
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_01.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_01.png.import b/Assets/Textures/kenny/Purple/texture_01.png.import
new file mode 100644
index 0000000..7a36356
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_01.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dfsldrhd5gg5j"
+path.s3tc="res://.godot/imported/texture_01.png-7061b8dad3a290ac2eeb105c0380d20e.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_01.png"
+dest_files=["res://.godot/imported/texture_01.png-7061b8dad3a290ac2eeb105c0380d20e.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Purple/texture_02.png b/Assets/Textures/kenny/Purple/texture_02.png
new file mode 100644
index 0000000..48a51c1
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_02.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_02.png.import b/Assets/Textures/kenny/Purple/texture_02.png.import
new file mode 100644
index 0000000..445cc7b
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bw5hqj0tq8yuv"
+path="res://.godot/imported/texture_02.png-452946ab99e6cdaaf9f22986eba7a429.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_02.png"
+dest_files=["res://.godot/imported/texture_02.png-452946ab99e6cdaaf9f22986eba7a429.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_03.png b/Assets/Textures/kenny/Purple/texture_03.png
new file mode 100644
index 0000000..5f97f24
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_03.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_03.png.import b/Assets/Textures/kenny/Purple/texture_03.png.import
new file mode 100644
index 0000000..49cc95b
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_03.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bb2vapfa1ypyx"
+path="res://.godot/imported/texture_03.png-b41d8b2987700ea392a736376d846ca6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_03.png"
+dest_files=["res://.godot/imported/texture_03.png-b41d8b2987700ea392a736376d846ca6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_04.png b/Assets/Textures/kenny/Purple/texture_04.png
new file mode 100644
index 0000000..b81951c
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_04.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_04.png.import b/Assets/Textures/kenny/Purple/texture_04.png.import
new file mode 100644
index 0000000..e9a604f
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dpnhrn6ojgtgc"
+path="res://.godot/imported/texture_04.png-90d25a46e339068806ce962b2b7011ee.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_04.png"
+dest_files=["res://.godot/imported/texture_04.png-90d25a46e339068806ce962b2b7011ee.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_05.png b/Assets/Textures/kenny/Purple/texture_05.png
new file mode 100644
index 0000000..52352c8
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_05.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_05.png.import b/Assets/Textures/kenny/Purple/texture_05.png.import
new file mode 100644
index 0000000..e084dc1
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_05.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://com3jlavoextm"
+path="res://.godot/imported/texture_05.png-54ad07a32f1018b46ea846b4f45f5341.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_05.png"
+dest_files=["res://.godot/imported/texture_05.png-54ad07a32f1018b46ea846b4f45f5341.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_06.png b/Assets/Textures/kenny/Purple/texture_06.png
new file mode 100644
index 0000000..86deeb3
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_06.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_06.png.import b/Assets/Textures/kenny/Purple/texture_06.png.import
new file mode 100644
index 0000000..64f2a66
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ebbaxhn8e1xb"
+path="res://.godot/imported/texture_06.png-9c0680d74e22754b24feffe370d06340.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_06.png"
+dest_files=["res://.godot/imported/texture_06.png-9c0680d74e22754b24feffe370d06340.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_07.png b/Assets/Textures/kenny/Purple/texture_07.png
new file mode 100644
index 0000000..b71ce4e
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_07.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_07.png.import b/Assets/Textures/kenny/Purple/texture_07.png.import
new file mode 100644
index 0000000..7d3a41e
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_07.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dg25hk82shhyl"
+path="res://.godot/imported/texture_07.png-e09d9fb5e25f6e52305c8bb974b4af7f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_07.png"
+dest_files=["res://.godot/imported/texture_07.png-e09d9fb5e25f6e52305c8bb974b4af7f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_08.png b/Assets/Textures/kenny/Purple/texture_08.png
new file mode 100644
index 0000000..470cc5c
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_08.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_08.png.import b/Assets/Textures/kenny/Purple/texture_08.png.import
new file mode 100644
index 0000000..8e47149
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_08.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cgi8pjwpl7t66"
+path="res://.godot/imported/texture_08.png-98135aa1b06e14f0afd3ff96254f03de.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-98135aa1b06e14f0afd3ff96254f03de.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_09.png b/Assets/Textures/kenny/Purple/texture_09.png
new file mode 100644
index 0000000..4a3f689
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_09.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_09.png.import b/Assets/Textures/kenny/Purple/texture_09.png.import
new file mode 100644
index 0000000..252bddc
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_09.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cflf2eh506won"
+path="res://.godot/imported/texture_09.png-5f2a1e7e5c02f883f15f06b25b0169c4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_09.png"
+dest_files=["res://.godot/imported/texture_09.png-5f2a1e7e5c02f883f15f06b25b0169c4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_10.png b/Assets/Textures/kenny/Purple/texture_10.png
new file mode 100644
index 0000000..ab7e84b
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_10.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_10.png.import b/Assets/Textures/kenny/Purple/texture_10.png.import
new file mode 100644
index 0000000..a488693
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_10.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdln2o524vva7"
+path="res://.godot/imported/texture_10.png-6f727fd722909977332b5bf687299368.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_10.png"
+dest_files=["res://.godot/imported/texture_10.png-6f727fd722909977332b5bf687299368.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_11.png b/Assets/Textures/kenny/Purple/texture_11.png
new file mode 100644
index 0000000..ad7bebc
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_11.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_11.png.import b/Assets/Textures/kenny/Purple/texture_11.png.import
new file mode 100644
index 0000000..8275d30
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_11.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bb5cuipdh77go"
+path="res://.godot/imported/texture_11.png-e4fe1b32c0f967466cb17dc2ed9cd637.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_11.png"
+dest_files=["res://.godot/imported/texture_11.png-e4fe1b32c0f967466cb17dc2ed9cd637.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_12.png b/Assets/Textures/kenny/Purple/texture_12.png
new file mode 100644
index 0000000..979ac5a
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_12.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_12.png.import b/Assets/Textures/kenny/Purple/texture_12.png.import
new file mode 100644
index 0000000..8f40d19
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_12.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cv3khmgxn7qvj"
+path="res://.godot/imported/texture_12.png-95239019241f5e4e9503d6d4c208fc5b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_12.png"
+dest_files=["res://.godot/imported/texture_12.png-95239019241f5e4e9503d6d4c208fc5b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Purple/texture_13.png b/Assets/Textures/kenny/Purple/texture_13.png
new file mode 100644
index 0000000..2a9df22
Binary files /dev/null and b/Assets/Textures/kenny/Purple/texture_13.png differ
diff --git a/Assets/Textures/kenny/Purple/texture_13.png.import b/Assets/Textures/kenny/Purple/texture_13.png.import
new file mode 100644
index 0000000..034bb72
--- /dev/null
+++ b/Assets/Textures/kenny/Purple/texture_13.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://clocipa51sdoa"
+path="res://.godot/imported/texture_13.png-aa0eca751c61b5956056dca5d29b2d6f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Purple/texture_13.png"
+dest_files=["res://.godot/imported/texture_13.png-aa0eca751c61b5956056dca5d29b2d6f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_01.png b/Assets/Textures/kenny/Red/texture_01.png
new file mode 100644
index 0000000..1aaab41
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_01.png differ
diff --git a/Assets/Textures/kenny/Red/texture_01.png.import b/Assets/Textures/kenny/Red/texture_01.png.import
new file mode 100644
index 0000000..69eff5d
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_01.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bh16g6j32q42o"
+path.s3tc="res://.godot/imported/texture_01.png-549b58ed7e30d026165be9e13d5d92bc.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_01.png"
+dest_files=["res://.godot/imported/texture_01.png-549b58ed7e30d026165be9e13d5d92bc.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Red/texture_02.png b/Assets/Textures/kenny/Red/texture_02.png
new file mode 100644
index 0000000..bf1cb17
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_02.png differ
diff --git a/Assets/Textures/kenny/Red/texture_02.png.import b/Assets/Textures/kenny/Red/texture_02.png.import
new file mode 100644
index 0000000..cb3c161
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_02.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://doch175dkew6l"
+path="res://.godot/imported/texture_02.png-103a57fa97142313d6de55eacf864156.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_02.png"
+dest_files=["res://.godot/imported/texture_02.png-103a57fa97142313d6de55eacf864156.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_03.png b/Assets/Textures/kenny/Red/texture_03.png
new file mode 100644
index 0000000..ff09c22
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_03.png differ
diff --git a/Assets/Textures/kenny/Red/texture_03.png.import b/Assets/Textures/kenny/Red/texture_03.png.import
new file mode 100644
index 0000000..6787857
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_03.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dnxi4gebmhjd8"
+path.s3tc="res://.godot/imported/texture_03.png-1d6eb170932a721c4b2d928f47e2311c.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_03.png"
+dest_files=["res://.godot/imported/texture_03.png-1d6eb170932a721c4b2d928f47e2311c.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Assets/Textures/kenny/Red/texture_04.png b/Assets/Textures/kenny/Red/texture_04.png
new file mode 100644
index 0000000..b5b77ff
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_04.png differ
diff --git a/Assets/Textures/kenny/Red/texture_04.png.import b/Assets/Textures/kenny/Red/texture_04.png.import
new file mode 100644
index 0000000..3f1b616
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_04.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmobdvhm6lda6"
+path="res://.godot/imported/texture_04.png-9b0487b31c9447ab66f4e7cb7e634efb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_04.png"
+dest_files=["res://.godot/imported/texture_04.png-9b0487b31c9447ab66f4e7cb7e634efb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_05.png b/Assets/Textures/kenny/Red/texture_05.png
new file mode 100644
index 0000000..7827035
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_05.png differ
diff --git a/Assets/Textures/kenny/Red/texture_05.png.import b/Assets/Textures/kenny/Red/texture_05.png.import
new file mode 100644
index 0000000..46e7b48
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_05.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://co07dkdvoptpa"
+path="res://.godot/imported/texture_05.png-35b98d6db02dd2c2f2c750965353806f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_05.png"
+dest_files=["res://.godot/imported/texture_05.png-35b98d6db02dd2c2f2c750965353806f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_06.png b/Assets/Textures/kenny/Red/texture_06.png
new file mode 100644
index 0000000..914a6f1
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_06.png differ
diff --git a/Assets/Textures/kenny/Red/texture_06.png.import b/Assets/Textures/kenny/Red/texture_06.png.import
new file mode 100644
index 0000000..cea929f
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_06.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2hfc6ktxmmkw"
+path="res://.godot/imported/texture_06.png-57d3ea73786f1597a04f69016f5e55f5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_06.png"
+dest_files=["res://.godot/imported/texture_06.png-57d3ea73786f1597a04f69016f5e55f5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_07.png b/Assets/Textures/kenny/Red/texture_07.png
new file mode 100644
index 0000000..47d517b
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_07.png differ
diff --git a/Assets/Textures/kenny/Red/texture_07.png.import b/Assets/Textures/kenny/Red/texture_07.png.import
new file mode 100644
index 0000000..f2df5d1
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_07.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bo5par3o6fw66"
+path="res://.godot/imported/texture_07.png-c537230f7478a87f275d9f492dd05c8a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_07.png"
+dest_files=["res://.godot/imported/texture_07.png-c537230f7478a87f275d9f492dd05c8a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_08.png b/Assets/Textures/kenny/Red/texture_08.png
new file mode 100644
index 0000000..07cfc41
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_08.png differ
diff --git a/Assets/Textures/kenny/Red/texture_08.png.import b/Assets/Textures/kenny/Red/texture_08.png.import
new file mode 100644
index 0000000..75789ba
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_08.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://62wgiaxs1wx5"
+path="res://.godot/imported/texture_08.png-af6dcee1bcc58a3e1a90eb56d1a52b06.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-af6dcee1bcc58a3e1a90eb56d1a52b06.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_09.png b/Assets/Textures/kenny/Red/texture_09.png
new file mode 100644
index 0000000..86d67d8
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_09.png differ
diff --git a/Assets/Textures/kenny/Red/texture_09.png.import b/Assets/Textures/kenny/Red/texture_09.png.import
new file mode 100644
index 0000000..bb9069e
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_09.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c2wxo7o13cob1"
+path="res://.godot/imported/texture_09.png-254cb1193af53a2be6add745610771ca.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_09.png"
+dest_files=["res://.godot/imported/texture_09.png-254cb1193af53a2be6add745610771ca.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_10.png b/Assets/Textures/kenny/Red/texture_10.png
new file mode 100644
index 0000000..a9266d1
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_10.png differ
diff --git a/Assets/Textures/kenny/Red/texture_10.png.import b/Assets/Textures/kenny/Red/texture_10.png.import
new file mode 100644
index 0000000..45a1072
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_10.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2lf0v3lain05"
+path="res://.godot/imported/texture_10.png-b99488c8376839e9659212836a4f779a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_10.png"
+dest_files=["res://.godot/imported/texture_10.png-b99488c8376839e9659212836a4f779a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_11.png b/Assets/Textures/kenny/Red/texture_11.png
new file mode 100644
index 0000000..2acc544
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_11.png differ
diff --git a/Assets/Textures/kenny/Red/texture_11.png.import b/Assets/Textures/kenny/Red/texture_11.png.import
new file mode 100644
index 0000000..29fff57
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_11.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://fhlv6uvffslr"
+path="res://.godot/imported/texture_11.png-d371f1ae8072c74eda0a72c5aedaeb5b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_11.png"
+dest_files=["res://.godot/imported/texture_11.png-d371f1ae8072c74eda0a72c5aedaeb5b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_12.png b/Assets/Textures/kenny/Red/texture_12.png
new file mode 100644
index 0000000..7054c4d
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_12.png differ
diff --git a/Assets/Textures/kenny/Red/texture_12.png.import b/Assets/Textures/kenny/Red/texture_12.png.import
new file mode 100644
index 0000000..c4f56b6
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_12.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dfje8xtld0cnm"
+path="res://.godot/imported/texture_12.png-2c9c8d078c5a586bf8f5dbd6d9328fc7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_12.png"
+dest_files=["res://.godot/imported/texture_12.png-2c9c8d078c5a586bf8f5dbd6d9328fc7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Textures/kenny/Red/texture_13.png b/Assets/Textures/kenny/Red/texture_13.png
new file mode 100644
index 0000000..ab8c7b9
Binary files /dev/null and b/Assets/Textures/kenny/Red/texture_13.png differ
diff --git a/Assets/Textures/kenny/Red/texture_13.png.import b/Assets/Textures/kenny/Red/texture_13.png.import
new file mode 100644
index 0000000..407a4d7
--- /dev/null
+++ b/Assets/Textures/kenny/Red/texture_13.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b8davjerh21ln"
+path="res://.godot/imported/texture_13.png-8052da5f6a2ff263030f7fb8cca4efe6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Textures/kenny/Red/texture_13.png"
+dest_files=["res://.godot/imported/texture_13.png-8052da5f6a2ff263030f7fb8cca4efe6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Themes/theme.tres b/Assets/Themes/theme.tres
new file mode 100644
index 0000000..edbbfe8
--- /dev/null
+++ b/Assets/Themes/theme.tres
@@ -0,0 +1,13 @@
+[gd_resource type="Theme" load_steps=2 format=3 uid="uid://bvso6uowlb8dh"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4rowt"]
+bg_color = Color(0.18359, 0.18359, 0.18359, 1)
+border_width_left = 20
+border_width_top = 20
+border_width_right = 20
+border_width_bottom = 20
+border_color = Color(0, 0, 0, 1)
+
+[resource]
+HeaderLarge/font_sizes/font_size = 28
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_4rowt")
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9c0ef87
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Kirill Ivanov
+
+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/Scenes/Chat/ChatMenu.tscn b/Scenes/Chat/ChatMenu.tscn
new file mode 100644
index 0000000..ca9888a
--- /dev/null
+++ b/Scenes/Chat/ChatMenu.tscn
@@ -0,0 +1,60 @@
+[gd_scene load_steps=2 format=3 uid="uid://dtpqexue3vgl6"]
+
+[ext_resource type="Script" uid="uid://c8k0usfm5rdm3" path="res://Scripts/Chat/chat_menu.gd" id="1_8p1r2"]
+
+[node name="ChatMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = 298.0
+offset_right = -852.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 8
+script = ExtResource("1_8p1r2")
+
+[node name="AspectRatioContainer" type="AspectRatioContainer" parent="."]
+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="AspectRatioContainer"]
+custom_minimum_size = Vector2(200, 0)
+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="AspectRatioContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="ScrollContainer" type="ScrollContainer" parent="AspectRatioContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+follow_focus = true
+horizontal_scroll_mode = 0
+
+[node name="MessageContainer" type="VBoxContainer" parent="AspectRatioContainer/MarginContainer/VBoxContainer/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 10
+
+[node name="Label" type="Label" parent="AspectRatioContainer/MarginContainer/VBoxContainer/ScrollContainer/MessageContainer"]
+layout_mode = 2
+
+[node name="CommandScrollContainer" type="ScrollContainer" parent="AspectRatioContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+vertical_scroll_mode = 0
+
+[node name="CommandTextEdit" type="TextEdit" parent="AspectRatioContainer/MarginContainer/VBoxContainer/CommandScrollContainer"]
+custom_minimum_size = Vector2(0, 35)
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="VisibleTimer" type="Timer" parent="."]
+wait_time = 5.0
diff --git a/Scenes/Items/goblet.tscn b/Scenes/Items/goblet.tscn
new file mode 100644
index 0000000..f321721
--- /dev/null
+++ b/Scenes/Items/goblet.tscn
@@ -0,0 +1,80 @@
+[gd_scene load_steps=12 format=3 uid="uid://cfsot3wc41tx"]
+
+[ext_resource type="Script" uid="uid://bntxwxn6ns5l0" path="res://Scripts/Items/goblet.gd" id="1_ihtnh"]
+[ext_resource type="Script" uid="uid://ctwdmnqaxkty" path="res://Scripts/Resources/item.gd" id="2_ihtnh"]
+[ext_resource type="Texture2D" uid="uid://1eu6ubigu64i" path="res://Assets/Textures/kenny/Orange/texture_01.png" id="3_ihtnh"]
+[ext_resource type="Script" uid="uid://bahmrqvs4pafg" path="res://Scripts/Items/interactable.gd" id="4_ajay6"]
+[ext_resource type="Shader" uid="uid://c0chn7ll6p7yk" path="res://Assets/Shaders/outline.gdshader" id="4_lowgp"]
+
+[sub_resource type="Resource" id="Resource_lowgp"]
+script = ExtResource("2_ihtnh")
+value = 100
+name = "Golden Goblet"
+weight = 10
+primary_objective = true
+secondary_objective = false
+metadata/_custom_type_script = "uid://ctwdmnqaxkty"
+
+[sub_resource type="BoxMesh" id="BoxMesh_lowgp"]
+size = Vector3(0.25, 1, 0.25)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ajay6"]
+albedo_texture = ExtResource("3_ihtnh")
+uv1_triplanar = true
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_ihtnh"]
+points = PackedVector3Array(-0.125, -0.5, -0.125, -0.125, -0.5, 0.125, -0.125, 0.5, -0.125, 0.125, -0.5, -0.125, 0.125, -0.5, 0.125, -0.125, 0.5, 0.125, 0.125, 0.5, -0.125, 0.125, 0.5, 0.125)
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_ajay6"]
+render_priority = 0
+shader = ExtResource("4_lowgp")
+shader_parameter/normal_threshold = 1.5
+shader_parameter/depth_threshold = 0.05
+shader_parameter/depth_artifact_correction_coef = 2.0
+shader_parameter/outline_color = Color(0.882353, 0.882353, 0.882353, 1)
+
+[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ajay6"]
+properties/0/path = NodePath(".:position")
+properties/0/spawn = true
+properties/0/replication_mode = 1
+properties/1/path = NodePath(".:rotation")
+properties/1/spawn = true
+properties/1/replication_mode = 1
+properties/2/path = NodePath(".:visible")
+properties/2/spawn = true
+properties/2/replication_mode = 1
+properties/3/path = NodePath(".:collision_layer")
+properties/3/spawn = true
+properties/3/replication_mode = 1
+properties/4/path = NodePath(".:freeze")
+properties/4/spawn = true
+properties/4/replication_mode = 1
+
+[node name="Goblet" type="RigidBody3D"]
+collision_layer = 2
+collision_mask = 3
+continuous_cd = true
+script = ExtResource("1_ihtnh")
+item = SubResource("Resource_lowgp")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+mesh = SubResource("BoxMesh_lowgp")
+surface_material_override/0 = SubResource("StandardMaterial3D_ajay6")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+shape = SubResource("ConvexPolygonShape3D_ihtnh")
+
+[node name="Interactable" type="Node3D" parent="."]
+script = ExtResource("4_ajay6")
+
+[node name="outline" type="MeshInstance3D" parent="Interactable"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+visible = false
+mesh = SubResource("BoxMesh_lowgp")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("ShaderMaterial_ajay6")
+
+[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
+replication_config = SubResource("SceneReplicationConfig_ajay6")
diff --git a/Scenes/Lobby.tscn b/Scenes/Lobby.tscn
new file mode 100644
index 0000000..e44e8e9
--- /dev/null
+++ b/Scenes/Lobby.tscn
@@ -0,0 +1,59 @@
+[gd_scene load_steps=5 format=3 uid="uid://x4ohd5qj3xpy"]
+
+[ext_resource type="Texture2D" uid="uid://youvyib1feqp" path="res://Assets/Textures/kenny/Dark/texture_02.png" id="1_bpe3w"]
+[ext_resource type="Script" uid="uid://cdc5npqxn0eda" path="res://Scripts/Level/lobby.gd" id="1_lolwk"]
+[ext_resource type="PackedScene" uid="uid://cfsot3wc41tx" path="res://Scenes/Items/goblet.tscn" id="3_wgubu"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8j60k"]
+albedo_texture = ExtResource("1_bpe3w")
+uv1_triplanar = true
+
+[node name="Lobby" type="Node3D"]
+script = ExtResource("1_lolwk")
+
+[node name="Terrain" type="Node3D" parent="."]
+
+[node name="Floor" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+material_override = SubResource("StandardMaterial3D_8j60k")
+use_collision = true
+size = Vector3(25, 0.5, 25)
+
+[node name="Wall1" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 13)
+material_override = SubResource("StandardMaterial3D_8j60k")
+use_collision = true
+size = Vector3(27, 3, 1)
+
+[node name="Wall2" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -13)
+material_override = SubResource("StandardMaterial3D_8j60k")
+use_collision = true
+size = Vector3(27, 3, 1)
+
+[node name="Wall3" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13, 1, 0)
+material_override = SubResource("StandardMaterial3D_8j60k")
+use_collision = true
+size = Vector3(1, 3, 25)
+
+[node name="Wall4" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13, 1, 0)
+material_override = SubResource("StandardMaterial3D_8j60k")
+use_collision = true
+size = Vector3(1, 3, 25)
+
+[node name="spawnpoint1" type="Marker3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.06025, 9.57144)
+
+[node name="spawnpoint2" type="Marker3D" parent="."]
+transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 1.75494, -11.1718)
+
+[node name="spawnpoint3" type="Marker3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.3942, 1.59, -0.787676)
+
+[node name="spawnpoint4" type="Marker3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.92384, 1.69536, -1.57535)
+
+[node name="Goblet" parent="." instance=ExtResource("3_wgubu")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.07599, 2, 0)
diff --git a/Scenes/Menu/InventoryMenu.tscn b/Scenes/Menu/InventoryMenu.tscn
new file mode 100644
index 0000000..cd5e04d
--- /dev/null
+++ b/Scenes/Menu/InventoryMenu.tscn
@@ -0,0 +1,58 @@
+[gd_scene load_steps=5 format=3 uid="uid://01ujg1e7atlb"]
+
+[ext_resource type="Script" uid="uid://ddwvtegkiite7" path="res://Scripts/Menu/inventory_menu.gd" id="1_iulmg"]
+[ext_resource type="Theme" uid="uid://bvso6uowlb8dh" path="res://Assets/Themes/theme.tres" id="1_r3ydb"]
+[ext_resource type="PackedScene" uid="uid://bnpbkpr5ref6k" path="res://Scenes/Menu/InventoryUiItem.tscn" id="2_o58an"]
+
+[sub_resource type="Theme" id="Theme_r3ydb"]
+
+[node name="InventoryMenu" type="Node"]
+script = ExtResource("1_iulmg")
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="Panel" type="Panel" parent="CanvasLayer"]
+offset_left = 241.0
+offset_top = 146.0
+offset_right = 241.0
+offset_bottom = 146.0
+
+[node name="PanelContainer" type="PanelContainer" parent="CanvasLayer"]
+offset_left = 159.0
+offset_top = 94.0
+offset_right = 1042.0
+offset_bottom = 578.0
+theme = ExtResource("1_r3ydb")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/PanelContainer"]
+layout_mode = 2
+
+[node name="Title" type="Label" parent="CanvasLayer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+theme = SubResource("Theme_r3ydb")
+theme_type_variation = &"HeaderLarge"
+text = "Inventory"
+horizontal_alignment = 1
+
+[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+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="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/PanelContainer/VBoxContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/PanelContainer/VBoxContainer/MarginContainer/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="ItemHeader" parent="CanvasLayer/PanelContainer/VBoxContainer/MarginContainer/ScrollContainer/VBoxContainer" instance=ExtResource("2_o58an")]
+layout_mode = 2
+
+[node name="CloseButton" type="Button" parent="CanvasLayer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+text = "Close
+"
diff --git a/Scenes/Menu/InventoryUiItem.tscn b/Scenes/Menu/InventoryUiItem.tscn
new file mode 100644
index 0000000..76e9ac7
--- /dev/null
+++ b/Scenes/Menu/InventoryUiItem.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=2 format=3 uid="uid://bnpbkpr5ref6k"]
+
+[ext_resource type="Script" uid="uid://bv3glh0xi771m" path="res://Scripts/Menu/inventory_ui_item.gd" id="1_ahd61"]
+
+[node name="ItemHeader" type="HBoxContainer"]
+size_flags_vertical = 0
+script = ExtResource("1_ahd61")
+
+[node name="Icon" type="Label" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Icon"
+
+[node name="Name" type="Label" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 5.0
+text = "Name"
+
+[node name="Value" type="Label" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Value"
+
+[node name="Weight" type="Label" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Weight"
diff --git a/Scenes/Menu/LobbyMenu.tscn b/Scenes/Menu/LobbyMenu.tscn
new file mode 100644
index 0000000..268c809
--- /dev/null
+++ b/Scenes/Menu/LobbyMenu.tscn
@@ -0,0 +1,137 @@
+[gd_scene load_steps=3 format=3 uid="uid://dt1v3f2oc7vbw"]
+
+[ext_resource type="Script" uid="uid://b2pee67ics25u" path="res://Scripts/Menu/lobby_menu.gd" id="1_4ga1p"]
+[ext_resource type="Theme" uid="uid://bvso6uowlb8dh" path="res://Assets/Themes/theme.tres" id="2_oc7xs"]
+
+[node name="LobbyMenu" type="Control"]
+process_mode = 3
+z_index = 1
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 6
+size_flags_vertical = 6
+script = ExtResource("1_4ga1p")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 20
+
+[node name="LobbyLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Lobby"
+
+[node name="SteamLobbyIdButton" type="Button" parent="VBoxContainer/HBoxContainer"]
+visible = false
+layout_mode = 2
+text = "Copy Lobby Code"
+
+[node name="Disconnect" type="Button" parent="VBoxContainer/HBoxContainer"]
+visible = false
+layout_mode = 2
+text = "Disconnect"
+
+[node name="Name" type="LineEdit" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
+layout_mode = 2
+current_tab = 0
+
+[node name="Steam" type="Control" parent="VBoxContainer/TabContainer"]
+layout_mode = 2
+metadata/_tab_index = 0
+
+[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/TabContainer/Steam"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 187.0
+grow_horizontal = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Steam/PanelContainer"]
+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="VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="Address" type="LineEdit" parent="VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="Host" type="Button" parent="VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Host
+"
+
+[node name="Join" type="Button" parent="VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Join
+"
+
+[node name="ENet" type="Control" parent="VBoxContainer/TabContainer"]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 1
+
+[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/TabContainer/ENet"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 83.0
+grow_horizontal = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/ENet/PanelContainer"]
+layout_mode = 2
+theme = ExtResource("2_oc7xs")
+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="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="HostToggleContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="HostLabel" type="Label" parent="VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer/HostToggleContainer"]
+layout_mode = 2
+text = "Host"
+
+[node name="Host" type="CheckButton" parent="VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer/HostToggleContainer"]
+layout_mode = 2
+
+[node name="Address" type="LineEdit" parent="VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+placeholder_text = "Enter IP Address to Join"
+
+[node name="Join" type="Button" parent="VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Join"
+
+[node name="LobbyContainer" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -40.0
+offset_top = -40.0
+grow_horizontal = 0
+grow_vertical = 0
diff --git a/Scenes/Mobs/character.tscn b/Scenes/Mobs/character.tscn
new file mode 100644
index 0000000..b6b180a
--- /dev/null
+++ b/Scenes/Mobs/character.tscn
@@ -0,0 +1,483 @@
+[gd_scene load_steps=26 format=3 uid="uid://cc1m2a1obsyn4"]
+
+[ext_resource type="Script" uid="uid://c1um4tmthbbpk" path="res://Scripts/fpc/character.gd" id="1_0t4e8"]
+[ext_resource type="Script" uid="uid://darcj1vokaiv2" path="res://Scripts/Resources/inventory.gd" id="2_bmlhv"]
+[ext_resource type="Script" uid="uid://c5g0jt1apb2al" path="res://Scripts/fpc/EditorModule.gd" id="3_v3ckk"]
+[ext_resource type="Script" uid="uid://bhfftu01dsfk7" path="res://Scripts/fpc/debug.gd" id="3_x1wcc"]
+[ext_resource type="PackedScene" uid="uid://01ujg1e7atlb" path="res://Scenes/Menu/InventoryMenu.tscn" id="4_8j081"]
+
+[sub_resource type="Resource" id="Resource_jcsm3"]
+script = ExtResource("2_bmlhv")
+metadata/_custom_type_script = "uid://darcj1vokaiv2"
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kp17n"]
+albedo_color = Color(0, 1, 0, 1)
+clearcoat_enabled = true
+clearcoat_roughness = 0.2
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_jw1de"]
+material = SubResource("StandardMaterial3D_kp17n")
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uy03j"]
+
+[sub_resource type="Animation" id="Animation_j8cx7"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+
+[sub_resource type="Animation" id="Animation_5ec5e"]
+resource_name = "crouch"
+length = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0), Vector3(0, 1.12508, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 2
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 2
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_5e5t5"]
+_data = {
+&"RESET": SubResource("Animation_j8cx7"),
+&"crouch": SubResource("Animation_5ec5e")
+}
+
+[sub_resource type="Animation" id="Animation_gh776"]
+resource_name = "RESET"
+length = 0.001
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+
+[sub_resource type="Animation" id="Animation_8ku67"]
+resource_name = "sprint"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.06, -0.25, 0, 0.25, -0.01, 0, 0, 0, 0, 0, -0.06, -0.25, 0.01, 0.25, 0.01, 0, 0, 0, 0, 0, 0.06, -0.25, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(0.05, -0.25, 0, 0.2, -0.01, 0, -0.2, 0.000186046, 0.2, 0.000186046, 0.05, -0.2, -0.01, 0.2, -0.01, 0, -0.2, 0, 0.2, 0, 0.05, -0.2, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="Animation" id="Animation_lrqmv"]
+resource_name = "walk"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, -0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.04, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(-0.05, -0.25, 0, 0.2, 0.005, 0, -0.2, 0.000186046, 0.2, 0.000186046, -0.05, -0.2, 0.005, 0.2, 0.005, 0, -0.2, 0, 0.2, 0, -0.05, -0.2, 0.005, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_o0unb"]
+_data = {
+&"RESET": SubResource("Animation_gh776"),
+&"sprint": SubResource("Animation_8ku67"),
+&"walk": SubResource("Animation_lrqmv")
+}
+
+[sub_resource type="Animation" id="Animation_fvvjq"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_s07ye"]
+resource_name = "jump"
+length = 3.0
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.6, 3),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_3eyjl"]
+resource_name = "land_center"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_l1rph"]
+resource_name = "land_left"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_vsknp"]
+resource_name = "land_right"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, -0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_qeg5r"]
+_data = {
+&"RESET": SubResource("Animation_fvvjq"),
+&"jump": SubResource("Animation_s07ye"),
+&"land_center": SubResource("Animation_3eyjl"),
+&"land_left": SubResource("Animation_l1rph"),
+&"land_right": SubResource("Animation_vsknp")
+}
+
+[sub_resource type="Theme" id="Theme_wdf0f"]
+MarginContainer/constants/margin_bottom = 10
+MarginContainer/constants/margin_left = 10
+MarginContainer/constants/margin_right = 10
+MarginContainer/constants/margin_top = 10
+
+[sub_resource type="SphereShape3D" id="SphereShape3D_k4wwl"]
+
+[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_a88hh"]
+properties/0/path = NodePath(".:position")
+properties/0/spawn = true
+properties/0/replication_mode = 1
+properties/1/path = NodePath(".:rotation")
+properties/1/spawn = true
+properties/1/replication_mode = 1
+properties/2/path = NodePath("Head:rotation")
+properties/2/spawn = true
+properties/2/replication_mode = 1
+
+[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "CAMERA_RAYCAST", "HEADBOB_ANIMATION", "JUMP_ANIMATION", "CROUCH_ANIMATION", "COLLISION_MESH")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0, 0)
+script = ExtResource("1_0t4e8")
+HEAD = NodePath("Head")
+CAMERA = NodePath("Head/Camera")
+CAMERA_RAYCAST = NodePath("Head/Camera/CamRayCast3D")
+INVENTORY = SubResource("Resource_jcsm3")
+HEADBOB_ANIMATION = NodePath("Head/HeadbobAnimation")
+JUMP_ANIMATION = NodePath("Head/JumpAnimation")
+CROUCH_ANIMATION = NodePath("CrouchAnimation")
+COLLISION_MESH = NodePath("Collision")
+
+[node name="Mesh" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("CapsuleMesh_jw1de")
+
+[node name="Collision" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("CapsuleShape3D_uy03j")
+
+[node name="CrouchAnimation" type="AnimationPlayer" parent="."]
+libraries = {
+&"": SubResource("AnimationLibrary_5e5t5")
+}
+
+[node name="Head" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
+
+[node name="Camera" type="Camera3D" parent="Head"]
+
+[node name="CamRayCast3D" type="RayCast3D" parent="Head/Camera"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.72772e-08, 0, -0.395256)
+target_position = Vector3(0, 0, -2)
+collision_mask = 3
+
+[node name="HeadbobAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+&"": SubResource("AnimationLibrary_o0unb")
+}
+blend_times = [&"RESET", &"RESET", 0.5, &"RESET", &"walk", 0.5, &"walk", &"RESET", 0.5]
+
+[node name="JumpAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+&"": SubResource("AnimationLibrary_qeg5r")
+}
+speed_scale = 4.0
+
+[node name="UserInterface" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 1
+
+[node name="DebugPanel" type="PanelContainer" parent="UserInterface"]
+visible = false
+layout_mode = 0
+offset_left = 10.0
+offset_top = 10.0
+offset_right = 453.0
+offset_bottom = 50.0
+theme = SubResource("Theme_wdf0f")
+script = ExtResource("3_x1wcc")
+
+[node name="MarginContainer" type="MarginContainer" parent="UserInterface/DebugPanel"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="UserInterface/DebugPanel/MarginContainer"]
+layout_mode = 2
+
+[node name="InventoryMenu" parent="UserInterface" instance=ExtResource("4_8j081")]
+
+[node name="CrouchCeilingDetection" type="ShapeCast3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("SphereShape3D_k4wwl")
+target_position = Vector3(0, 0.5, 0)
+
+[node name="EditorModule" type="Node" parent="."]
+script = ExtResource("3_v3ckk")
+
+[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
+replication_config = SubResource("SceneReplicationConfig_a88hh")
diff --git a/Scenes/Server/Server.tscn b/Scenes/Server/Server.tscn
new file mode 100644
index 0000000..af9547c
--- /dev/null
+++ b/Scenes/Server/Server.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://d1ofd327cqcd1"]
+
+[ext_resource type="Script" uid="uid://dwwtrox3lp2jp" path="res://scripts/server/server.gd" id="1_tov25"]
+
+[node name="Server" type="Node"]
+script = ExtResource("1_tov25")
diff --git a/Scenes/Server/ServerLobby.tscn b/Scenes/Server/ServerLobby.tscn
new file mode 100644
index 0000000..f50e889
--- /dev/null
+++ b/Scenes/Server/ServerLobby.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://cibp7jxet63em"]
+
+[ext_resource type="Script" uid="uid://dql0i4kd8813p" path="res://scripts/server/server_lobby.gd" id="1_omv2j"]
+
+[node name="ServerLobby" type="Node"]
+script = ExtResource("1_omv2j")
diff --git a/Scenes/quality-godot-first-person-2-main/.gitattributes b/Scenes/quality-godot-first-person-2-main/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/Scenes/quality-godot-first-person-2-main/.gitignore b/Scenes/quality-godot-first-person-2-main/.gitignore
new file mode 100644
index 0000000..4709183
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/.gitignore
@@ -0,0 +1,2 @@
+# Godot 4+ specific ignores
+.godot/
diff --git a/Scenes/quality-godot-first-person-2-main/LICENSE b/Scenes/quality-godot-first-person-2-main/LICENSE
new file mode 100644
index 0000000..f34733d
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Colormatic
+
+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/Scenes/quality-godot-first-person-2-main/README.md b/Scenes/quality-godot-first-person-2-main/README.md
new file mode 100644
index 0000000..0acb1cb
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/README.md
@@ -0,0 +1,67 @@
+# quality-godot-first-person-2
+Actually good first person controller for the Godot Engine.
+MIT License (credit Colormatic Studios)
+
+This first person controller was made because there aren't many first person controllers for Godot, and the ones that do exist are pretty bad.
+It is highly customizable and comes with many features, QOL, and clean code.
+
+Some parts came from StayAtHomeDev's FPS tutorial. You can find that [here](https://www.youtube.com/playlist?list=PLEHvj4yeNfeF6s-UVs5Zx5TfNYmeCiYwf).
+
+# Directions
+Move with WASD, space to jump, shift to sprint, C to crouch.
+
+**FEATURES:**
+- Extremely configurable
+- In-air momentum
+- Motion smoothing
+- FOV smoothing
+- Movement animations
+- Crouching
+- Sprinting
+- 2 crosshairs/reticles, one is animated (more to come?)
+- Controller/GamePad support (enabled through code, see wiki)
+- In-editor tools (enable editable children to use)
+
+If you make a cool game with this addon, I would love to hear about it!
+
+# Wiki
+**To start out**, you should probably remap all of the movement keys to your own control set.
+
+You can make this a super basic controller by just disabling everything.
+
+**How to add controller/GamePad support**
+- In the controls export group, there is a commented section at the end that says "Uncomment this if you want full controller support". Uncomment that block.
+- Make a key map for each direction (left, right, up, down) and map them to your joystick.
+- Write in these keymaps in the controls section of the player settings.
+- In the `handle_head_rotation` function, there is another block of commented code that says the same thing. Uncomment that too.
+- You should now be able to look around with the joystick. Make sure you add the other controls to the input map. (movement, jumping, crouching, sprinting, etc.)
+
+**Slope/staircase:**
+Credit to [roberto-urbani23](https://github.com/roberto-urbani23)
+In the character inspector, you can uncheck Stop on Slope and set the max angle to 89 (for some reason, 90 will make the player stuck). Also Snap Length to 1 otherwise your character will not remain attached to stairs if you sprint while going downstairs.
+
+**How to change settings:**
+Click on the character node and there should be settings in the "Feature Settings" group.
+
+**How to add animations for a mesh:**
+- Create a function for your animation and attach it to `_physics_process` to call it every frame.
+- Use `input_dir` as a boolean (it is actually a `Vector2`) to know if the player is walking.
+- Use the `state` member variable to tell if the player is sprinting or crouching.
+- Use the `is_on_floor` function to tell if the player is standing or falling.
+
+**How to change reticles (crosshairs):**
+Change the "Default Reticle" setting to your reticle file.
+During runtime:
+Use the `change_reticle` function on the character.
+
+**How to create a new reticle:**
+- Choose a reticle to base it off of.
+- Open that reticle and save it as a new reticle.
+- Remove the script from the reticle and create a new one. (for some reason you have to do this)
+- Edit the reticle to your needs.
+- Follow the "how to change reticles" directions to use it.
+
+**How to use the editor tools:**
+- Enable editable children on the `CharacterBody` node
+- Use the options in the Properties tab to change things
+- These changes apply in runtime as well
diff --git a/Scenes/quality-godot-first-person-2-main/addons/fpc/EditorModule.gd b/Scenes/quality-godot-first-person-2-main/addons/fpc/EditorModule.gd
new file mode 100644
index 0000000..0a3a74f
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/addons/fpc/EditorModule.gd
@@ -0,0 +1,49 @@
+@tool
+extends Node
+
+# This does not effect runtime yet but will in the future.
+
+@export_category("Controller Editor Module")
+@export_range(-360.0, 360.0, 0.01, "or_greater", "or_less") var head_y_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_y_rotation = new_rotation
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ update_configuration_warnings()
+@export_range(-90.0, 90.0, 0.01, "or_greater", "or_less") var head_x_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_x_rotation = new_rotation
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+ update_configuration_warnings()
+
+@export_group("Nodes")
+@export var CHARACTER : CharacterBody3D
+@export var head_path : String = "Head" # Relative to the parent node
+#@export var CAMERA : Camera3D
+#@export var HEADBOB_ANIMATION : AnimationPlayer
+#@export var JUMP_ANIMATION : AnimationPlayer
+#@export var CROUCH_ANIMATION : AnimationPlayer
+#@export var COLLISION_MESH : CollisionShape3D
+
+@onready var HEAD = get_node("../" + head_path)
+
+
+func _ready():
+ if !Engine.is_editor_hint():
+ #print("not editor")
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+
+
+func _get_configuration_warnings():
+ var warnings = []
+
+ if head_y_rotation > 360:
+ warnings.append("The head rotation is greater than 360")
+
+ if head_y_rotation < -360:
+ warnings.append("The head rotation is less than -360")
+
+ # Returning an empty array gives no warnings
+ return warnings
diff --git a/Scenes/quality-godot-first-person-2-main/addons/fpc/character.gd b/Scenes/quality-godot-first-person-2-main/addons/fpc/character.gd
new file mode 100644
index 0000000..5a5f7f7
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/addons/fpc/character.gd
@@ -0,0 +1,488 @@
+# COPYRIGHT Colormatic Studios
+# MIT license
+# Quality Godot First Person Controller v2
+
+
+extends CharacterBody3D
+
+
+#region Character Export Group
+
+## The settings for the character's movement and feel.
+@export_category("Character")
+## The speed that the character moves at without crouching or sprinting.
+@export var base_speed : float = 3.0
+## The speed that the character moves at when sprinting.
+@export var sprint_speed : float = 6.0
+## The speed that the character moves at when crouching.
+@export var crouch_speed : float = 1.0
+
+## How fast the character speeds up and slows down when Motion Smoothing is on.
+@export var acceleration : float = 10.0
+## How high the player jumps.
+@export var jump_velocity : float = 4.5
+## How far the player turns when the mouse is moved.
+@export var mouse_sensitivity : float = 0.1
+## Invert the X axis input for the camera.
+@export var invert_camera_x_axis : bool = false
+## Invert the Y axis input for the camera.
+@export var invert_camera_y_axis : bool = false
+## Whether the player can use movement inputs. Does not stop outside forces or jumping. See Jumping Enabled.
+@export var immobile : bool = false
+## The reticle file to import at runtime. By default are in res://addons/fpc/reticles/. Set to an empty string to remove.
+@export_file var default_reticle
+
+#endregion
+
+#region Nodes Export Group
+
+@export_group("Nodes")
+## A reference to the camera for use in the character script. This is the parent node to the camera and is rotated instead of the camera for mouse input.
+@export var HEAD : Node3D
+## A reference to the camera for use in the character script.
+@export var CAMERA : Camera3D
+## A reference to the headbob animation for use in the character script.
+@export var HEADBOB_ANIMATION : AnimationPlayer
+## A reference to the jump animation for use in the character script.
+@export var JUMP_ANIMATION : AnimationPlayer
+## A reference to the crouch animation for use in the character script.
+@export var CROUCH_ANIMATION : AnimationPlayer
+## A reference to the the player's collision shape for use in the character script.
+@export var COLLISION_MESH : CollisionShape3D
+
+#endregion
+
+#region Controls Export Group
+
+# We are using UI controls because they are built into Godot Engine so they can be used right away
+@export_group("Controls")
+## Use the Input Map to map a mouse/keyboard input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controls : Dictionary = {
+ LEFT = "ui_left",
+ RIGHT = "ui_right",
+ FORWARD = "ui_up",
+ BACKWARD = "ui_down",
+ JUMP = "ui_accept",
+ CROUCH = "crouch",
+ SPRINT = "sprint",
+ PAUSE = "ui_cancel"
+ }
+@export_subgroup("Controller Specific")
+## This only affects how the camera is handled, the rest should be covered by adding controller inputs to the existing actions in the Input Map.
+@export var controller_support : bool = false
+## Use the Input Map to map a controller input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controller_controls : Dictionary = {
+ LOOK_LEFT = "look_left",
+ LOOK_RIGHT = "look_right",
+ LOOK_UP = "look_up",
+ LOOK_DOWN = "look_down"
+ }
+## The sensitivity of the analog stick that controls camera rotation. Lower is less sensitive and higher is more sensitive.
+@export_range(0.001, 1, 0.001) var look_sensitivity : float = 0.035
+
+#endregion
+
+#region Feature Settings Export Group
+
+@export_group("Feature Settings")
+## Enable or disable jumping. Useful for restrictive storytelling environments.
+@export var jumping_enabled : bool = true
+## Whether the player can move in the air or not.
+@export var in_air_momentum : bool = true
+## Smooths the feel of walking.
+@export var motion_smoothing : bool = true
+## Enables or disables sprinting.
+@export var sprint_enabled : bool = true
+## Toggles the sprinting state when button is pressed or requires the player to hold the button down to remain sprinting.
+@export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0
+## Enables or disables crouching.
+@export var crouch_enabled : bool = true
+## Toggles the crouch state when button is pressed or requires the player to hold the button down to remain crouched.
+@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
+## Wether sprinting should effect FOV.
+@export var dynamic_fov : bool = true
+## If the player holds down the jump button, should the player keep hopping.
+@export var continuous_jumping : bool = true
+## Enables the view bobbing animation.
+@export var view_bobbing : bool = true
+## Enables an immersive animation when the player jumps and hits the ground.
+@export var jump_animation : bool = true
+## This determines wether the player can use the pause button, not wether the game will actually pause.
+@export var pausing_enabled : bool = true
+## Use with caution.
+@export var gravity_enabled : bool = true
+## If your game changes the gravity value during gameplay, check this property to allow the player to experience the change in gravity.
+@export var dynamic_gravity : bool = false
+
+#endregion
+
+#region Member Variable Initialization
+
+# These are variables used in this script that don't need to be exposed in the editor.
+var speed : float = base_speed
+var current_speed : float = 0.0
+# States: normal, crouching, sprinting
+var state : String = "normal"
+var low_ceiling : bool = false # This is for when the ceiling is too low and the player needs to crouch.
+var was_on_floor : bool = true # Was the player on the floor last frame (for landing animation)
+
+# The reticle should always have a Control node as the root
+var RETICLE : Control
+
+# Get the gravity from the project settings to be synced with RigidBody nodes
+var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process
+
+# Stores mouse input for rotating the camera in the physics process
+var mouseInput : Vector2 = Vector2(0,0)
+
+#endregion
+
+
+
+#region Main Control Flow
+
+func _ready():
+ #It is safe to comment this line if your game doesn't start with the mouse captured
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+
+ # If the controller is rotated in a certain direction for game design purposes, redirect this rotation into the head.
+ HEAD.rotation.y = rotation.y
+ rotation.y = 0
+
+ if default_reticle:
+ change_reticle(default_reticle)
+
+ initialize_animations()
+ check_controls()
+ enter_normal_state()
+
+
+func _process(_delta):
+ if pausing_enabled:
+ handle_pausing()
+
+ update_debug_menu_per_frame()
+
+
+func _physics_process(delta): # Most things happen here.
+ # Gravity
+ if dynamic_gravity:
+ gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
+ if not is_on_floor() and gravity and gravity_enabled:
+ velocity.y -= gravity * delta
+
+ handle_jumping()
+
+ var input_dir = Vector2.ZERO
+
+ if not immobile: # Immobility works by interrupting user input, so other forces can still be applied to the player
+ input_dir = Input.get_vector(controls.LEFT, controls.RIGHT, controls.FORWARD, controls.BACKWARD)
+
+ handle_movement(delta, input_dir)
+
+ handle_head_rotation()
+
+ # The player is not able to stand up if the ceiling is too low
+ low_ceiling = $CrouchCeilingDetection.is_colliding()
+
+ handle_state(input_dir)
+ if dynamic_fov: # This may be changed to an AnimationPlayer
+ update_camera_fov()
+
+ if view_bobbing:
+ play_headbob_animation(input_dir)
+
+ if jump_animation:
+ play_jump_animation()
+
+ update_debug_menu_per_tick()
+
+ was_on_floor = is_on_floor() # This must always be at the end of physics_process
+
+#endregion
+
+#region Input Handling
+
+func handle_jumping():
+ if jumping_enabled:
+ if continuous_jumping: # Hold down the jump button
+ if Input.is_action_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity # Adding instead of setting so jumping on slopes works properly
+ else:
+ if Input.is_action_just_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity
+
+
+func handle_movement(delta, input_dir):
+ var direction = input_dir.rotated(-HEAD.rotation.y)
+ direction = Vector3(direction.x, 0, direction.y)
+ move_and_slide()
+
+ if in_air_momentum:
+ if is_on_floor():
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+ else:
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+
+
+func handle_head_rotation():
+ if invert_camera_x_axis:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity
+
+ if invert_camera_y_axis:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity
+
+ if controller_support:
+ var controller_view_rotation = Input.get_vector(controller_controls.LOOK_DOWN, controller_controls.LOOK_UP, controller_controls.LOOK_RIGHT, controller_controls.LOOK_LEFT) * look_sensitivity # These are inverted because of the nature of 3D rotation.
+ if invert_camera_x_axis:
+ HEAD.rotation.x += controller_view_rotation.x * -1
+ else:
+ HEAD.rotation.x += controller_view_rotation.x
+
+ if invert_camera_y_axis:
+ HEAD.rotation.y += controller_view_rotation.y * -1
+ else:
+ HEAD.rotation.y += controller_view_rotation.y
+
+ mouseInput = Vector2(0,0)
+ HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
+
+
+func check_controls(): # If you add a control, you might want to add a check for it here.
+ # The actions are being disabled so the engine doesn't halt the entire project in debug mode
+ if !InputMap.has_action(controls.JUMP):
+ push_error("No control mapped for jumping. Please add an input map control. Disabling jump.")
+ jumping_enabled = false
+ if !InputMap.has_action(controls.LEFT):
+ push_error("No control mapped for move left. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.RIGHT):
+ push_error("No control mapped for move right. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.FORWARD):
+ push_error("No control mapped for move forward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.BACKWARD):
+ push_error("No control mapped for move backward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.PAUSE):
+ push_error("No control mapped for pause. Please add an input map control. Disabling pausing.")
+ pausing_enabled = false
+ if !InputMap.has_action(controls.CROUCH):
+ push_error("No control mapped for crouch. Please add an input map control. Disabling crouching.")
+ crouch_enabled = false
+ if !InputMap.has_action(controls.SPRINT):
+ push_error("No control mapped for sprint. Please add an input map control. Disabling sprinting.")
+ sprint_enabled = false
+
+#endregion
+
+#region State Handling
+
+func handle_state(moving):
+ if sprint_enabled:
+ if sprint_mode == 0:
+ if Input.is_action_pressed(controls.SPRINT) and state != "crouching":
+ if moving:
+ if state != "sprinting":
+ enter_sprint_state()
+ else:
+ if state == "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+ elif sprint_mode == 1:
+ if moving:
+ # If the player is holding sprint before moving, handle that scenario
+ if Input.is_action_pressed(controls.SPRINT) and state == "normal":
+ enter_sprint_state()
+ if Input.is_action_just_pressed(controls.SPRINT):
+ match state:
+ "normal":
+ enter_sprint_state()
+ "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+
+ if crouch_enabled:
+ if crouch_mode == 0:
+ if Input.is_action_pressed(controls.CROUCH) and state != "sprinting":
+ if state != "crouching":
+ enter_crouch_state()
+ elif state == "crouching" and !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+ elif crouch_mode == 1:
+ if Input.is_action_just_pressed(controls.CROUCH):
+ match state:
+ "normal":
+ enter_crouch_state()
+ "crouching":
+ if !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+
+
+# Any enter state function should only be called once when you want to enter that state, not every frame.
+func enter_normal_state():
+ #print("entering normal state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "normal"
+ speed = base_speed
+
+func enter_crouch_state():
+ #print("entering crouch state")
+ state = "crouching"
+ speed = crouch_speed
+ CROUCH_ANIMATION.play("crouch")
+
+func enter_sprint_state():
+ #print("entering sprint state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "sprinting"
+ speed = sprint_speed
+
+#endregion
+
+#region Animation Handling
+
+func initialize_animations():
+ # Reset the camera position
+ # If you want to change the default head height, change these animations.
+ HEADBOB_ANIMATION.play("RESET")
+ JUMP_ANIMATION.play("RESET")
+ CROUCH_ANIMATION.play("RESET")
+
+func play_headbob_animation(moving):
+ if moving and is_on_floor():
+ var use_headbob_animation : String
+ match state:
+ "normal","crouching":
+ use_headbob_animation = "walk"
+ "sprinting":
+ use_headbob_animation = "sprint"
+
+ var was_playing : bool = false
+ if HEADBOB_ANIMATION.current_animation == use_headbob_animation:
+ was_playing = true
+
+ HEADBOB_ANIMATION.play(use_headbob_animation, 0.25)
+ HEADBOB_ANIMATION.speed_scale = (current_speed / base_speed) * 1.75
+ if !was_playing:
+ HEADBOB_ANIMATION.seek(float(randi() % 2)) # Randomize the initial headbob direction
+ # Let me explain that piece of code because it looks like it does the opposite of what it actually does.
+ # The headbob animation has two starting positions. One is at 0 and the other is at 1.
+ # randi() % 2 returns either 0 or 1, and so the animation randomly starts at one of the starting positions.
+ # This code is extremely performant but it makes no sense.
+
+ else:
+ if HEADBOB_ANIMATION.current_animation == "sprint" or HEADBOB_ANIMATION.current_animation == "walk":
+ HEADBOB_ANIMATION.speed_scale = 1
+ HEADBOB_ANIMATION.play("RESET", 1)
+
+func play_jump_animation():
+ if !was_on_floor and is_on_floor(): # The player just landed
+ var facing_direction : Vector3 = CAMERA.get_global_transform().basis.x
+ var facing_direction_2D : Vector2 = Vector2(facing_direction.x, facing_direction.z).normalized()
+ var velocity_2D : Vector2 = Vector2(velocity.x, velocity.z).normalized()
+
+ # Compares velocity direction against the camera direction (via dot product) to determine which landing animation to play.
+ var side_landed : int = round(velocity_2D.dot(facing_direction_2D))
+
+ if side_landed > 0:
+ JUMP_ANIMATION.play("land_right", 0.25)
+ elif side_landed < 0:
+ JUMP_ANIMATION.play("land_left", 0.25)
+ else:
+ JUMP_ANIMATION.play("land_center", 0.25)
+
+#endregion
+
+#region Debug Menu
+
+func update_debug_menu_per_frame():
+ $UserInterface/DebugPanel.add_property("FPS", Performance.get_monitor(Performance.TIME_FPS), 0)
+ var status : String = state
+ if !is_on_floor():
+ status += " in the air"
+ $UserInterface/DebugPanel.add_property("State", status, 4)
+
+
+func update_debug_menu_per_tick():
+ # Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
+ current_speed = Vector3.ZERO.distance_to(get_real_velocity())
+ $UserInterface/DebugPanel.add_property("Speed", snappedf(current_speed, 0.001), 1)
+ $UserInterface/DebugPanel.add_property("Target speed", speed, 2)
+ var cv : Vector3 = get_real_velocity()
+ var vd : Array[float] = [
+ snappedf(cv.x, 0.001),
+ snappedf(cv.y, 0.001),
+ snappedf(cv.z, 0.001)
+ ]
+ var readable_velocity : String = "X: " + str(vd[0]) + " Y: " + str(vd[1]) + " Z: " + str(vd[2])
+ $UserInterface/DebugPanel.add_property("Velocity", readable_velocity, 3)
+
+
+func _unhandled_input(event : InputEvent):
+ if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
+ mouseInput.x += event.relative.x
+ mouseInput.y += event.relative.y
+ # Toggle debug menu
+ elif event is InputEventKey:
+ if event.is_released():
+ # Where we're going, we don't need InputMap
+ if event.keycode == 4194338: # F7
+ $UserInterface/DebugPanel.visible = !$UserInterface/DebugPanel.visible
+
+#endregion
+
+#region Misc Functions
+
+func change_reticle(reticle): # Yup, this function is kinda strange
+ if RETICLE:
+ RETICLE.queue_free()
+
+ RETICLE = load(reticle).instantiate()
+ RETICLE.character = self
+ $UserInterface.add_child(RETICLE)
+
+
+func update_camera_fov():
+ if state == "sprinting":
+ CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3)
+ else:
+ CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3)
+
+func handle_pausing():
+ if Input.is_action_just_pressed(controls.PAUSE):
+ # You may want another node to handle pausing, because this player may get paused too.
+ match Input.mouse_mode:
+ Input.MOUSE_MODE_CAPTURED:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ #get_tree().paused = false
+ Input.MOUSE_MODE_VISIBLE:
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+ #get_tree().paused = false
+
+#endregion
diff --git a/Scenes/quality-godot-first-person-2-main/addons/fpc/character.tscn b/Scenes/quality-godot-first-person-2-main/addons/fpc/character.tscn
new file mode 100644
index 0000000..3cfd7c8
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/addons/fpc/character.tscn
@@ -0,0 +1,455 @@
+[gd_scene load_steps=22 format=3 uid="uid://cc1m2a1obsyn4"]
+
+[ext_resource type="Script" path="res://addons/fpc/character.gd" id="1_0t4e8"]
+[ext_resource type="Script" path="res://addons/fpc/EditorModule.gd" id="3_v3ckk"]
+[ext_resource type="Script" path="res://addons/fpc/debug.gd" id="3_x1wcc"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kp17n"]
+albedo_color = Color(0.909804, 0.596078, 0, 1)
+clearcoat_enabled = true
+clearcoat_roughness = 0.2
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_jw1de"]
+material = SubResource("StandardMaterial3D_kp17n")
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uy03j"]
+
+[sub_resource type="Animation" id="Animation_j8cx7"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+
+[sub_resource type="Animation" id="Animation_5ec5e"]
+resource_name = "crouch"
+length = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0), Vector3(0, 1.12508, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 2
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 2
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_5e5t5"]
+_data = {
+"RESET": SubResource("Animation_j8cx7"),
+"crouch": SubResource("Animation_5ec5e")
+}
+
+[sub_resource type="Animation" id="Animation_gh776"]
+resource_name = "RESET"
+length = 0.001
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+
+[sub_resource type="Animation" id="Animation_8ku67"]
+resource_name = "sprint"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.06, -0.25, 0, 0.25, -0.01, 0, 0, 0, 0, 0, -0.06, -0.25, 0.01, 0.25, 0.01, 0, 0, 0, 0, 0, 0.06, -0.25, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(0.05, -0.25, 0, 0.2, -0.01, 0, -0.2, 0.000186046, 0.2, 0.000186046, 0.05, -0.2, -0.01, 0.2, -0.01, 0, -0.2, 0, 0.2, 0, 0.05, -0.2, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="Animation" id="Animation_lrqmv"]
+resource_name = "walk"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, -0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.04, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(-0.05, -0.25, 0, 0.2, 0.005, 0, -0.2, 0.000186046, 0.2, 0.000186046, -0.05, -0.2, 0.005, 0.2, 0.005, 0, -0.2, 0, 0.2, 0, -0.05, -0.2, 0.005, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_o0unb"]
+_data = {
+"RESET": SubResource("Animation_gh776"),
+"sprint": SubResource("Animation_8ku67"),
+"walk": SubResource("Animation_lrqmv")
+}
+
+[sub_resource type="Animation" id="Animation_fvvjq"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_s07ye"]
+resource_name = "jump"
+length = 3.0
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.6, 3),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_3eyjl"]
+resource_name = "land_center"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_l1rph"]
+resource_name = "land_left"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_vsknp"]
+resource_name = "land_right"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, -0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_qeg5r"]
+_data = {
+"RESET": SubResource("Animation_fvvjq"),
+"jump": SubResource("Animation_s07ye"),
+"land_center": SubResource("Animation_3eyjl"),
+"land_left": SubResource("Animation_l1rph"),
+"land_right": SubResource("Animation_vsknp")
+}
+
+[sub_resource type="Theme" id="Theme_wdf0f"]
+MarginContainer/constants/margin_bottom = 10
+MarginContainer/constants/margin_left = 10
+MarginContainer/constants/margin_right = 10
+MarginContainer/constants/margin_top = 10
+
+[sub_resource type="SphereShape3D" id="SphereShape3D_k4wwl"]
+
+[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "HEADBOB_ANIMATION", "JUMP_ANIMATION", "CROUCH_ANIMATION", "COLLISION_MESH")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0, 0)
+script = ExtResource("1_0t4e8")
+default_reticle = "res://addons/fpc/reticles/reticle_1.tscn"
+HEAD = NodePath("Head")
+CAMERA = NodePath("Head/Camera")
+HEADBOB_ANIMATION = NodePath("Head/HeadbobAnimation")
+JUMP_ANIMATION = NodePath("Head/JumpAnimation")
+CROUCH_ANIMATION = NodePath("CrouchAnimation")
+COLLISION_MESH = NodePath("Collision")
+
+[node name="Mesh" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("CapsuleMesh_jw1de")
+
+[node name="Collision" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("CapsuleShape3D_uy03j")
+
+[node name="CrouchAnimation" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_5e5t5")
+}
+
+[node name="Head" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
+
+[node name="Camera" type="Camera3D" parent="Head"]
+
+[node name="HeadbobAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+"": SubResource("AnimationLibrary_o0unb")
+}
+blend_times = [&"RESET", &"RESET", 0.5, &"RESET", &"walk", 0.5, &"walk", &"RESET", 0.5]
+
+[node name="JumpAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+"": SubResource("AnimationLibrary_qeg5r")
+}
+speed_scale = 4.0
+
+[node name="UserInterface" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 1
+
+[node name="DebugPanel" type="PanelContainer" parent="UserInterface"]
+visible = false
+layout_mode = 0
+offset_left = 10.0
+offset_top = 10.0
+offset_right = 453.0
+offset_bottom = 50.0
+theme = SubResource("Theme_wdf0f")
+script = ExtResource("3_x1wcc")
+
+[node name="MarginContainer" type="MarginContainer" parent="UserInterface/DebugPanel"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="UserInterface/DebugPanel/MarginContainer"]
+layout_mode = 2
+
+[node name="CrouchCeilingDetection" type="ShapeCast3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("SphereShape3D_k4wwl")
+target_position = Vector3(0, 0.5, 0)
+
+[node name="EditorModule" type="Node" parent="."]
+script = ExtResource("3_v3ckk")
diff --git a/Scenes/quality-godot-first-person-2-main/addons/fpc/debug.gd b/Scenes/quality-godot-first-person-2-main/addons/fpc/debug.gd
new file mode 100644
index 0000000..efdb7a4
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/addons/fpc/debug.gd
@@ -0,0 +1,18 @@
+extends PanelContainer
+
+
+func _process(delta):
+ if visible:
+ pass
+
+func add_property(title : String, value, order : int): # This can either be called once for a static property or called every frame for a dynamic property
+ var target
+ target = $MarginContainer/VBoxContainer.find_child(title, true, false) # I have no idea what true and false does here, the function should be more specific
+ if !target:
+ target = Label.new() # Debug lines are of type Label
+ $MarginContainer/VBoxContainer.add_child(target)
+ target.name = title
+ target.text = title + ": " + str(value)
+ elif visible:
+ target.text = title + ": " + str(value)
+ $MarginContainer/VBoxContainer.move_child(target, order)
diff --git a/Scenes/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_0.tscn b/Scenes/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_0.tscn
new file mode 100644
index 0000000..2828124
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_0.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=2 format=3 uid="uid://coqpusufa8a6k"]
+
+[sub_resource type="GDScript" id="GDScript_10f85"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var character : CharacterBody3D
+
+@export_group(\"Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+
+func update_reticle_settings():
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+"
+
+[node name="Reticle" type="CenterContainer"]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_10f85")
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
diff --git a/Scenes/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_1.tscn b/Scenes/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_1.tscn
new file mode 100644
index 0000000..bb83b83
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_1.tscn
@@ -0,0 +1,104 @@
+[gd_scene load_steps=2 format=3 uid="uid://3mij3cjhkwsm"]
+
+[sub_resource type="GDScript" id="GDScript_a8kpl"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var reticle_lines : Array[Line2D]
+@export var character : CharacterBody3D
+
+@export_group(\"Animate\")
+@export var animated_reticle : bool = true
+@export var reticle_speed : float = 0.5
+@export var reticle_spread : float = 4.0
+
+@export_group(\"Dot Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+@export_group(\"Line Settings\")
+@export var line_color : Color = Color.WHITE
+@export var line_width : int = 2
+@export var line_length : int = 10
+@export var line_distance : int = 5
+@export_enum(\"None\", \"Round\") var cap_mode : int = 0
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+ if animated_reticle:
+ animate_reticle_lines()
+
+
+func animate_reticle_lines():
+ var vel = character.get_real_velocity()
+ var origin = Vector3(0,0,0)
+ var pos = Vector2(0,0)
+ var speed = origin.distance_to(vel)
+
+ reticle_lines[0].position = lerp(reticle_lines[0].position, pos + Vector2(0, -speed * reticle_spread), reticle_speed)
+ reticle_lines[1].position = lerp(reticle_lines[1].position, pos + Vector2(-speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[2].position = lerp(reticle_lines[2].position, pos + Vector2(speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[3].position = lerp(reticle_lines[3].position, pos + Vector2(0, speed * reticle_spread), reticle_speed)
+
+
+func update_reticle_settings():
+ # Dot
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+
+ # Lines
+ for line in reticle_lines:
+ line.default_color = line_color
+ line.width = line_width
+ if cap_mode == 0:
+ line.begin_cap_mode = Line2D.LINE_CAP_NONE
+ line.end_cap_mode = Line2D.LINE_CAP_NONE
+ elif cap_mode == 1:
+ line.begin_cap_mode = Line2D.LINE_CAP_ROUND
+ line.end_cap_mode = Line2D.LINE_CAP_ROUND
+
+ # Please someone find a better way to do this
+ reticle_lines[0].points[0].y = -line_distance
+ reticle_lines[0].points[1].y = -line_length - line_distance
+ reticle_lines[1].points[0].x = -line_distance
+ reticle_lines[1].points[1].x = -line_length - line_distance
+ reticle_lines[2].points[0].x = line_distance
+ reticle_lines[2].points[1].x = line_length + line_distance
+ reticle_lines[3].points[0].y = line_distance
+ reticle_lines[3].points[1].y = line_length + line_distance
+"
+
+[node name="Reticle" type="CenterContainer" node_paths=PackedStringArray("reticle_lines")]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_a8kpl")
+reticle_lines = [NodePath("top"), NodePath("left"), NodePath("right"), NodePath("bottom")]
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
+
+[node name="top" type="Line2D" parent="."]
+points = PackedVector2Array(0, -5, 0, -15)
+width = 2.0
+
+[node name="left" type="Line2D" parent="."]
+points = PackedVector2Array(-5, 0, -15, 0)
+width = 2.0
+
+[node name="right" type="Line2D" parent="."]
+points = PackedVector2Array(5, 0, 15, 0)
+width = 2.0
+
+[node name="bottom" type="Line2D" parent="."]
+points = PackedVector2Array(0, 5, 0, 15)
+width = 2.0
diff --git a/Scenes/quality-godot-first-person-2-main/icon.svg b/Scenes/quality-godot-first-person-2-main/icon.svg
new file mode 100644
index 0000000..ea6ce87
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/icon.svg
@@ -0,0 +1,126 @@
+
+
+
+
diff --git a/Scenes/quality-godot-first-person-2-main/icon.svg.import b/Scenes/quality-godot-first-person-2-main/icon.svg.import
new file mode 100644
index 0000000..9e0a681
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://64npoko7rqya"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Scenes/quality-godot-first-person-2-main/project.godot b/Scenes/quality-godot-first-person-2-main/project.godot
new file mode 100644
index 0000000..4626ad9
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/project.godot
@@ -0,0 +1,65 @@
+; 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
+
+[application]
+
+config/name="first person controller 2.0"
+run/main_scene="res://test_world.tscn"
+config/features=PackedStringArray("4.3", "Forward Plus")
+config/icon="res://icon.svg"
+
+[display]
+
+window/size/mode=2
+
+[input]
+
+ui_left={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
+, 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)
+]
+}
+ui_right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
+, 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)
+]
+}
+ui_up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
+, 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)
+]
+}
+ui_down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
+, 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)
+]
+}
+crouch={
+"deadzone": 0.5,
+"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)
+]
+}
+sprint={
+"deadzone": 0.5,
+"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)
+]
+}
diff --git a/Scenes/quality-godot-first-person-2-main/test_world.tscn b/Scenes/quality-godot-first-person-2-main/test_world.tscn
new file mode 100644
index 0000000..b6901b9
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/test_world.tscn
@@ -0,0 +1,106 @@
+[gd_scene load_steps=15 format=3 uid="uid://cs4drhmc1bql5"]
+
+[ext_resource type="PackedScene" uid="uid://cc1m2a1obsyn4" path="res://addons/fpc/character.tscn" id="1_e18vq"]
+[ext_resource type="Texture2D" uid="uid://pe7a4weirj2g" path="res://textures/dark.png" id="2_08fdt"]
+[ext_resource type="Texture2D" uid="uid://cxjxvqmf4boxq" path="res://textures/green.png" id="3_q4clv"]
+[ext_resource type="Texture2D" uid="uid://dsv4jm4vydflb" path="res://textures/orange.png" id="4_1ns5t"]
+
+[sub_resource type="PhysicalSkyMaterial" id="PhysicalSkyMaterial_edcox"]
+ground_color = Color(0.160784, 0.815686, 0.905882, 1)
+
+[sub_resource type="Sky" id="Sky_2iust"]
+sky_material = SubResource("PhysicalSkyMaterial_edcox")
+
+[sub_resource type="Environment" id="Environment_20rw3"]
+background_mode = 2
+sky = SubResource("Sky_2iust")
+tonemap_mode = 1
+ssao_enabled = true
+
+[sub_resource type="Gradient" id="Gradient_ur0vy"]
+colors = PackedColorArray(0, 0.476245, 0.0193456, 1, 0.360494, 0.612721, 0.119744, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_jd3pw"]
+frequency = 0.0027
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_7akuf"]
+width = 1024
+height = 1024
+in_3d_space = true
+seamless = true
+color_ramp = SubResource("Gradient_ur0vy")
+noise = SubResource("FastNoiseLite_jd3pw")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_67ysu"]
+albedo_texture = SubResource("NoiseTexture2D_7akuf")
+uv1_scale = Vector3(0.1, 0.1, 0.1)
+uv1_triplanar = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gomnb"]
+albedo_texture = ExtResource("2_08fdt")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_u0sbk"]
+albedo_texture = ExtResource("3_q4clv")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7j4uu"]
+albedo_texture = ExtResource("4_1ns5t")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[node name="test_world" type="Node3D"]
+
+[node name="Character" parent="." instance=ExtResource("1_e18vq")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_20rw3")
+
+[node name="sun" type="DirectionalLight3D" parent="."]
+transform = Transform3D(0.87959, -0.436605, 0.188936, 0, 0.397148, 0.917755, -0.475732, -0.807248, 0.349328, 0, 0, 0)
+light_energy = 2.0
+shadow_enabled = true
+
+[node name="terrain" type="Node3D" parent="."]
+
+[node name="CSGBox3D" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -0.5, 10)
+use_collision = true
+size = Vector3(30, 1, 30)
+material = SubResource("StandardMaterial3D_67ysu")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, -10)
+use_collision = true
+size = Vector3(10, 1, 10)
+material = SubResource("StandardMaterial3D_gomnb")
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 1.8, -13)
+use_collision = true
+size = Vector3(4, 0.5, 4)
+material = SubResource("StandardMaterial3D_gomnb")
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(0.939693, 0.34202, 0, -0.34202, 0.939693, 0, 0, 0, 1, -9.5, 1.2, -10)
+use_collision = true
+size = Vector3(10, 1, 10)
+material = SubResource("StandardMaterial3D_u0sbk")
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.5, 3, -15.5)
+use_collision = true
+size = Vector3(19, 8, 1)
+material = SubResource("StandardMaterial3D_7j4uu")
diff --git a/Scenes/quality-godot-first-person-2-main/textures/dark.png b/Scenes/quality-godot-first-person-2-main/textures/dark.png
new file mode 100644
index 0000000..69be211
Binary files /dev/null and b/Scenes/quality-godot-first-person-2-main/textures/dark.png differ
diff --git a/Scenes/quality-godot-first-person-2-main/textures/dark.png.import b/Scenes/quality-godot-first-person-2-main/textures/dark.png.import
new file mode 100644
index 0000000..2a82a3f
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/textures/dark.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pe7a4weirj2g"
+path.s3tc="res://.godot/imported/dark.png-6d46f668c80e231a58e570df85aad257.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/dark.png"
+dest_files=["res://.godot/imported/dark.png-6d46f668c80e231a58e570df85aad257.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Scenes/quality-godot-first-person-2-main/textures/green.png b/Scenes/quality-godot-first-person-2-main/textures/green.png
new file mode 100644
index 0000000..7bc7cf8
Binary files /dev/null and b/Scenes/quality-godot-first-person-2-main/textures/green.png differ
diff --git a/Scenes/quality-godot-first-person-2-main/textures/green.png.import b/Scenes/quality-godot-first-person-2-main/textures/green.png.import
new file mode 100644
index 0000000..7c7e044
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/textures/green.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxjxvqmf4boxq"
+path.s3tc="res://.godot/imported/green.png-b4f8ddc6b00d4e627f0e027e2e1193bf.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/green.png"
+dest_files=["res://.godot/imported/green.png-b4f8ddc6b00d4e627f0e027e2e1193bf.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Scenes/quality-godot-first-person-2-main/textures/orange.png b/Scenes/quality-godot-first-person-2-main/textures/orange.png
new file mode 100644
index 0000000..dec5b59
Binary files /dev/null and b/Scenes/quality-godot-first-person-2-main/textures/orange.png differ
diff --git a/Scenes/quality-godot-first-person-2-main/textures/orange.png.import b/Scenes/quality-godot-first-person-2-main/textures/orange.png.import
new file mode 100644
index 0000000..311f8ac
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/textures/orange.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dsv4jm4vydflb"
+path.s3tc="res://.godot/imported/orange.png-6785d3f8216fd22318e8ea839823715b.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/orange.png"
+dest_files=["res://.godot/imported/orange.png-6785d3f8216fd22318e8ea839823715b.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/Scenes/quality-godot-first-person-2-main/textures/purple.png b/Scenes/quality-godot-first-person-2-main/textures/purple.png
new file mode 100644
index 0000000..48a51c1
Binary files /dev/null and b/Scenes/quality-godot-first-person-2-main/textures/purple.png differ
diff --git a/Scenes/quality-godot-first-person-2-main/textures/purple.png.import b/Scenes/quality-godot-first-person-2-main/textures/purple.png.import
new file mode 100644
index 0000000..9dc0969
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/textures/purple.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cl4kewig3pk7s"
+path="res://.godot/imported/purple.png-23488e84f4f0a47488be2c78494f2155.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://textures/purple.png"
+dest_files=["res://.godot/imported/purple.png-23488e84f4f0a47488be2c78494f2155.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Scenes/quality-godot-first-person-2-main/textures/red.png b/Scenes/quality-godot-first-person-2-main/textures/red.png
new file mode 100644
index 0000000..bf1cb17
Binary files /dev/null and b/Scenes/quality-godot-first-person-2-main/textures/red.png differ
diff --git a/Scenes/quality-godot-first-person-2-main/textures/red.png.import b/Scenes/quality-godot-first-person-2-main/textures/red.png.import
new file mode 100644
index 0000000..c8c15aa
--- /dev/null
+++ b/Scenes/quality-godot-first-person-2-main/textures/red.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1h161t0v6hau"
+path="res://.godot/imported/red.png-3cad0ca19141406d60f5fd2311159a86.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://textures/red.png"
+dest_files=["res://.godot/imported/red.png-3cad0ca19141406d60f5fd2311159a86.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Scenes/reticles/reticle_0.tscn b/Scenes/reticles/reticle_0.tscn
new file mode 100644
index 0000000..2828124
--- /dev/null
+++ b/Scenes/reticles/reticle_0.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=2 format=3 uid="uid://coqpusufa8a6k"]
+
+[sub_resource type="GDScript" id="GDScript_10f85"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var character : CharacterBody3D
+
+@export_group(\"Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+
+func update_reticle_settings():
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+"
+
+[node name="Reticle" type="CenterContainer"]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_10f85")
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
diff --git a/Scenes/reticles/reticle_1.tscn b/Scenes/reticles/reticle_1.tscn
new file mode 100644
index 0000000..bb83b83
--- /dev/null
+++ b/Scenes/reticles/reticle_1.tscn
@@ -0,0 +1,104 @@
+[gd_scene load_steps=2 format=3 uid="uid://3mij3cjhkwsm"]
+
+[sub_resource type="GDScript" id="GDScript_a8kpl"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var reticle_lines : Array[Line2D]
+@export var character : CharacterBody3D
+
+@export_group(\"Animate\")
+@export var animated_reticle : bool = true
+@export var reticle_speed : float = 0.5
+@export var reticle_spread : float = 4.0
+
+@export_group(\"Dot Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+@export_group(\"Line Settings\")
+@export var line_color : Color = Color.WHITE
+@export var line_width : int = 2
+@export var line_length : int = 10
+@export var line_distance : int = 5
+@export_enum(\"None\", \"Round\") var cap_mode : int = 0
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+ if animated_reticle:
+ animate_reticle_lines()
+
+
+func animate_reticle_lines():
+ var vel = character.get_real_velocity()
+ var origin = Vector3(0,0,0)
+ var pos = Vector2(0,0)
+ var speed = origin.distance_to(vel)
+
+ reticle_lines[0].position = lerp(reticle_lines[0].position, pos + Vector2(0, -speed * reticle_spread), reticle_speed)
+ reticle_lines[1].position = lerp(reticle_lines[1].position, pos + Vector2(-speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[2].position = lerp(reticle_lines[2].position, pos + Vector2(speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[3].position = lerp(reticle_lines[3].position, pos + Vector2(0, speed * reticle_spread), reticle_speed)
+
+
+func update_reticle_settings():
+ # Dot
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+
+ # Lines
+ for line in reticle_lines:
+ line.default_color = line_color
+ line.width = line_width
+ if cap_mode == 0:
+ line.begin_cap_mode = Line2D.LINE_CAP_NONE
+ line.end_cap_mode = Line2D.LINE_CAP_NONE
+ elif cap_mode == 1:
+ line.begin_cap_mode = Line2D.LINE_CAP_ROUND
+ line.end_cap_mode = Line2D.LINE_CAP_ROUND
+
+ # Please someone find a better way to do this
+ reticle_lines[0].points[0].y = -line_distance
+ reticle_lines[0].points[1].y = -line_length - line_distance
+ reticle_lines[1].points[0].x = -line_distance
+ reticle_lines[1].points[1].x = -line_length - line_distance
+ reticle_lines[2].points[0].x = line_distance
+ reticle_lines[2].points[1].x = line_length + line_distance
+ reticle_lines[3].points[0].y = line_distance
+ reticle_lines[3].points[1].y = line_length + line_distance
+"
+
+[node name="Reticle" type="CenterContainer" node_paths=PackedStringArray("reticle_lines")]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_a8kpl")
+reticle_lines = [NodePath("top"), NodePath("left"), NodePath("right"), NodePath("bottom")]
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
+
+[node name="top" type="Line2D" parent="."]
+points = PackedVector2Array(0, -5, 0, -15)
+width = 2.0
+
+[node name="left" type="Line2D" parent="."]
+points = PackedVector2Array(-5, 0, -15, 0)
+width = 2.0
+
+[node name="right" type="Line2D" parent="."]
+points = PackedVector2Array(5, 0, 15, 0)
+width = 2.0
+
+[node name="bottom" type="Line2D" parent="."]
+points = PackedVector2Array(0, 5, 0, 15)
+width = 2.0
diff --git a/Scenes/town.tscn b/Scenes/town.tscn
new file mode 100644
index 0000000..b5e2cb7
--- /dev/null
+++ b/Scenes/town.tscn
@@ -0,0 +1,47 @@
+[gd_scene load_steps=3 format=3 uid="uid://b5lihf68rflsh"]
+
+[ext_resource type="Texture2D" uid="uid://youvyib1feqp" path="res://Assets/Textures/kenny/Dark/texture_02.png" id="1_kswdu"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_iau6b"]
+albedo_texture = ExtResource("1_kswdu")
+uv1_triplanar = true
+
+[node name="Town" type="Node3D"]
+
+[node name="Terrain" type="Node3D" parent="."]
+
+[node name="Floor" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+material_override = SubResource("StandardMaterial3D_iau6b")
+use_collision = true
+size = Vector3(50, 0.5, 50)
+
+[node name="wall 1" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 12)
+material_override = SubResource("StandardMaterial3D_iau6b")
+use_collision = true
+size = Vector3(25, 10, 1)
+
+[node name="wall 2" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, -12)
+material_override = SubResource("StandardMaterial3D_iau6b")
+use_collision = true
+size = Vector3(25, 10, 1)
+
+[node name="wall 3" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13, 5, 0)
+material_override = SubResource("StandardMaterial3D_iau6b")
+use_collision = true
+size = Vector3(1, 10, 25)
+
+[node name="wall 4" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16, 5, 0)
+material_override = SubResource("StandardMaterial3D_iau6b")
+use_collision = true
+size = Vector3(1, 10, 25)
+
+[node name="platform" type="CSGBox3D" parent="Terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 7)
+material_override = SubResource("StandardMaterial3D_iau6b")
+use_collision = true
+size = Vector3(5, 0.5, 5)
diff --git a/Scenes/world.tscn b/Scenes/world.tscn
new file mode 100644
index 0000000..150a537
--- /dev/null
+++ b/Scenes/world.tscn
@@ -0,0 +1,56 @@
+[gd_scene load_steps=11 format=3 uid="uid://cjimt73bcja16"]
+
+[ext_resource type="Script" uid="uid://klp20wju1i26" path="res://Scripts/world.gd" id="1_8j60k"]
+[ext_resource type="PackedScene" uid="uid://x4ohd5qj3xpy" path="res://Scenes/Lobby.tscn" id="2_5a7ea"]
+[ext_resource type="PackedScene" uid="uid://dt1v3f2oc7vbw" path="res://Scenes/Menu/LobbyMenu.tscn" id="4_fo5ed"]
+[ext_resource type="Script" uid="uid://bsyvwqveefopb" path="res://Scripts/Level/level.gd" id="5_5ukr8"]
+[ext_resource type="PackedScene" uid="uid://d1ofd327cqcd1" path="res://Scenes/Server/Server.tscn" id="5_fo5ed"]
+[ext_resource type="PackedScene" uid="uid://b5lihf68rflsh" path="res://Scenes/town.tscn" id="5_o06y3"]
+[ext_resource type="PackedScene" uid="uid://dtpqexue3vgl6" path="res://Scenes/Chat/ChatMenu.tscn" id="7_k7acu"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_go701"]
+sky_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
+ground_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
+
+[sub_resource type="Sky" id="Sky_5a7ea"]
+sky_material = SubResource("ProceduralSkyMaterial_go701")
+
+[sub_resource type="Environment" id="Environment_8j60k"]
+background_mode = 2
+sky = SubResource("Sky_5a7ea")
+tonemap_mode = 2
+glow_enabled = true
+
+[node name="World" type="Node" node_paths=PackedStringArray("lobby_menu", "server", "level")]
+script = ExtResource("1_8j60k")
+lobby_menu = NodePath("LobbyMenu")
+server = NodePath("Server")
+level = NodePath("Level")
+
+[node name="Server" parent="." instance=ExtResource("5_fo5ed")]
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_8j60k")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
+shadow_enabled = true
+
+[node name="Level" type="Node" parent="." node_paths=PackedStringArray("lobby")]
+script = ExtResource("5_5ukr8")
+lobby = NodePath("Lobby")
+
+[node name="Lobby" parent="Level" instance=ExtResource("2_5a7ea")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 48.3442, -31.095, 21.7287)
+
+[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="Level"]
+_spawnable_scenes = PackedStringArray("uid://cc1m2a1obsyn4")
+spawn_path = NodePath("..")
+
+[node name="Town" parent="Level" instance=ExtResource("5_o06y3")]
+
+[node name="LobbyMenu" parent="." instance=ExtResource("4_fo5ed")]
+visible = false
+
+[node name="ChatMenu" parent="." instance=ExtResource("7_k7acu")]
+visible = false
diff --git a/Scripts/Chat/chat_menu.gd b/Scripts/Chat/chat_menu.gd
new file mode 100644
index 0000000..9172fed
--- /dev/null
+++ b/Scripts/Chat/chat_menu.gd
@@ -0,0 +1,246 @@
+class_name ChatMenu
+extends Control
+
+
+#region Onready Variables
+
+@onready var scroll_container = $AspectRatioContainer/MarginContainer/VBoxContainer/ScrollContainer
+@onready var command_text_edit = $AspectRatioContainer/MarginContainer/VBoxContainer/CommandScrollContainer/CommandTextEdit
+@onready var message_container = $AspectRatioContainer/MarginContainer/VBoxContainer/ScrollContainer/MessageContainer
+@onready var visible_timer = $VisibleTimer
+
+#endregion
+
+
+#region Variables
+
+var opened : bool = false
+var clear_command_text : bool = false
+
+#endregion
+
+
+#region Signals
+
+signal chat_opened()
+signal spawn_item(item_name : String, x : String, y : String, z : String)
+signal lobby_move(x : String, z : String)
+
+#endregion
+
+
+#region Godot Functions
+
+func _ready() -> void:
+ hide()
+ opened = false
+ command_text_edit.gui_input.connect(_command_text_edit_gui_input)
+ visible_timer.timeout.connect(hide)
+
+func _process(_delta : float):
+ if clear_command_text == true:
+ command_text_edit.text = ""
+ clear_command_text = false
+
+ if opened == true:
+ if Input.is_action_just_pressed("pause"):
+ remove_focus()
+ visible_timer.start()
+ return
+
+ if Input.is_action_just_pressed("open_chat"):
+ visible_timer.stop()
+ chat_opened.emit()
+ show()
+ focus()
+
+ if Input.is_action_just_pressed("open_chat_as_command"):
+ visible_timer.stop()
+ command_text_edit.text = "/"
+ chat_opened.emit()
+ show()
+ focus()
+ command_text_edit.set_caret_column(1, false, 0)
+
+#endregion
+
+
+#region Public Functions
+
+func focus():
+ mouse_filter = Control.MOUSE_FILTER_STOP
+ opened = true
+ Global.in_menu = true
+ command_text_edit.grab_focus()
+
+func remove_focus():
+ mouse_filter = Control.MOUSE_FILTER_IGNORE
+ opened = false
+ command_text_edit.release_focus()
+ Global.in_menu = false
+
+func send_message(message : String, keep_focus : bool, label_color : Color = Color(1, 1, 1, 1)):
+ visible_timer.stop()
+ show()
+ var message_label : Label = Label.new()
+ message_label.text = message
+ message_label.label_settings = LabelSettings.new()
+ message_label.label_settings.font_color = label_color
+ message_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
+ message_container.add_child(message_label)
+
+ if keep_focus == false:
+ remove_focus()
+ visible_timer.start()
+
+ get_tree().create_timer(0.01).timeout.connect(
+ func() -> void:
+ scroll_container.scroll_vertical = scroll_container.get_v_scroll_bar().max_value
+ )
+
+#endregion
+
+
+#region Signal Functions
+
+func _command_text_edit_gui_input(event : InputEvent):
+ if event.is_action_pressed("chat_send"):
+ send_message(command_text_edit.text, false)
+ check_command(command_text_edit.text)
+ clear_command_text = true
+
+#endregion
+
+
+#region "Private" Functions
+
+func check_command(command : String):
+ command.strip_edges(true, true)
+
+ if command.begins_with("/") == false:
+ return
+
+ if Global.is_admin == false:
+ send_message("Cannot run command without admin status", false)
+ return
+
+ command = command.lstrip("/")
+
+ # ~ SPAWN ~ #
+ if command.begins_with("spawn ") or command == "spawn":
+ command = command.lstrip("spawn ")
+ command = command.trim_prefix(" ")
+
+ #get the item name and check that it exists
+ var item_next_space : int = command.find(" ")
+ if item_next_space == -1:
+ send_message("Spawn Syntax error, spawn command looks like `spawn x y z`", false, Color(1, 0, 0, 1))
+ return
+
+ var item_name : String = command.substr(0, item_next_space)
+ command = command.lstrip(item_name)
+ command = command.trim_prefix(" ")
+
+ # if EntityResource.ids_by_name.has(item_name) == false:
+ # var entity_names = ""
+ # for key in EntityResource.data:
+ # if EntityResource.data[key].spawnable == false:
+ # continue
+
+ # entity_names += EntityResource.data[key].name + " "
+
+ # send_message("Entity: \"" + item_name + "\" was not found, cannot spawn. Entity List is: " + entity_names, false, Color(1, 0, 0, 1))
+ # return
+
+ #get x
+ item_next_space = command.find(" ")
+ if item_next_space == -1:
+ send_message("Spawn Syntax error, spawn command looks like `spawn x y z`", false, Color(1, 0, 0, 1))
+ return
+
+ var x_name : String = command.substr(0, item_next_space)
+ command = command.lstrip(x_name)
+ command = command.trim_prefix(" ")
+
+ if x_name != "~" and x_name.is_valid_int() == false:
+ send_message("Spawn Syntax error: x value was not ~ or int", false, Color(1, 0, 0, 1))
+ return
+
+ #get y
+ item_next_space = command.find(" ")
+ if item_next_space == -1:
+ send_message("Spawn Syntax error, spawn command looks like `spawn x y z`", false, Color(1, 0, 0, 1))
+ return
+
+ var y_name : String = command.substr(0, item_next_space)
+ command = command.lstrip(y_name)
+ command = command.trim_prefix(" ")
+
+ if y_name != "~" and y_name.is_valid_int() == false:
+ send_message("Spawn Syntax error: y value was not ~ or int", false, Color(1, 0, 0, 1))
+ return
+
+ #check z (which is what is left in command)
+ command = command.trim_prefix(" ")
+ command = command.trim_suffix(" ")
+ if command == "":
+ send_message("Spawn Syntax error, spawn command looks like `spawn x y z`", false, Color(1, 0, 0, 1))
+
+ if command != "~" and command.is_valid_int() == false:
+ send_message("Item Spawn Syntax error: z value was not ~ or int", false, Color(1, 0, 0, 1))
+ return
+
+ #call spawn item
+ spawn_item.emit(item_name, x_name, y_name, command)
+ return
+
+ # ~ LOBBY ~ #
+ if command.begins_with("lobby ") or command == "lobby":
+ command = command.lstrip("lobby ")
+ command = command.trim_prefix(" ")
+
+ #get the type name and check that it exists
+ var type_next_space : int = command.find(" ")
+ if type_next_space == -1:
+ send_message("Lobby Syntax error, lobby command looks like `lobby [args]`", false, Color(1, 0, 0, 1))
+ return
+
+ # ~ move ~ #
+ if command.begins_with("move ") or command == "move":
+ command = command.lstrip("move ")
+ command = command.trim_prefix(" ")
+
+ #error check for if there is no position
+ if command == "":
+ send_message("Lobby Move Syntax error, lobby move command looks like `lobby move x z`", false, Color(1, 0, 0, 1))
+ return
+
+ #get x
+ var position_space : int = command.find(" ")
+ if position_space == -1:
+ send_message("Lobby Move Syntax error, lobby move command looks like `lobby move x z`", false, Color(1, 0, 0, 1))
+ return
+
+ var x_name : String = command.substr(0, position_space)
+ command = command.lstrip(x_name)
+ command = command.trim_prefix(" ")
+
+ if x_name != "~" and x_name.is_valid_int() == false:
+ send_message("Lobby Move Syntax error: x value was not ~ or int", false, Color(1, 0, 0, 1))
+ return
+
+ #check z (which is what is left in command)
+ command = command.trim_prefix(" ")
+ command = command.trim_suffix(" ")
+ if command == "":
+ send_message("Lobby Move Syntax error, lobby move command looks like `lobby move x z`", false, Color(1, 0, 0, 1))
+
+ if command != "~" and command.is_valid_int() == false:
+ send_message("Lobby Move Syntax error: z value was not ~ or int", false, Color(1, 0, 0, 1))
+ return
+
+ #call spawn item
+ lobby_move.emit(x_name, command)
+ return
+
+#endregion
diff --git a/Scripts/Chat/chat_menu.gd.uid b/Scripts/Chat/chat_menu.gd.uid
new file mode 100644
index 0000000..3fafcc7
--- /dev/null
+++ b/Scripts/Chat/chat_menu.gd.uid
@@ -0,0 +1 @@
+uid://c8k0usfm5rdm3
diff --git a/Scripts/Items/goblet.gd b/Scripts/Items/goblet.gd
new file mode 100644
index 0000000..a28fc86
--- /dev/null
+++ b/Scripts/Items/goblet.gd
@@ -0,0 +1,5 @@
+extends RigidBody3D
+
+@export var item : Item
+@onready var interactable : Interactable = $Interactable
+@onready var is_interactable : bool = true
\ No newline at end of file
diff --git a/Scripts/Items/goblet.gd.uid b/Scripts/Items/goblet.gd.uid
new file mode 100644
index 0000000..922da1e
--- /dev/null
+++ b/Scripts/Items/goblet.gd.uid
@@ -0,0 +1 @@
+uid://bntxwxn6ns5l0
diff --git a/Scripts/Items/interactable.gd b/Scripts/Items/interactable.gd
new file mode 100644
index 0000000..eeeaf47
--- /dev/null
+++ b/Scripts/Items/interactable.gd
@@ -0,0 +1,5 @@
+extends Node3D
+
+class_name Interactable
+
+@onready var outline : MeshInstance3D = $outline
\ No newline at end of file
diff --git a/Scripts/Items/interactable.gd.uid b/Scripts/Items/interactable.gd.uid
new file mode 100644
index 0000000..bd711d3
--- /dev/null
+++ b/Scripts/Items/interactable.gd.uid
@@ -0,0 +1 @@
+uid://bahmrqvs4pafg
diff --git a/Scripts/Level/level.gd b/Scripts/Level/level.gd
new file mode 100644
index 0000000..3da97c4
--- /dev/null
+++ b/Scripts/Level/level.gd
@@ -0,0 +1,60 @@
+class_name Level
+extends Node
+
+var player_scene : PackedScene = preload("res://Scenes/Mobs/character.tscn")
+
+@export var lobby : Lobby
+
+var spawnid : int = 0
+
+func _ready() -> void:
+ spawn_player.rpc_id(1, 1)
+
+@rpc("any_peer", "call_local")
+func request_spawn_player(id : int) -> void:
+ #only run on the multiplayer instance
+ if not multiplayer.is_server():
+ return
+
+ #don't spawn an already spawned player
+ if get_node_or_null("Player" + str(id)) != null:
+ return
+
+ spawn_player.rpc_id(1, id)
+
+@rpc("any_peer", "call_local")
+func spawn_player(id: int) -> void:
+ var new_player : Player = player_scene.instantiate()
+
+ new_player.name = "Player" + str(id)
+ print("Spawning ID: ", id)
+ add_child(new_player)
+ var spawn_point: Marker3D = lobby.spawnpoints[spawnid]
+ print(spawn_point.global_position)
+ new_player.teleport_player.rpc(spawn_point.global_position, spawn_point.rotation)
+ spawnid += 1
+
+@rpc("authority", "call_local")
+func remove_client_from_server(id : int) -> void:
+ var disconnected_player : Player = get_node("Player" + str(id))
+ spawnid = 1
+ remove_child.call_deferred(disconnected_player)
+ disconnected_player.queue_free()
+
+@rpc("any_peer", "call_local")
+func disconnect_from_server():
+ remove_players()
+ spawnid = 0
+ # reset map
+ print("Disconnect: ", multiplayer.get_unique_id())
+ request_spawn_player.rpc(1)
+
+
+
+func remove_players(remove_host : bool = true) -> void:
+ for child in get_children():
+ if child is Player:
+ if !remove_host and child.name == "Player1":
+ continue
+ remove_child(child)
+ child.queue_free()
diff --git a/Scripts/Level/level.gd.uid b/Scripts/Level/level.gd.uid
new file mode 100644
index 0000000..c604e64
--- /dev/null
+++ b/Scripts/Level/level.gd.uid
@@ -0,0 +1 @@
+uid://bsyvwqveefopb
diff --git a/Scripts/Level/lobby.gd b/Scripts/Level/lobby.gd
new file mode 100644
index 0000000..ec7dd0a
--- /dev/null
+++ b/Scripts/Level/lobby.gd
@@ -0,0 +1,5 @@
+class_name Lobby
+extends Node3D
+
+
+@onready var spawnpoints : Array[Marker3D] = [$spawnpoint1, $spawnpoint2, $spawnpoint3, $spawnpoint4]
\ No newline at end of file
diff --git a/Scripts/Level/lobby.gd.uid b/Scripts/Level/lobby.gd.uid
new file mode 100644
index 0000000..1e360b7
--- /dev/null
+++ b/Scripts/Level/lobby.gd.uid
@@ -0,0 +1 @@
+uid://cdc5npqxn0eda
diff --git a/Scripts/Menu/inventory_menu.gd b/Scripts/Menu/inventory_menu.gd
new file mode 100644
index 0000000..ace3f44
--- /dev/null
+++ b/Scripts/Menu/inventory_menu.gd
@@ -0,0 +1,33 @@
+extends Node
+
+class_name InventoryMenu
+
+
+@onready var inventory : Inventory
+@onready var item_list : VBoxContainer = $CanvasLayer/PanelContainer/VBoxContainer/MarginContainer/ScrollContainer/VBoxContainer
+var item_scene : PackedScene = preload("res://Scenes/Menu/InventoryUiItem.tscn")
+
+func _ready() -> void:
+ $CanvasLayer.hide()
+ pass
+
+
+func toggle_inventory() -> bool:
+ var canvas = $CanvasLayer
+ if canvas.visible == true:
+ $CanvasLayer.hide()
+ return false
+ else:
+ update_inventory()
+ $CanvasLayer.show()
+ return true
+
+func update_inventory():
+ for i in inventory.items:
+ var new_item = item_scene.instantiate()
+ # new_item.set_values(i.item.name, i.item.value, i.item.weight)
+ item_list.add_child(new_item)
+ new_item.set_values(i.item.name, 10, 10)
+ print(i.item.name)
+ print(i)
+ pass
diff --git a/Scripts/Menu/inventory_menu.gd.uid b/Scripts/Menu/inventory_menu.gd.uid
new file mode 100644
index 0000000..7c50a50
--- /dev/null
+++ b/Scripts/Menu/inventory_menu.gd.uid
@@ -0,0 +1 @@
+uid://ddwvtegkiite7
diff --git a/Scripts/Menu/inventory_ui_item.gd b/Scripts/Menu/inventory_ui_item.gd
new file mode 100644
index 0000000..4a8cf55
--- /dev/null
+++ b/Scripts/Menu/inventory_ui_item.gd
@@ -0,0 +1,11 @@
+extends Node
+
+@onready var title : Label = $Name
+@onready var value : Label = $Value
+@onready var weight : Label = $Weight
+
+
+func set_values(new_title : String, new_value : int, new_weight : int) -> void:
+ title.text = new_title
+ value.text = str(new_value)
+ weight.text = str(new_weight)
diff --git a/Scripts/Menu/inventory_ui_item.gd.uid b/Scripts/Menu/inventory_ui_item.gd.uid
new file mode 100644
index 0000000..897b78f
--- /dev/null
+++ b/Scripts/Menu/inventory_ui_item.gd.uid
@@ -0,0 +1 @@
+uid://bv3glh0xi771m
diff --git a/Scripts/Menu/lobby_menu.gd b/Scripts/Menu/lobby_menu.gd
new file mode 100644
index 0000000..a2f8ece
--- /dev/null
+++ b/Scripts/Menu/lobby_menu.gd
@@ -0,0 +1,169 @@
+class_name LobbyMenu
+extends Node
+
+
+#region Onready Variables
+
+@onready var lobby_container : Container = $LobbyContainer
+@onready var username : LineEdit = $VBoxContainer/Name
+@onready var lobby_label : Label = $VBoxContainer/HBoxContainer/LobbyLabel
+@onready var steam_lobby_id_button : Button = $VBoxContainer/HBoxContainer/SteamLobbyIdButton
+@onready var disconnect_button : Button = $VBoxContainer/HBoxContainer/Disconnect
+@onready var tab_container : TabContainer = $VBoxContainer/TabContainer
+
+@onready var steam_tab : Control = $VBoxContainer/TabContainer/Steam
+@onready var steam_address_entry : LineEdit = $VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer/VBoxContainer/Address
+@onready var steam_host_button : Button = $VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer/VBoxContainer/Host
+@onready var steam_join_button : Button = $VBoxContainer/TabContainer/Steam/PanelContainer/MarginContainer/VBoxContainer/Join
+
+@onready var enet_address_entry : LineEdit = $VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer/Address
+@onready var enet_host_toggle : CheckButton = $VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer/HostToggleContainer/Host
+@onready var enet_join_button : Button = $VBoxContainer/TabContainer/ENet/PanelContainer/MarginContainer/VBoxContainer/Join
+
+#endregion
+
+
+#region Variables
+
+var steam_lobby_id : String = ""
+
+#endregion
+
+
+#region Signals
+
+signal disconnect_button_pressed()
+
+signal steam_host_pressed(new_username : String)
+signal steam_join_pressed(new_username : String)
+
+signal enet_host_toggled_on(new_username : String)
+signal enet_host_toggled_off()
+signal enet_join_pressed(new_username : String)
+
+#endregion
+
+
+#region Godot Functions
+
+func _ready() -> void:
+ #set username to steam username
+ username.text = "username"
+
+ disconnect_button.pressed.connect(_on_disconnet_button_pressed)
+
+ #steam
+ if Global.steam_connected == true:
+ username.text = Steam.getPersonaName()
+
+ steam_lobby_id_button.pressed.connect(_on_steam_lobby_id_button_pressed)
+ steam_host_button.pressed.connect(_on_steam_host_pressed)
+ steam_join_button.pressed.connect(_on_steam_join_pressed)
+
+ #enet
+ enet_host_toggle.toggled.connect(_on_enet_host_toggled)
+ enet_join_button.pressed.connect(_on_enet_join_pressed)
+
+ if Global.steam_connected == false:
+ tab_container.remove_child(steam_tab)
+
+#endregion
+
+
+#region Public Functions
+
+func reset_visible() -> void:
+ steam_lobby_id_button.hide()
+ disconnect_button.hide()
+ clear_lobby_text()
+ username.show()
+
+ #steam
+ steam_join_button.show()
+ steam_address_entry.show()
+ steam_host_button.show()
+
+ #enet
+ tab_container.tabs_visible = true
+ tab_container.show()
+ enet_address_entry.show()
+ enet_join_button.show()
+
+func request_lobby_list() -> void:
+ for child in lobby_container.get_children():
+ child.queue_free()
+
+ Steam.requestLobbyList()
+
+func set_lobby_text(new_names : Array[String]) -> void:
+ clear_lobby_text()
+
+ for new_name in new_names:
+ var name_label : Label = Label.new()
+ name_label.text = new_name
+ lobby_container.add_child(name_label)
+
+func clear_lobby_text() -> void:
+ var lobby_container_children = lobby_container.get_children()
+ for child in lobby_container_children:
+ child.free()
+
+func update_steam_lobby_id_button():
+ steam_lobby_id_button.show()
+ steam_lobby_id = Global.steam_lobby_id
+
+#endregion
+
+
+#region Signal Functions
+func _on_disconnet_button_pressed() -> void:
+ disconnect_button_pressed.emit()
+
+func _on_steam_host_pressed() -> void:
+ steam_host_pressed.emit(username.text)
+ steam_join_button.hide()
+ tab_container.tabs_visible = false
+ steam_address_entry.hide()
+ steam_host_button.hide()
+ username.hide()
+
+func _on_steam_join_pressed() -> void:
+ Global.steam_lobby_id = steam_address_entry.text
+ steam_join_pressed.emit(username.text)
+ steam_join_button.hide()
+ tab_container.tabs_visible = false
+ steam_address_entry.hide()
+ steam_host_button.hide()
+ username.hide()
+ disconnect_button.show()
+
+func _on_enet_host_toggled(toggled_on : bool) -> void:
+ if toggled_on:
+ enet_address_entry.hide()
+ enet_join_button.hide()
+ username.hide()
+ enet_host_toggled_on.emit(username.text)
+ tab_container.tabs_visible = false
+ return
+
+ tab_container.tabs_visible = true
+ enet_host_toggled_off.emit()
+ enet_address_entry.show()
+ enet_join_button.show()
+ username.show()
+
+func _on_enet_join_pressed() -> void:
+ if enet_address_entry.text.is_empty() == false:
+ Global.enet_address = enet_address_entry.text
+
+ tab_container.tabs_visible = false
+ enet_join_pressed.emit(username.text)
+ enet_join_button.hide()
+ tab_container.hide()
+ username.hide()
+ disconnect_button.show()
+
+func _on_steam_lobby_id_button_pressed():
+ DisplayServer.clipboard_set(steam_lobby_id)
+
+#endregion
diff --git a/Scripts/Menu/lobby_menu.gd.uid b/Scripts/Menu/lobby_menu.gd.uid
new file mode 100644
index 0000000..d4cffb8
--- /dev/null
+++ b/Scripts/Menu/lobby_menu.gd.uid
@@ -0,0 +1 @@
+uid://b2pee67ics25u
diff --git a/Scripts/Resources/inventory.gd b/Scripts/Resources/inventory.gd
new file mode 100644
index 0000000..e115b09
--- /dev/null
+++ b/Scripts/Resources/inventory.gd
@@ -0,0 +1,15 @@
+extends Resource
+
+class_name Inventory
+
+var items : Array[RigidBody3D]
+
+
+func get_current_slot(index : int) -> RigidBody3D:
+ return items[index]
+
+func add_item(item : RigidBody3D) -> void:
+ items.append(item)
+
+func remove_items(item : RigidBody3D) -> void:
+ items.erase(item)
\ No newline at end of file
diff --git a/Scripts/Resources/inventory.gd.uid b/Scripts/Resources/inventory.gd.uid
new file mode 100644
index 0000000..508e8fe
--- /dev/null
+++ b/Scripts/Resources/inventory.gd.uid
@@ -0,0 +1 @@
+uid://darcj1vokaiv2
diff --git a/Scripts/Resources/item.gd b/Scripts/Resources/item.gd
new file mode 100644
index 0000000..0f5557e
--- /dev/null
+++ b/Scripts/Resources/item.gd
@@ -0,0 +1,9 @@
+extends Resource
+
+class_name Item
+
+@export var value : int
+@export var name : String
+@export var weight : int
+@export var primary_objective : bool
+@export var secondary_objective : bool
diff --git a/Scripts/Resources/item.gd.uid b/Scripts/Resources/item.gd.uid
new file mode 100644
index 0000000..05d2279
--- /dev/null
+++ b/Scripts/Resources/item.gd.uid
@@ -0,0 +1 @@
+uid://ctwdmnqaxkty
diff --git a/Scripts/Server/player_data.gd b/Scripts/Server/player_data.gd
new file mode 100644
index 0000000..2b89d68
--- /dev/null
+++ b/Scripts/Server/player_data.gd
@@ -0,0 +1,12 @@
+class_name PlayerData
+
+extends Resource
+
+@export var id : int
+@export var lobby_index : int
+@export var name : String
+
+func _init(new_id : int = -1, new_lobby_index : int = -1, new_name : String = "username"):
+ id = new_id
+ lobby_index = new_lobby_index
+ name = new_name
\ No newline at end of file
diff --git a/Scripts/Server/player_data.gd.uid b/Scripts/Server/player_data.gd.uid
new file mode 100644
index 0000000..95d0899
--- /dev/null
+++ b/Scripts/Server/player_data.gd.uid
@@ -0,0 +1 @@
+uid://b8fojvchw16hl
diff --git a/Scripts/Server/server.gd b/Scripts/Server/server.gd
new file mode 100644
index 0000000..31e49c8
--- /dev/null
+++ b/Scripts/Server/server.gd
@@ -0,0 +1,273 @@
+class_name Server
+
+extends Node
+
+
+#region Constants
+
+const DEFAULT_PORT = 10567
+const MAX_CLIENTS = 3
+
+#endregion
+
+
+#region Onready Variables
+
+# @onready var microphone : AudioStreamPlayer3D = $Microphone
+
+#endregion
+
+
+#region Variables
+
+var players_ready : Array = []
+
+var peer : MultiplayerPeer = null
+
+#var mic_capture : AudioEffectOpusChunked
+
+var lobby_id : int = -1
+
+#endregion
+
+
+#region RPC
+
+# @rpc("any_peer", "unreliable")
+# func voice_packet_recieved(packet):
+# var sender_id = multiplayer.get_remote_sender_id()
+ # push_opus_packet_to_player.emit(sender_id, packet)
+
+#endregion
+
+
+#region Signals
+
+signal game_log(message : String, color : Color)
+
+signal server_close()
+
+signal client_connected_to_server()
+
+signal client_disconnected_from_server()
+
+signal remove_client_from_server(id : int)
+
+signal steam_lobby_created()
+
+# signal push_opus_packet_to_player(id : int, packet)
+
+#endregion
+
+
+#region Godot Functions
+
+func _ready() -> void:
+ for argument in OS.get_cmdline_args():
+ if argument == "-steam":
+ ready_steam()
+
+ # ready_voip()
+
+ #set multiplayer signals
+ multiplayer.peer_connected.connect(_multiplayer_peer_connected)
+ multiplayer.peer_disconnected.connect(_multiplayer_peer_disconnected)
+ multiplayer.connected_to_server.connect(_multiplayer_connected_to_server)
+ multiplayer.connection_failed.connect(_multiplayer_connection_failed)
+ multiplayer.server_disconnected.connect(_multiplayer_server_disconnected)
+
+
+ #set steam signals
+ if Global.steam_connected == true:
+ Steam.lobby_joined.connect(_steam_lobby_joined)
+ Steam.lobby_created.connect(_steam_lobby_created)
+
+func _process(_delta : float):
+ # process_voip()
+ Steam.run_callbacks()
+
+#endregion
+
+
+#region Ready Functions
+
+func ready_steam() -> void:
+ var steam_response : Dictionary = Steam.steamInitEx()
+ Global.steam_connected = steam_response["status"] == 0
+
+ if Global.steam_connected == false:
+ get_tree().create_timer(3.0).timeout.connect(
+ func():
+ game_log.emit("Could Not Connect To Steam", Color(1, 0, 0, 1))
+ )
+
+# func ready_voip() -> void:
+# assert(microphone.bus == "MicrophoneBus")
+# var mic_bus = AudioServer.get_bus_index("MicrophoneBus")
+# mic_capture = AudioServer.get_bus_effect(mic_bus, 0)
+
+#endregion
+
+
+#region Process Functions
+
+# func process_voip() -> void:
+# if mic_capture == null:
+# return
+
+# while mic_capture.chunk_available():
+# var packet = mic_capture.read_opus_packet(PackedByteArray())
+# mic_capture.drop_chunk()
+
+# if Global.voip_on == false:
+# continue
+
+# if Global.is_multiplayer and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
+# voice_packet_recieved.rpc(packet)
+
+#endregion
+
+
+#region Default Multiplayer Functions
+
+func destroy_lobby() -> void:
+ Global.is_multiplayer = false
+
+ multiplayer.multiplayer_peer.close()
+ peer = OfflineMultiplayerPeer.new()
+ multiplayer.multiplayer_peer = peer
+
+ server_close.emit()
+
+func disconnect_client() -> void:
+ Global.is_multiplayer = false
+
+ multiplayer.multiplayer_peer.close()
+ peer = OfflineMultiplayerPeer.new()
+ multiplayer.multiplayer_peer = peer
+
+ client_disconnected_from_server.emit()
+
+#endregion
+
+
+#region Enet Functions
+
+func enet_create_host() -> void:
+ #set multiplayer to true
+ Global.is_multiplayer = true
+
+ #init multiplayer host
+ peer = ENetMultiplayerPeer.new()
+ (peer as ENetMultiplayerPeer).create_server(DEFAULT_PORT, MAX_CLIENTS)
+ multiplayer.multiplayer_peer = peer
+
+func enet_create_client(address : String) -> void:
+ #set multiplayer to true
+ Global.is_multiplayer = true
+
+ #init client and wait for connection
+ peer = ENetMultiplayerPeer.new()
+ (peer as ENetMultiplayerPeer).create_client(address, DEFAULT_PORT)
+ multiplayer.set_multiplayer_peer(peer)
+ await multiplayer.connected_to_server
+
+#endregion
+
+
+#region Steam Functions
+
+func steam_create_host() -> void:
+ #players[1] = PlayerData.new(1, 0, new_username)
+ Steam.createLobby(Steam.LOBBY_TYPE_FRIENDS_ONLY, MAX_CLIENTS)
+
+func steam_join_lobby(new_lobby_id : int) -> void:
+ Steam.joinLobby(new_lobby_id)
+
+func steam_create_socket():
+ Global.is_multiplayer = true
+
+ peer = SteamMultiplayerPeer.new()
+ peer.create_host(0)
+ multiplayer.set_multiplayer_peer(peer)
+
+func steam_connect_socket(steam_id : int) -> void:
+ Global.is_multiplayer = true
+
+ peer = SteamMultiplayerPeer.new()
+ peer.create_client(steam_id, 0)
+ multiplayer.set_multiplayer_peer(peer)
+
+#endregion
+
+
+#region Signal Functions
+
+func _multiplayer_peer_connected(_id : int):
+ pass
+
+func _multiplayer_peer_disconnected(id : int):
+ if multiplayer.is_server():
+ game_log.emit("Player \"" + str(id) + "\" disconnected", Color(1, 0, 0, 1))
+ remove_client_from_server.emit(id)
+
+func _multiplayer_connected_to_server() -> void:
+ client_connected_to_server.emit()
+
+func _multiplayer_connection_failed() -> void:
+ Global.is_multiplayer = false
+ peer = OfflineMultiplayerPeer.new()
+ multiplayer.multiplayer_peer = peer
+ client_disconnected_from_server.emit()
+ game_log.emit("Server connection failed", Color(1, 0, 0, 1))
+
+func _multiplayer_server_disconnected() -> void:
+ Global.is_multiplayer = false
+ peer = OfflineMultiplayerPeer.new()
+ multiplayer.multiplayer_peer = peer
+ client_disconnected_from_server.emit()
+ game_log.emit("Server disconnected", Color(1, 0, 0, 1))
+
+func _steam_lobby_joined(new_lobby_id : int, _permissions : int, _locked : bool, response : int) -> void:
+ if response == Steam.CHAT_ROOM_ENTER_RESPONSE_SUCCESS:
+ lobby_id = new_lobby_id
+ var id : int = Steam.getLobbyOwner(new_lobby_id)
+ if id != Steam.getSteamID():
+ steam_connect_socket(id)
+ #player_register.rpc(username)
+ #players[multiplayer.get_unique_id()] = username
+ return
+ var message : String
+ match response:
+ Steam.CHAT_ROOM_ENTER_RESPONSE_DOESNT_EXIST:
+ message = "This lobby no longer exists."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_NOT_ALLOWED:
+ message = "You don't have permission to join this lobby."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_FULL:
+ message = "The lobby is now full."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_ERROR:
+ message = "Response errored."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_BANNED:
+ message = "You are banned from this lobby."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_LIMITED:
+ message = "You cannot join due to having a limited account."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_CLAN_DISABLED:
+ message = "This lobby is locked or disabled."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_COMMUNITY_BAN:
+ message = "This lobby is community locked."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_MEMBER_BLOCKED_YOU:
+ message = "A user in the lobby has blocked you from joining."
+ Steam.CHAT_ROOM_ENTER_RESPONSE_YOU_BLOCKED_MEMBER:
+ message = "A user you have blocked is in the lobby."
+ game_log.emit(message, Color(1, 0, 0, 1))
+
+func _steam_lobby_created(connected: int, this_lobby_id: int) -> void:
+ if connected == 1:
+ Global.steam_lobby_id = str(this_lobby_id)
+ Steam.setLobbyData(this_lobby_id, "name", str(Steam.getPersonaName(), "'s test server"))
+ steam_create_socket()
+ steam_lobby_created.emit()
+ return
+ game_log.emit("Error on create lobby!", Color(1, 0, 0, 1))
+
+#endregion
diff --git a/Scripts/Server/server.gd.uid b/Scripts/Server/server.gd.uid
new file mode 100644
index 0000000..c8949f5
--- /dev/null
+++ b/Scripts/Server/server.gd.uid
@@ -0,0 +1 @@
+uid://dwwtrox3lp2jp
diff --git a/Scripts/Server/server_lobby.gd b/Scripts/Server/server_lobby.gd
new file mode 100644
index 0000000..9d3da72
--- /dev/null
+++ b/Scripts/Server/server_lobby.gd
@@ -0,0 +1,71 @@
+class_name ServerLobby
+extends Node
+
+
+#region Exports
+
+@export var players : Dictionary[int, PlayerData] = {}
+
+#endregion
+
+
+#region Variables
+
+var free_lobby_indexs : Array[int] = []
+
+#endregion
+
+
+#region Signals
+
+signal player_registered()
+
+#endregion
+
+
+#region Client RPC
+
+@rpc("any_peer", "call_local")
+func request_player_register(id: int, username) -> void:
+ #only run on the multiplayer instance
+ if not multiplayer.is_server():
+ return
+
+ player_register.rpc_id(1, id, username)
+
+@rpc("any_peer", "call_local")
+func update_client_players(id : int, username : String) -> void:
+ if not players.has(id):
+ players[id] = PlayerData.new(id, players.size(), username)
+
+ player_registered.emit()
+
+#endregion
+
+
+#region Server RPC
+
+@rpc("authority", "call_local")
+func player_register(id : int, username : String) -> void:
+ var lobby_index : int = players.size()
+ if free_lobby_indexs.size() != 0:
+ lobby_index = free_lobby_indexs.pop_front()
+
+ players[id] = PlayerData.new(id, lobby_index, username)
+
+ #send the entire list for clients that join later
+ for player_id in players:
+ update_client_players.rpc(player_id, players[player_id].name)
+
+@rpc("authority", "call_local")
+func player_unregister(id) -> void:
+ free_lobby_indexs.append(players[id].lobby_index)
+ players.erase(id)
+ player_registered.emit()
+
+@rpc("authority", "call_local")
+func players_clear() -> void:
+ players.clear()
+ player_registered.emit()
+
+#endregion
diff --git a/Scripts/Server/server_lobby.gd.uid b/Scripts/Server/server_lobby.gd.uid
new file mode 100644
index 0000000..f516d7a
--- /dev/null
+++ b/Scripts/Server/server_lobby.gd.uid
@@ -0,0 +1 @@
+uid://k0xvfdmvtyrt
diff --git a/Scripts/fpc/EditorModule.gd b/Scripts/fpc/EditorModule.gd
new file mode 100644
index 0000000..0a3a74f
--- /dev/null
+++ b/Scripts/fpc/EditorModule.gd
@@ -0,0 +1,49 @@
+@tool
+extends Node
+
+# This does not effect runtime yet but will in the future.
+
+@export_category("Controller Editor Module")
+@export_range(-360.0, 360.0, 0.01, "or_greater", "or_less") var head_y_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_y_rotation = new_rotation
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ update_configuration_warnings()
+@export_range(-90.0, 90.0, 0.01, "or_greater", "or_less") var head_x_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_x_rotation = new_rotation
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+ update_configuration_warnings()
+
+@export_group("Nodes")
+@export var CHARACTER : CharacterBody3D
+@export var head_path : String = "Head" # Relative to the parent node
+#@export var CAMERA : Camera3D
+#@export var HEADBOB_ANIMATION : AnimationPlayer
+#@export var JUMP_ANIMATION : AnimationPlayer
+#@export var CROUCH_ANIMATION : AnimationPlayer
+#@export var COLLISION_MESH : CollisionShape3D
+
+@onready var HEAD = get_node("../" + head_path)
+
+
+func _ready():
+ if !Engine.is_editor_hint():
+ #print("not editor")
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+
+
+func _get_configuration_warnings():
+ var warnings = []
+
+ if head_y_rotation > 360:
+ warnings.append("The head rotation is greater than 360")
+
+ if head_y_rotation < -360:
+ warnings.append("The head rotation is less than -360")
+
+ # Returning an empty array gives no warnings
+ return warnings
diff --git a/Scripts/fpc/EditorModule.gd.uid b/Scripts/fpc/EditorModule.gd.uid
new file mode 100644
index 0000000..96be9c8
--- /dev/null
+++ b/Scripts/fpc/EditorModule.gd.uid
@@ -0,0 +1 @@
+uid://c5g0jt1apb2al
diff --git a/Scripts/fpc/character.gd b/Scripts/fpc/character.gd
new file mode 100644
index 0000000..c2db386
--- /dev/null
+++ b/Scripts/fpc/character.gd
@@ -0,0 +1,551 @@
+# COPYRIGHT Colormatic Studios
+# MIT license
+# Quality Godot First Person Controller v2
+
+class_name Player
+extends CharacterBody3D
+
+
+#region Character Export Group
+
+## The settings for the character's movement and feel.
+@export_category("Character")
+## The speed that the character moves at without crouching or sprinting.
+@export var base_speed : float = 3.0
+## The speed that the character moves at when sprinting.
+@export var sprint_speed : float = 6.0
+## The speed that the character moves at when crouching.
+@export var crouch_speed : float = 1.0
+
+## How fast the character speeds up and slows down when Motion Smoothing is on.
+@export var acceleration : float = 10.0
+## How high the player jumps.
+@export var jump_velocity : float = 4.5
+## How far the player turns when the mouse is moved.
+@export var mouse_sensitivity : float = 0.1
+## Invert the X axis input for the camera.
+@export var invert_camera_x_axis : bool = false
+## Invert the Y axis input for the camera.
+@export var invert_camera_y_axis : bool = false
+## Whether the player can use movement inputs. Does not stop outside forces or jumping. See Jumping Enabled.
+@export var immobile : bool = false
+## The reticle file to import at runtime. By default are in res://addons/fpc/reticles/. Set to an empty string to remove.
+@export_file var default_reticle = "res://Scenes/reticles/reticle_1.tscn"
+
+#endregion
+
+#region Nodes Export Group
+
+@export_group("Nodes")
+## A reference to the camera for use in the character script. This is the parent node to the camera and is rotated instead of the camera for mouse input.
+@export var HEAD : Node3D
+## A reference to the camera for use in the character script.
+@export var CAMERA : Camera3D
+# A reference to the Camera Ray Cast
+@export var CAMERA_RAYCAST : RayCast3D
+# current item player is looking at
+@onready var HIGHLIGHTED_ITEM : RigidBody3D
+# A reference to the inventory
+@export var INVENTORY : Inventory
+# A reference to the inventory menu
+@onready var inventory_menu : InventoryMenu
+## A reference to the headbob animation for use in the character script.
+@export var HEADBOB_ANIMATION : AnimationPlayer
+## A reference to the jump animation for use in the character script.
+@export var JUMP_ANIMATION : AnimationPlayer
+## A reference to the crouch animation for use in the character script.
+@export var CROUCH_ANIMATION : AnimationPlayer
+## A reference to the the player's collision shape for use in the character script.
+@export var COLLISION_MESH : CollisionShape3D
+
+#endregion
+
+#region Controls Export Group
+
+# We are using UI controls because they are built into Godot Engine so they can be used right away
+@export_group("Controls")
+## Use the Input Map to map a mouse/keyboard input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controls : Dictionary = {
+ LEFT = "move_left",
+ RIGHT = "move_right",
+ FORWARD = "move_forward",
+ BACKWARD = "move_back",
+ JUMP = "jump",
+ CROUCH = "crouch",
+ SPRINT = "sprint",
+ PAUSE = "pause",
+ INTERACT = "interact",
+ INVENTORY = "inventory"
+ }
+@export_subgroup("Controller Specific")
+## This only affects how the camera is handled, the rest should be covered by adding controller inputs to the existing actions in the Input Map.
+@export var controller_support : bool = false
+## Use the Input Map to map a controller input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controller_controls : Dictionary = {
+ LOOK_LEFT = "look_left",
+ LOOK_RIGHT = "look_right",
+ LOOK_UP = "look_up",
+ LOOK_DOWN = "look_down"
+ }
+## The sensitivity of the analog stick that controls camera rotation. Lower is less sensitive and higher is more sensitive.
+@export_range(0.001, 1, 0.001) var look_sensitivity : float = 0.035
+
+#endregion
+
+#region Feature Settings Export Group
+
+@export_group("Feature Settings")
+## Enable or disable jumping. Useful for restrictive storytelling environments.
+@export var jumping_enabled : bool = true
+## Whether the player can move in the air or not.
+@export var in_air_momentum : bool = true
+## Smooths the feel of walking.
+@export var motion_smoothing : bool = true
+## Enables or disables sprinting.
+@export var sprint_enabled : bool = true
+## Toggles the sprinting state when button is pressed or requires the player to hold the button down to remain sprinting.
+@export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0
+## Enables or disables crouching.
+@export var crouch_enabled : bool = true
+## Toggles the crouch state when button is pressed or requires the player to hold the button down to remain crouched.
+@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
+## Wether sprinting should effect FOV.
+@export var dynamic_fov : bool = true
+## If the player holds down the jump button, should the player keep hopping.
+@export var continuous_jumping : bool = true
+## Enables the view bobbing animation.
+@export var view_bobbing : bool = true
+## Enables an immersive animation when the player jumps and hits the ground.
+@export var jump_animation : bool = true
+## This determines wether the player can use the pause button, not wether the game will actually pause.
+@export var pausing_enabled : bool = true
+## Use with caution.
+@export var gravity_enabled : bool = true
+## If your game changes the gravity value during gameplay, check this property to allow the player to experience the change in gravity.
+@export var dynamic_gravity : bool = false
+
+#endregion
+
+#region Member Variable Initialization
+
+# These are variables used in this script that don't need to be exposed in the editor.
+var speed : float = base_speed
+var current_speed : float = 0.0
+# States: normal, crouching, sprinting
+var state : String = "normal"
+var low_ceiling : bool = false # This is for when the ceiling is too low and the player needs to crouch.
+var was_on_floor : bool = true # Was the player on the floor last frame (for landing animation)
+
+# The reticle should always have a Control node as the root
+var RETICLE : Control
+
+# Get the gravity from the project settings to be synced with RigidBody nodes
+var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process
+
+# Stores mouse input for rotating the camera in the physics process
+var mouseInput : Vector2 = Vector2(0,0)
+
+#endregion
+
+
+#region Godot Functions
+func _enter_tree() -> void:
+
+ #get the id off the player name and set authority
+ var new_id = name.lstrip("Player")
+ set_multiplayer_authority(new_id.to_int())
+
+func _ready():
+ #It is safe to comment this line if your game doesn't start with the mouse captured
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+
+ # If the controller is rotated in a certain direction for game design purposes, redirect this rotation into the head.
+ HEAD.rotation.y = rotation.y
+ rotation.y = 0
+
+ if default_reticle:
+ change_reticle(default_reticle)
+
+ initialize_animations()
+ check_controls()
+ enter_normal_state()
+ if is_multiplayer_authority():
+ CAMERA.current = true
+ inventory_menu = $UserInterface/InventoryMenu
+ inventory_menu.inventory = INVENTORY
+
+func _process(_delta):
+ # if pausing_enabled:
+ # handle_pausing()
+
+ update_debug_menu_per_frame()
+
+ handle_open_inventory()
+
+
+func _physics_process(delta): # Most things happen here.
+ # Gravity
+ if dynamic_gravity:
+ gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
+ if not is_on_floor() and gravity and gravity_enabled:
+ velocity.y -= gravity * delta
+ handle_jumping()
+
+ var input_dir = Vector2.ZERO
+
+ if not immobile && !Global.in_menu: # Immobility works by interrupting user input, so other forces can still be applied to the player
+ input_dir = Input.get_vector(controls.LEFT, controls.RIGHT, controls.FORWARD, controls.BACKWARD)
+
+ handle_movement(delta, input_dir)
+
+ handle_head_rotation()
+
+ # The player is not able to stand up if the ceiling is too low
+ low_ceiling = $CrouchCeilingDetection.is_colliding()
+
+ handle_state(input_dir)
+ if dynamic_fov: # This may be changed to an AnimationPlayer
+ update_camera_fov()
+
+ if view_bobbing:
+ play_headbob_animation(input_dir)
+
+ if jump_animation:
+ play_jump_animation()
+ handle_interact()
+ update_debug_menu_per_tick()
+
+ was_on_floor = is_on_floor() # This must always be at the end of physics_process
+
+#endregion
+
+#region Server Functions
+
+@rpc("any_peer", "call_local")
+func teleport_player(location: Vector3, new_rotation: Vector3) -> void:
+ if not is_multiplayer_authority():
+ return
+ global_position = location
+ HEAD.rotation = new_rotation
+ velocity = Vector3.ZERO
+
+
+#endregion
+
+#region Input Handling
+
+func handle_jumping():
+ if jumping_enabled:
+ if continuous_jumping: # Hold down the jump button
+ if Input.is_action_pressed(controls.JUMP) and is_on_floor() and !low_ceiling and !Global.in_menu:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity # Adding instead of setting so jumping on slopes works properly
+ else:
+ if Input.is_action_just_pressed(controls.JUMP) and is_on_floor() and !low_ceiling and !Global.in_menu:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity
+
+
+func handle_movement(delta, input_dir):
+ var direction = input_dir.rotated(-HEAD.rotation.y)
+ direction = Vector3(direction.x, 0, direction.y)
+ move_and_slide()
+
+ if in_air_momentum:
+ if is_on_floor():
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+ else:
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+
+
+func handle_head_rotation():
+ if invert_camera_x_axis:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity
+
+ if invert_camera_y_axis:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity
+
+ if controller_support:
+ var controller_view_rotation = Input.get_vector(controller_controls.LOOK_DOWN, controller_controls.LOOK_UP, controller_controls.LOOK_RIGHT, controller_controls.LOOK_LEFT) * look_sensitivity # These are inverted because of the nature of 3D rotation.
+ if invert_camera_x_axis:
+ HEAD.rotation.x += controller_view_rotation.x * -1
+ else:
+ HEAD.rotation.x += controller_view_rotation.x
+
+ if invert_camera_y_axis:
+ HEAD.rotation.y += controller_view_rotation.y * -1
+ else:
+ HEAD.rotation.y += controller_view_rotation.y
+
+ mouseInput = Vector2(0,0)
+ HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
+
+func handle_interact():
+ if CAMERA_RAYCAST.is_colliding():
+ var interactable = CAMERA_RAYCAST.get_collider()
+ if "interactable" in interactable:
+ HIGHLIGHTED_ITEM = interactable
+ interactable.interactable.outline.show()
+ if "item" in interactable:
+ if Input.is_action_just_pressed(controls.INTERACT):
+ print(CAMERA_RAYCAST.get_collider())
+ var item = interactable as RigidBody3D
+ item.freeze = true
+ item.collision_layer = 0
+ item.hide()
+ INVENTORY.add_item(item)
+ print("This is a item")
+ elif HIGHLIGHTED_ITEM != null:
+ HIGHLIGHTED_ITEM.get_node("Interactable").outline.hide()
+ HIGHLIGHTED_ITEM = null
+
+func check_controls(): # If you add a control, you might want to add a check for it here.
+ # The actions are being disabled so the engine doesn't halt the entire project in debug mode
+ if !InputMap.has_action(controls.JUMP):
+ push_error("No control mapped for jumping. Please add an input map control. Disabling jump.")
+ jumping_enabled = false
+ if !InputMap.has_action(controls.LEFT):
+ push_error("No control mapped for move left. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.RIGHT):
+ push_error("No control mapped for move right. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.FORWARD):
+ push_error("No control mapped for move forward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.BACKWARD):
+ push_error("No control mapped for move backward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.PAUSE):
+ push_error("No control mapped for pause. Please add an input map control. Disabling pausing.")
+ pausing_enabled = false
+ if !InputMap.has_action(controls.CROUCH):
+ push_error("No control mapped for crouch. Please add an input map control. Disabling crouching.")
+ crouch_enabled = false
+ if !InputMap.has_action(controls.SPRINT):
+ push_error("No control mapped for sprint. Please add an input map control. Disabling sprinting.")
+ sprint_enabled = false
+
+func handle_open_inventory(force_toggle : bool = false):
+ if Input.is_action_just_pressed(controls.INVENTORY) || force_toggle:
+ Global.inventory_showing = inventory_menu.toggle_inventory()
+ match Input.mouse_mode:
+ Input.MOUSE_MODE_CAPTURED:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ #get_tree().paused = false
+ Input.MOUSE_MODE_VISIBLE:
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+ #get_tree().paused = false
+#endregion
+
+#region State Handling
+
+func handle_state(moving):
+ if sprint_enabled:
+ if sprint_mode == 0:
+ if Input.is_action_pressed(controls.SPRINT) and state != "crouching":
+ if moving:
+ if state != "sprinting":
+ enter_sprint_state()
+ else:
+ if state == "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+ elif sprint_mode == 1:
+ if moving:
+ # If the player is holding sprint before moving, handle that scenario
+ if Input.is_action_pressed(controls.SPRINT) and state == "normal":
+ enter_sprint_state()
+ if Input.is_action_just_pressed(controls.SPRINT):
+ match state:
+ "normal":
+ enter_sprint_state()
+ "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+
+ if crouch_enabled:
+ if crouch_mode == 0:
+ if Input.is_action_pressed(controls.CROUCH) and state != "sprinting":
+ if state != "crouching":
+ enter_crouch_state()
+ elif state == "crouching" and !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+ elif crouch_mode == 1:
+ if Input.is_action_just_pressed(controls.CROUCH):
+ match state:
+ "normal":
+ enter_crouch_state()
+ "crouching":
+ if !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+
+
+# Any enter state function should only be called once when you want to enter that state, not every frame.
+func enter_normal_state():
+ #print("entering normal state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "normal"
+ speed = base_speed
+
+func enter_crouch_state():
+ #print("entering crouch state")
+ state = "crouching"
+ speed = crouch_speed
+ CROUCH_ANIMATION.play("crouch")
+
+func enter_sprint_state():
+ #print("entering sprint state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "sprinting"
+ speed = sprint_speed
+
+#endregion
+
+#region Animation Handling
+
+func initialize_animations():
+ # Reset the camera position
+ # If you want to change the default head height, change these animations.
+ HEADBOB_ANIMATION.play("RESET")
+ JUMP_ANIMATION.play("RESET")
+ CROUCH_ANIMATION.play("RESET")
+
+func play_headbob_animation(moving):
+ if moving and is_on_floor():
+ var use_headbob_animation : String
+ match state:
+ "normal","crouching":
+ use_headbob_animation = "walk"
+ "sprinting":
+ use_headbob_animation = "sprint"
+
+ var was_playing : bool = false
+ if HEADBOB_ANIMATION.current_animation == use_headbob_animation:
+ was_playing = true
+
+ HEADBOB_ANIMATION.play(use_headbob_animation, 0.25)
+ HEADBOB_ANIMATION.speed_scale = (current_speed / base_speed) * 1.75
+ if !was_playing:
+ HEADBOB_ANIMATION.seek(float(randi() % 2)) # Randomize the initial headbob direction
+ # Let me explain that piece of code because it looks like it does the opposite of what it actually does.
+ # The headbob animation has two starting positions. One is at 0 and the other is at 1.
+ # randi() % 2 returns either 0 or 1, and so the animation randomly starts at one of the starting positions.
+ # This code is extremely performant but it makes no sense.
+
+ else:
+ if HEADBOB_ANIMATION.current_animation == "sprint" or HEADBOB_ANIMATION.current_animation == "walk":
+ HEADBOB_ANIMATION.speed_scale = 1
+ HEADBOB_ANIMATION.play("RESET", 1)
+
+func play_jump_animation():
+ if !was_on_floor and is_on_floor(): # The player just landed
+ var facing_direction : Vector3 = CAMERA.get_global_transform().basis.x
+ var facing_direction_2D : Vector2 = Vector2(facing_direction.x, facing_direction.z).normalized()
+ var velocity_2D : Vector2 = Vector2(velocity.x, velocity.z).normalized()
+
+ # Compares velocity direction against the camera direction (via dot product) to determine which landing animation to play.
+ var side_landed : int = round(velocity_2D.dot(facing_direction_2D))
+
+ if side_landed > 0:
+ JUMP_ANIMATION.play("land_right", 0.25)
+ elif side_landed < 0:
+ JUMP_ANIMATION.play("land_left", 0.25)
+ else:
+ JUMP_ANIMATION.play("land_center", 0.25)
+
+#endregion
+
+#region Debug Menu
+
+func update_debug_menu_per_frame():
+ $UserInterface/DebugPanel.add_property("FPS", Performance.get_monitor(Performance.TIME_FPS), 0)
+ var status : String = state
+ if !is_on_floor():
+ status += " in the air"
+ $UserInterface/DebugPanel.add_property("State", status, 4)
+
+
+func update_debug_menu_per_tick():
+ # Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
+ current_speed = Vector3.ZERO.distance_to(get_real_velocity())
+ $UserInterface/DebugPanel.add_property("Speed", snappedf(current_speed, 0.001), 1)
+ $UserInterface/DebugPanel.add_property("Target speed", speed, 2)
+ var cv : Vector3 = get_real_velocity()
+ var vd : Array[float] = [
+ snappedf(cv.x, 0.001),
+ snappedf(cv.y, 0.001),
+ snappedf(cv.z, 0.001)
+ ]
+ var readable_velocity : String = "X: " + str(vd[0]) + " Y: " + str(vd[1]) + " Z: " + str(vd[2])
+ $UserInterface/DebugPanel.add_property("Velocity", readable_velocity, 3)
+
+
+func _unhandled_input(event : InputEvent):
+ if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
+ mouseInput.x += event.relative.x
+ mouseInput.y += event.relative.y
+ # Toggle debug menu
+ elif event is InputEventKey:
+ if event.is_released():
+ # Where we're going, we don't need InputMap
+ if event.keycode == 4194338: # F7
+ $UserInterface/DebugPanel.visible = !$UserInterface/DebugPanel.visible
+
+#endregion
+
+#region Misc Functions
+
+func change_reticle(reticle): # Yup, this function is kinda strange
+ if not is_multiplayer_authority():
+ return
+ if RETICLE:
+ RETICLE.queue_free()
+
+ RETICLE = load(reticle).instantiate()
+ RETICLE.character = self
+ $UserInterface.add_child(RETICLE)
+
+
+func update_camera_fov():
+ if state == "sprinting":
+ CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3)
+ else:
+ CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3)
+
+func handle_pausing():
+ if Global.inventory_showing:
+ handle_open_inventory()
+ if Input.is_action_just_pressed(controls.PAUSE):
+ # You may want another node to handle pausing, because this player may get paused too.
+ match Input.mouse_mode:
+ Input.MOUSE_MODE_CAPTURED:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ #get_tree().paused = false
+ Input.MOUSE_MODE_VISIBLE:
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+ #get_tree().paused = false
+
+#endregion
diff --git a/Scripts/fpc/character.gd.uid b/Scripts/fpc/character.gd.uid
new file mode 100644
index 0000000..26b6d96
--- /dev/null
+++ b/Scripts/fpc/character.gd.uid
@@ -0,0 +1 @@
+uid://c1um4tmthbbpk
diff --git a/Scripts/fpc/debug.gd b/Scripts/fpc/debug.gd
new file mode 100644
index 0000000..e274258
--- /dev/null
+++ b/Scripts/fpc/debug.gd
@@ -0,0 +1,18 @@
+extends PanelContainer
+
+
+func _process(_delta):
+ if visible:
+ pass
+
+func add_property(title : String, value, order : int): # This can either be called once for a static property or called every frame for a dynamic property
+ var target
+ target = $MarginContainer/VBoxContainer.find_child(title, true, false) # I have no idea what true and false does here, the function should be more specific
+ if !target:
+ target = Label.new() # Debug lines are of type Label
+ $MarginContainer/VBoxContainer.add_child(target)
+ target.name = title
+ target.text = title + ": " + str(value)
+ elif visible:
+ target.text = title + ": " + str(value)
+ $MarginContainer/VBoxContainer.move_child(target, order)
diff --git a/Scripts/fpc/debug.gd.uid b/Scripts/fpc/debug.gd.uid
new file mode 100644
index 0000000..c5b6f3e
--- /dev/null
+++ b/Scripts/fpc/debug.gd.uid
@@ -0,0 +1 @@
+uid://bhfftu01dsfk7
diff --git a/Scripts/global.gd b/Scripts/global.gd
new file mode 100644
index 0000000..d29f5e0
--- /dev/null
+++ b/Scripts/global.gd
@@ -0,0 +1,29 @@
+extends Node
+
+#region Constants
+
+const DEFAULT_ENET_ADDRESS = "127.0.0.1"
+
+#endregion
+
+
+#region Variables
+
+var is_admin : bool = true
+
+var is_multiplayer : bool = false
+
+var steam_connected : bool = false
+
+var in_game : bool = false
+var in_menu : bool = false
+var inventory_showing : bool = false
+
+var enet_address : String = DEFAULT_ENET_ADDRESS
+var steam_lobby_id : String = ""
+
+var controller_connected : bool = false
+
+var voip_on : bool = false
+
+#endregion
\ No newline at end of file
diff --git a/Scripts/global.gd.uid b/Scripts/global.gd.uid
new file mode 100644
index 0000000..524b7ce
--- /dev/null
+++ b/Scripts/global.gd.uid
@@ -0,0 +1 @@
+uid://bma0n1wnb0yxd
diff --git a/Scripts/world.gd b/Scripts/world.gd
new file mode 100644
index 0000000..1249e2d
--- /dev/null
+++ b/Scripts/world.gd
@@ -0,0 +1,86 @@
+extends Node
+
+@export var lobby_menu : LobbyMenu
+var menu_showing : bool = false;
+
+@export var server : Server
+
+@export var level: Level
+
+@onready var chat : ChatMenu = $ChatMenu
+
+func _ready() -> void:
+ lobby_menu.enet_host_toggled_on.connect(_enet_host_on)
+ lobby_menu.enet_join_pressed.connect(_enet_join_pressed)
+ lobby_menu.disconnect_button_pressed.connect(_disconnect_button_pressed)
+ lobby_menu.enet_host_toggled_off.connect(_menu_toggle_host_off)
+ server.client_connected_to_server.connect(_client_joined_server)
+ server.remove_client_from_server.connect(_remove_client_from_server)
+ server.client_disconnected_from_server.connect(_disconnect_from_server)
+ server.server_close.connect(_server_close)
+ server.game_log.connect(_server_send_chat_message)
+
+func _physics_process(_delta: float) -> void:
+ pause_toggle()
+
+
+func pause_toggle():
+ if Input.is_action_just_pressed("pause"):
+ if Global.inventory_showing:
+ var player_id : int = multiplayer.get_unique_id()
+ var player : Player = level.get_node("Player"+str(player_id))
+ player.handle_open_inventory(true)
+ return
+ if !menu_showing:
+ lobby_menu.show()
+ menu_showing = true
+ Global.in_menu = true
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+
+ elif menu_showing:
+ lobby_menu.hide()
+ menu_showing = false
+ Global.in_menu = false
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+
+func _enet_host_on(username: String) -> void:
+ print("Server started, Host: " + username);
+ server.enet_create_host()
+
+
+func _enet_join_pressed(_username: String) -> void:
+ print("... Joining " + Global.enet_address)
+ var playername : Player = level.get_node("Player1")
+ level.remove_child(playername)
+ server.enet_create_client(Global.enet_address)
+
+
+func _client_joined_server() -> void:
+
+ var player_id : int = multiplayer.get_unique_id()
+ level.request_spawn_player.rpc(player_id)
+ lobby_menu.hide()
+ menu_showing = false
+ Global.in_menu = false
+
+func _menu_toggle_host_off() -> void:
+ server.destroy_lobby()
+ lobby_menu.reset_visible()
+
+func _server_close() -> void:
+ level.remove_players(false)
+ lobby_menu.reset_visible()
+
+func _disconnect_button_pressed():
+ server.disconnect_client()
+ lobby_menu.reset_visible()
+
+func _remove_client_from_server(id : int) -> void:
+ level.remove_client_from_server.rpc_id(1, id)
+
+func _disconnect_from_server():
+ level.disconnect_from_server()
+
+
+func _server_send_chat_message(message : String, color : Color) -> void:
+ chat.send_message(message, false, color)
diff --git a/Scripts/world.gd.uid b/Scripts/world.gd.uid
new file mode 100644
index 0000000..eb541fb
--- /dev/null
+++ b/Scripts/world.gd.uid
@@ -0,0 +1 @@
+uid://klp20wju1i26
diff --git a/addons/godotsteam/godotsteam.gdextension b/addons/godotsteam/godotsteam.gdextension
new file mode 100644
index 0000000..5917bed
--- /dev/null
+++ b/addons/godotsteam/godotsteam.gdextension
@@ -0,0 +1,22 @@
+[configuration]
+entry_symbol = "godotsteam_init"
+compatibility_minimum = "4.4"
+
+[libraries]
+macos.debug = "res://addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework"
+macos.release = "res://addons/godotsteam/osx/libgodotsteam.macos.template_release.framework"
+windows.debug.x86_64 = "res://addons/godotsteam/win64/libgodotsteam.windows.template_debug.x86_64.dll"
+windows.debug.x86_32 = "res://addons/godotsteam/win32/libgodotsteam.windows.template_debug.x86_32.dll"
+windows.release.x86_64 = "res://addons/godotsteam/win64/libgodotsteam.windows.template_release.x86_64.dll"
+windows.release.x86_32 = "res://addons/godotsteam/win32/libgodotsteam.windows.template_release.x86_32.dll"
+linux.debug.x86_64 = "res://addons/godotsteam/linux64/libgodotsteam.linux.template_debug.x86_64.so"
+linux.debug.x86_32 = "res://addons/godotsteam/linux32/libgodotsteam.linux.template_debug.x86_32.so"
+linux.release.x86_64 = "res://addons/godotsteam/linux64/libgodotsteam.linux.template_release.x86_64.so"
+linux.release.x86_32 = "res://addons/godotsteam/linux32/libgodotsteam.linux.template_release.x86_32.so"
+
+[dependencies]
+macos.universal = { "res://addons/godotsteam/osx/libsteam_api.dylib": "" }
+windows.x86_64 = { "res://addons/godotsteam/win64/steam_api64.dll": "" }
+windows.x86_32 = { "res://addons/godotsteam/win32/steam_api.dll": "" }
+linux.x86_64 = { "res://addons/godotsteam/linux64/libsteam_api.so": "" }
+linux.x86_32 = { "res://addons/godotsteam/linux32/libsteam_api.so": "" }
diff --git a/addons/godotsteam/godotsteam.gdextension.uid b/addons/godotsteam/godotsteam.gdextension.uid
new file mode 100644
index 0000000..8b94933
--- /dev/null
+++ b/addons/godotsteam/godotsteam.gdextension.uid
@@ -0,0 +1 @@
+uid://d02t56dfoplvx
diff --git a/addons/godotsteam/linux32/libgodotsteam.linux.template_debug.x86_32.so b/addons/godotsteam/linux32/libgodotsteam.linux.template_debug.x86_32.so
new file mode 100644
index 0000000..00be7c5
Binary files /dev/null and b/addons/godotsteam/linux32/libgodotsteam.linux.template_debug.x86_32.so differ
diff --git a/addons/godotsteam/linux32/libgodotsteam.linux.template_release.x86_32.so b/addons/godotsteam/linux32/libgodotsteam.linux.template_release.x86_32.so
new file mode 100644
index 0000000..658694b
Binary files /dev/null and b/addons/godotsteam/linux32/libgodotsteam.linux.template_release.x86_32.so differ
diff --git a/addons/godotsteam/linux32/libsteam_api.so b/addons/godotsteam/linux32/libsteam_api.so
new file mode 100644
index 0000000..2d9e8a7
Binary files /dev/null and b/addons/godotsteam/linux32/libsteam_api.so differ
diff --git a/addons/godotsteam/linux64/libgodotsteam.linux.template_debug.x86_64.so b/addons/godotsteam/linux64/libgodotsteam.linux.template_debug.x86_64.so
new file mode 100644
index 0000000..045b00d
Binary files /dev/null and b/addons/godotsteam/linux64/libgodotsteam.linux.template_debug.x86_64.so differ
diff --git a/addons/godotsteam/linux64/libgodotsteam.linux.template_release.x86_64.so b/addons/godotsteam/linux64/libgodotsteam.linux.template_release.x86_64.so
new file mode 100644
index 0000000..b273ec7
Binary files /dev/null and b/addons/godotsteam/linux64/libgodotsteam.linux.template_release.x86_64.so differ
diff --git a/addons/godotsteam/linux64/libsteam_api.so b/addons/godotsteam/linux64/libsteam_api.so
new file mode 100644
index 0000000..8783570
Binary files /dev/null and b/addons/godotsteam/linux64/libsteam_api.so differ
diff --git a/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/Resources/Info.plist b/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/Resources/Info.plist
new file mode 100644
index 0000000..68caf2b
--- /dev/null
+++ b/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/Resources/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleExecutable
+ libgodotsteam.debug
+ CFBundleIdentifier
+ org.godotsteam.godotsteam
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ libgodotsteam.debug
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 4.15
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ CFBundleVersion
+ 4.15
+ LSMinimumSystemVersion
+ 10.12
+
+
\ No newline at end of file
diff --git a/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/libgodotsteam.macos.template_debug b/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/libgodotsteam.macos.template_debug
new file mode 100644
index 0000000..8a8b2b7
Binary files /dev/null and b/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/libgodotsteam.macos.template_debug differ
diff --git a/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/libsteam_api.dylib b/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/libsteam_api.dylib
new file mode 100644
index 0000000..c493b2b
Binary files /dev/null and b/addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework/libsteam_api.dylib differ
diff --git a/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/Resources/Info.plist b/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/Resources/Info.plist
new file mode 100644
index 0000000..e3e8dac
--- /dev/null
+++ b/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/Resources/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleExecutable
+ libgodotsteam
+ CFBundleIdentifier
+ org.godotsteam.godotsteam
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ libgodotsteam
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 4.15
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ CFBundleVersion
+ 4.15
+ LSMinimumSystemVersion
+ 10.12
+
+
\ No newline at end of file
diff --git a/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/libgodotsteam.macos.template_release b/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/libgodotsteam.macos.template_release
new file mode 100644
index 0000000..ce91b3e
Binary files /dev/null and b/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/libgodotsteam.macos.template_release differ
diff --git a/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/libsteam_api.dylib b/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/libsteam_api.dylib
new file mode 100644
index 0000000..c493b2b
Binary files /dev/null and b/addons/godotsteam/osx/libgodotsteam.macos.template_release.framework/libsteam_api.dylib differ
diff --git a/addons/godotsteam/osx/libsteam_api.dylib b/addons/godotsteam/osx/libsteam_api.dylib
new file mode 100644
index 0000000..c493b2b
Binary files /dev/null and b/addons/godotsteam/osx/libsteam_api.dylib differ
diff --git a/addons/godotsteam/win32/libgodotsteam.windows.template_debug.x86_32.dll b/addons/godotsteam/win32/libgodotsteam.windows.template_debug.x86_32.dll
new file mode 100644
index 0000000..cebcc9b
Binary files /dev/null and b/addons/godotsteam/win32/libgodotsteam.windows.template_debug.x86_32.dll differ
diff --git a/addons/godotsteam/win32/libgodotsteam.windows.template_release.x86_32.dll b/addons/godotsteam/win32/libgodotsteam.windows.template_release.x86_32.dll
new file mode 100644
index 0000000..21c3a1e
Binary files /dev/null and b/addons/godotsteam/win32/libgodotsteam.windows.template_release.x86_32.dll differ
diff --git a/addons/godotsteam/win32/steam_api.dll b/addons/godotsteam/win32/steam_api.dll
new file mode 100644
index 0000000..2372ab1
Binary files /dev/null and b/addons/godotsteam/win32/steam_api.dll differ
diff --git a/addons/godotsteam/win64/libgodotsteam.windows.template_debug.x86_64.dll b/addons/godotsteam/win64/libgodotsteam.windows.template_debug.x86_64.dll
new file mode 100644
index 0000000..3d02848
Binary files /dev/null and b/addons/godotsteam/win64/libgodotsteam.windows.template_debug.x86_64.dll differ
diff --git a/addons/godotsteam/win64/libgodotsteam.windows.template_release.x86_64.dll b/addons/godotsteam/win64/libgodotsteam.windows.template_release.x86_64.dll
new file mode 100644
index 0000000..6bb49b3
Binary files /dev/null and b/addons/godotsteam/win64/libgodotsteam.windows.template_release.x86_64.dll differ
diff --git a/addons/godotsteam/win64/steam_api64.dll b/addons/godotsteam/win64/steam_api64.dll
new file mode 100644
index 0000000..6d11825
Binary files /dev/null and b/addons/godotsteam/win64/steam_api64.dll differ
diff --git a/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll b/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll
new file mode 100644
index 0000000..3d02848
Binary files /dev/null and b/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll differ
diff --git a/addons/steam-multiplayer-peer/.gitignore b/addons/steam-multiplayer-peer/.gitignore
new file mode 100644
index 0000000..9581441
--- /dev/null
+++ b/addons/steam-multiplayer-peer/.gitignore
@@ -0,0 +1 @@
+~*.dll
\ No newline at end of file
diff --git a/addons/steam-multiplayer-peer/LICENSE b/addons/steam-multiplayer-peer/LICENSE
new file mode 100644
index 0000000..0cefe81
--- /dev/null
+++ b/addons/steam-multiplayer-peer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Expresso Bits
+
+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/addons/steam-multiplayer-peer/README.md b/addons/steam-multiplayer-peer/README.md
new file mode 100644
index 0000000..cbaf359
--- /dev/null
+++ b/addons/steam-multiplayer-peer/README.md
@@ -0,0 +1,87 @@
+#
Welcome to Expresso Steam Multiplayer Peer 👋
+
+[](todo-doc)
+[](MIT)
+
+See demos in:
+Github: [Branch demos](https://github.com/expressobits/steam-multiplayer-peer/tree/demos)
+Godot Asset Lib: https://godotengine.org/asset-library/asset/2258
+
+## Tutorial and Learnings (How to use)
+
+See post of Michael Macha
+https://michaelmacha.wordpress.com/2024/04/08/godotsteam-and-steammultiplayerpeer/
+
+See too on youtube videos
+https://www.youtube.com/playlist?list=PLg_8mgEWE2p8ZA-AqUUJ3CYEtrRVFhl_v
+
+Thank you Michael!
+
+## Features
+
+✔️ Change easy Enet peer to Steam Peer
+
+✔️ Use Steam Sockets (Low level like enet).
+
+✔️ GDExtension (Easy to add your project)
+
+✔️ No dependency with GodotSteam, but demo use GodotSteam to handle connections with lobbies (See lobbies tutorial in Godot Steam [here](https://godotsteam.com/tutorials/lobbies/)).
+
+
+## GodotSteam SteamMultiplayerPeer Differences
+
+| Differences | This SteamMultiplayerPeer | GodotSteam SteamMultiplayerPeer |
+|---|---|---|
+| Lib Type | GDExtension, add on your project libs to use easy. | C++ module, you need to use the precompiled
ones from godotsteam or compile it yourself |
+| Steam Connection | Steam Sockets [Steam Docs](https://partner.steamgames.com/doc/api/ISteamNetworkingSockets)
Steam's lowest connection level,
manages a connection
(It's very close to Enet,
that's why I chose this approach for the plugin) | Steam Messages [Steam Docs](https://partner.steamgames.com/doc/api/ISteamNetworkingMessages)
Without a connection idea,
the connection is managed by the lobby,
Need Steam lobbies. |
+| TODO | | |
+
+## Known issues
+
+⚠️ Features No channel support currently
+At some point I intend to integrate channels to be used in rpcs commands, but currently it is only necessary to use channel 0 or the default rpcs.
+
+## In Progress
+
+🔨 Bugs fixes
+
+## Planneds
+
+📅 No planned features.
+
+
+
+## Authors
+
+👤 **Rafael Correa**
+* Twitter: [@ScriptsEngineer](https://twitter.com/ScriptsEngineer)
+* Github: [@scriptsengineer](https://github.com/scriptsengineer)
+
+👤 **Zennyth**
+* Github: [@Zennyth](https://github.com/Zennyth)
+
+👤 **greenfox1505**
+* Github: [@greenfox1505](https://github.com/greenfox1505)
+
+👤 **MichaelMacha**
+* Github: [@MichaelMacha](https://github.com/MichaelMacha)
+
+
+## 🤝 Contributing
+
+Contributions, issues and feature requests are welcome!
+
+Feel free to check [issues page](https://github.com/ExpressoBits/steam-multiplayer-peer/issues).
+
+To suggest or discuss some project structure, feel free here [discussions page](https://github.com/expressobits/steam-multiplayer-peer/discussions)
+
+
+## Show your support
+
+Give a ⭐️ if this project helped you!
+
+
+## 📝 License
+
+This project is [MIT](MIT) licensed.
\ No newline at end of file
diff --git a/addons/steam-multiplayer-peer/linux/.gitkeep b/addons/steam-multiplayer-peer/linux/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_debug.double.x86_64.so b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_debug.double.x86_64.so
new file mode 100644
index 0000000..a8a45e9
Binary files /dev/null and b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_debug.double.x86_64.so differ
diff --git a/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_debug.x86_64.so b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_debug.x86_64.so
new file mode 100644
index 0000000..3d1ee20
Binary files /dev/null and b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_debug.x86_64.so differ
diff --git a/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_release.double.x86_64.so b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_release.double.x86_64.so
new file mode 100644
index 0000000..30d1bdf
Binary files /dev/null and b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_release.double.x86_64.so differ
diff --git a/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_release.x86_64.so b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_release.x86_64.so
new file mode 100644
index 0000000..4b34abf
Binary files /dev/null and b/addons/steam-multiplayer-peer/linux/libsteam-multiplayer-peer.linux.template_release.x86_64.so differ
diff --git a/addons/steam-multiplayer-peer/linux/libsteam_api.so b/addons/steam-multiplayer-peer/linux/libsteam_api.so
new file mode 100644
index 0000000..4a1260c
Binary files /dev/null and b/addons/steam-multiplayer-peer/linux/libsteam_api.so differ
diff --git a/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_debug.double.universal.dylib b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_debug.double.universal.dylib
new file mode 100644
index 0000000..eda02f2
Binary files /dev/null and b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_debug.double.universal.dylib differ
diff --git a/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_debug.universal.dylib b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_debug.universal.dylib
new file mode 100644
index 0000000..820ea49
Binary files /dev/null and b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_debug.universal.dylib differ
diff --git a/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_release.double.universal.dylib b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_release.double.universal.dylib
new file mode 100644
index 0000000..1f74832
Binary files /dev/null and b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_release.double.universal.dylib differ
diff --git a/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_release.universal.dylib b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_release.universal.dylib
new file mode 100644
index 0000000..6bf8111
Binary files /dev/null and b/addons/steam-multiplayer-peer/macos/libsteam-multiplayer-peer.macos.template_release.universal.dylib differ
diff --git a/addons/steam-multiplayer-peer/macos/libsteam_api.dylib b/addons/steam-multiplayer-peer/macos/libsteam_api.dylib
new file mode 100644
index 0000000..1897bef
Binary files /dev/null and b/addons/steam-multiplayer-peer/macos/libsteam_api.dylib differ
diff --git a/addons/steam-multiplayer-peer/steam-multiplayer-peer.gdextension b/addons/steam-multiplayer-peer/steam-multiplayer-peer.gdextension
new file mode 100644
index 0000000..c877cf5
--- /dev/null
+++ b/addons/steam-multiplayer-peer/steam-multiplayer-peer.gdextension
@@ -0,0 +1,25 @@
+[configuration]
+entry_symbol = "steam_multiplayer_peer_init"
+compatibility_minimum = 4.2
+
+[libraries]
+linux.debug.x86_64 = "./linux/libsteam-multiplayer-peer.linux.template_debug.x86_64.so"
+linux.release.x86_64 = "./linux/libsteam-multiplayer-peer.linux.template_release.x86_64.so"
+linux.debug.arm64 = "./linux/libsteam-multiplayer-peer.linux.template_debug.arm64.so"
+linux.release.arm64 = "./linux/libsteam-multiplayer-peer.linux.template_release.arm64.so"
+linux.debug.rv64 = "./linux/libsteam-multiplayer-peer.linux.template_debug.rv64.so"
+linux.release.rv64 = "./linux/libsteam-multiplayer-peer.linux.template_release.rv64.so"
+macos.debug = "./macos/libsteam-multiplayer-peer.macos.template_debug.universal.dylib"
+macos.release = "./macos/libsteam-multiplayer-peer.macos.template_release.universal.dylib"
+windows.debug.x86_32 = "./windows/steam-multiplayer-peer.windows.template_debug.x86_32.dll"
+windows.release.x86_32 = "./windows/steam-multiplayer-peer.windows.template_release.x86_32.dll"
+windows.debug.x86_64 = "./windows/steam-multiplayer-peer.windows.template_debug.x86_64.dll"
+windows.release.x86_64 = "./windows/steam-multiplayer-peer.windows.template_release.x86_64.dll"
+
+[dependencies]
+linux.x86_64 = { "linux/libsteam_api.so": "" }
+linux.arm64 = { "linux/libsteam_api.so": "" }
+linux.rv64 = { "linux/libsteam_api.so": "" }
+macos.universal = { "macos/libsteam_api.dylib": "" }
+windows.x86_64 = { "windows/steam_api64.dll": "" }
+windows.x86_32 = { "windows/steam_api.dll": "" }
\ No newline at end of file
diff --git a/addons/steam-multiplayer-peer/steam-multiplayer-peer.gdextension.uid b/addons/steam-multiplayer-peer/steam-multiplayer-peer.gdextension.uid
new file mode 100644
index 0000000..0bf22ce
--- /dev/null
+++ b/addons/steam-multiplayer-peer/steam-multiplayer-peer.gdextension.uid
@@ -0,0 +1 @@
+uid://2kvjphhw06ra
diff --git a/addons/steam-multiplayer-peer/windows/.gitkeep b/addons/steam-multiplayer-peer/windows/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_debug.double.x86_64.dll b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_debug.double.x86_64.dll
new file mode 100644
index 0000000..5069c2c
Binary files /dev/null and b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_debug.double.x86_64.dll differ
diff --git a/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_debug.x86_64.dll b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_debug.x86_64.dll
new file mode 100644
index 0000000..11292cf
Binary files /dev/null and b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_debug.x86_64.dll differ
diff --git a/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_release.double.x86_64.dll b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_release.double.x86_64.dll
new file mode 100644
index 0000000..f1a81c2
Binary files /dev/null and b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_release.double.x86_64.dll differ
diff --git a/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_release.x86_64.dll b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_release.x86_64.dll
new file mode 100644
index 0000000..f6f4e55
Binary files /dev/null and b/addons/steam-multiplayer-peer/windows/steam-multiplayer-peer.windows.template_release.x86_64.dll differ
diff --git a/addons/steam-multiplayer-peer/windows/steam_api.dll b/addons/steam-multiplayer-peer/windows/steam_api.dll
new file mode 100644
index 0000000..0bcfde3
Binary files /dev/null and b/addons/steam-multiplayer-peer/windows/steam_api.dll differ
diff --git a/addons/steam-multiplayer-peer/windows/steam_api64.dll b/addons/steam-multiplayer-peer/windows/steam_api64.dll
new file mode 100644
index 0000000..6594c50
Binary files /dev/null and b/addons/steam-multiplayer-peer/windows/steam_api64.dll differ
diff --git a/project.godot b/project.godot
new file mode 100644
index 0000000..09ff5a7
--- /dev/null
+++ b/project.godot
@@ -0,0 +1,105 @@
+; 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
+
+[application]
+
+config/name="Goblin Dishonored"
+run/main_scene="uid://cjimt73bcja16"
+config/features=PackedStringArray("4.4", "Forward Plus")
+
+[autoload]
+
+Global="*res://Scripts/global.gd"
+
+[file_customization]
+
+folder_colors={
+"res://Assets/": "red",
+"res://Scenes/": "purple",
+"res://Scripts/": "green"
+}
+
+[gui]
+
+theme/custom="uid://bvso6uowlb8dh"
+
+[input]
+
+move_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)
+]
+}
+move_back={
+"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)
+]
+}
+move_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)
+]
+}
+move_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)
+]
+}
+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)
+]
+}
+crouch={
+"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":4194326,"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)
+]
+}
+pause={
+"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)
+]
+}
+open_chat={
+"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":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+open_chat_as_command={
+"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":47,"key_label":0,"unicode":47,"location":0,"echo":false,"script":null)
+]
+}
+chat_send={
+"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":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+interact={
+"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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
+]
+}
+inventory={
+"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":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+
+[layer_names]
+
+3d_physics/layer_1="Terrain and Players"
+3d_physics/layer_2="Interactables"
+3d_physics/layer_3="Enemies"
diff --git a/quality-godot-first-person-2-2.6.1/.gitattributes b/quality-godot-first-person-2-2.6.1/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/quality-godot-first-person-2-2.6.1/.gitignore b/quality-godot-first-person-2-2.6.1/.gitignore
new file mode 100644
index 0000000..4709183
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/.gitignore
@@ -0,0 +1,2 @@
+# Godot 4+ specific ignores
+.godot/
diff --git a/quality-godot-first-person-2-2.6.1/LICENSE b/quality-godot-first-person-2-2.6.1/LICENSE
new file mode 100644
index 0000000..f34733d
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Colormatic
+
+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/quality-godot-first-person-2-2.6.1/README.md b/quality-godot-first-person-2-2.6.1/README.md
new file mode 100644
index 0000000..6e64887
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/README.md
@@ -0,0 +1,67 @@
+# quality-godot-first-person-2
+Actually good first person controller for the Godot Engine.
+MIT License (credit Colormatic Studios)
+
+This first person controller was made because there aren't many first person controllers for Godot, and the ones that do exist are pretty bad.
+It is highly customizable and comes with many features, QOL, and clean code.
+
+Some parts came from StayAtHomeDev's FPS tutorial. You can find that [here](https://www.youtube.com/playlist?list=PLEHvj4yeNfeF6s-UVs5Zx5TfNYmeCiYwf).
+
+# Directions
+Move with WASD, space to jump, shift to sprint, C to crouch.
+
+**FEATURES:**
+- Extremely configurable
+- In-air momentum
+- Motion smoothing
+- FOV smoothing
+- Movement animations
+- Crouching
+- Sprinting
+- 2 crosshairs/reticles, one is animated (more to come?)
+- Controller/GamePad support (enabled through code, see wiki)
+- In-editor tools (enable editable children to use)
+
+If you make a cool game with this addon, I would love to hear about it!
+
+# Wiki
+**To start out**, you should probably remap all of the movement keys to your own control set.
+
+You can make this a super basic controller by just disabling everything.
+
+**How to add controller/GamePad support**
+- In the controls export group, there is a commented section at the end that says "Uncomment this if you want full controller support". Uncomment that block.
+- Make a key map for each direction (left, right, up, down) and map them to your joystick.
+- Write in these keymaps in the controls section of the player settings.
+- In the `handle_head_rotation` function, there is another block of commented code that says the same thing. Uncomment that too.
+- You should now be able to look around with the joystick. Make sure you add the other controls to the input map. (movement, jumping, crouching, sprinting, etc.)
+
+**Slope/staircase:**
+Credit to @roberto-urbani23
+In the character inspector, you can uncheck Stop on Slope and set the max angle to 89 (for some reason, 90 will make the player stuck). Also Snap Length to 1 otherwise your character will not remain attached to stairs if you sprint while going downstairs.
+
+**How to change settings:**
+Click on the character node and there should be settings in the "Feature Settings" group.
+
+**How to add animations for a mesh:**
+- Create a function for your animation and attach it to `_physics_process` to call it every frame.
+- Use `input_dir` as a boolean (it is actually a `Vector2`) to know if the player is walking.
+- Use the `state` member variable to tell if the player is sprinting or crouching.
+- Use the `is_on_floor` function to tell if the player is standing or falling.
+
+**How to change reticles (crosshairs):**
+Change the "Default Reticle" setting to your reticle file.
+During runtime:
+Use the `change_reticle` function on the character.
+
+**How to create a new reticle:**
+- Choose a reticle to base it off of.
+- Open that reticle and save it as a new reticle.
+- Remove the script from the reticle and create a new one. (for some reason you have to do this)
+- Edit the reticle to your needs.
+- Follow the "how to change reticles" directions to use it.
+
+**How to use the editor tools:**
+- Enable editable children on the `CharacterBody` node
+- Use the options in the Properties tab to change things
+- These changes apply in runtime as well
diff --git a/quality-godot-first-person-2-2.6.1/addons/fpc/EditorModule.gd b/quality-godot-first-person-2-2.6.1/addons/fpc/EditorModule.gd
new file mode 100644
index 0000000..0a3a74f
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/addons/fpc/EditorModule.gd
@@ -0,0 +1,49 @@
+@tool
+extends Node
+
+# This does not effect runtime yet but will in the future.
+
+@export_category("Controller Editor Module")
+@export_range(-360.0, 360.0, 0.01, "or_greater", "or_less") var head_y_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_y_rotation = new_rotation
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ update_configuration_warnings()
+@export_range(-90.0, 90.0, 0.01, "or_greater", "or_less") var head_x_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_x_rotation = new_rotation
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+ update_configuration_warnings()
+
+@export_group("Nodes")
+@export var CHARACTER : CharacterBody3D
+@export var head_path : String = "Head" # Relative to the parent node
+#@export var CAMERA : Camera3D
+#@export var HEADBOB_ANIMATION : AnimationPlayer
+#@export var JUMP_ANIMATION : AnimationPlayer
+#@export var CROUCH_ANIMATION : AnimationPlayer
+#@export var COLLISION_MESH : CollisionShape3D
+
+@onready var HEAD = get_node("../" + head_path)
+
+
+func _ready():
+ if !Engine.is_editor_hint():
+ #print("not editor")
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+
+
+func _get_configuration_warnings():
+ var warnings = []
+
+ if head_y_rotation > 360:
+ warnings.append("The head rotation is greater than 360")
+
+ if head_y_rotation < -360:
+ warnings.append("The head rotation is less than -360")
+
+ # Returning an empty array gives no warnings
+ return warnings
diff --git a/quality-godot-first-person-2-2.6.1/addons/fpc/character.gd b/quality-godot-first-person-2-2.6.1/addons/fpc/character.gd
new file mode 100644
index 0000000..5a5f7f7
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/addons/fpc/character.gd
@@ -0,0 +1,488 @@
+# COPYRIGHT Colormatic Studios
+# MIT license
+# Quality Godot First Person Controller v2
+
+
+extends CharacterBody3D
+
+
+#region Character Export Group
+
+## The settings for the character's movement and feel.
+@export_category("Character")
+## The speed that the character moves at without crouching or sprinting.
+@export var base_speed : float = 3.0
+## The speed that the character moves at when sprinting.
+@export var sprint_speed : float = 6.0
+## The speed that the character moves at when crouching.
+@export var crouch_speed : float = 1.0
+
+## How fast the character speeds up and slows down when Motion Smoothing is on.
+@export var acceleration : float = 10.0
+## How high the player jumps.
+@export var jump_velocity : float = 4.5
+## How far the player turns when the mouse is moved.
+@export var mouse_sensitivity : float = 0.1
+## Invert the X axis input for the camera.
+@export var invert_camera_x_axis : bool = false
+## Invert the Y axis input for the camera.
+@export var invert_camera_y_axis : bool = false
+## Whether the player can use movement inputs. Does not stop outside forces or jumping. See Jumping Enabled.
+@export var immobile : bool = false
+## The reticle file to import at runtime. By default are in res://addons/fpc/reticles/. Set to an empty string to remove.
+@export_file var default_reticle
+
+#endregion
+
+#region Nodes Export Group
+
+@export_group("Nodes")
+## A reference to the camera for use in the character script. This is the parent node to the camera and is rotated instead of the camera for mouse input.
+@export var HEAD : Node3D
+## A reference to the camera for use in the character script.
+@export var CAMERA : Camera3D
+## A reference to the headbob animation for use in the character script.
+@export var HEADBOB_ANIMATION : AnimationPlayer
+## A reference to the jump animation for use in the character script.
+@export var JUMP_ANIMATION : AnimationPlayer
+## A reference to the crouch animation for use in the character script.
+@export var CROUCH_ANIMATION : AnimationPlayer
+## A reference to the the player's collision shape for use in the character script.
+@export var COLLISION_MESH : CollisionShape3D
+
+#endregion
+
+#region Controls Export Group
+
+# We are using UI controls because they are built into Godot Engine so they can be used right away
+@export_group("Controls")
+## Use the Input Map to map a mouse/keyboard input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controls : Dictionary = {
+ LEFT = "ui_left",
+ RIGHT = "ui_right",
+ FORWARD = "ui_up",
+ BACKWARD = "ui_down",
+ JUMP = "ui_accept",
+ CROUCH = "crouch",
+ SPRINT = "sprint",
+ PAUSE = "ui_cancel"
+ }
+@export_subgroup("Controller Specific")
+## This only affects how the camera is handled, the rest should be covered by adding controller inputs to the existing actions in the Input Map.
+@export var controller_support : bool = false
+## Use the Input Map to map a controller input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controller_controls : Dictionary = {
+ LOOK_LEFT = "look_left",
+ LOOK_RIGHT = "look_right",
+ LOOK_UP = "look_up",
+ LOOK_DOWN = "look_down"
+ }
+## The sensitivity of the analog stick that controls camera rotation. Lower is less sensitive and higher is more sensitive.
+@export_range(0.001, 1, 0.001) var look_sensitivity : float = 0.035
+
+#endregion
+
+#region Feature Settings Export Group
+
+@export_group("Feature Settings")
+## Enable or disable jumping. Useful for restrictive storytelling environments.
+@export var jumping_enabled : bool = true
+## Whether the player can move in the air or not.
+@export var in_air_momentum : bool = true
+## Smooths the feel of walking.
+@export var motion_smoothing : bool = true
+## Enables or disables sprinting.
+@export var sprint_enabled : bool = true
+## Toggles the sprinting state when button is pressed or requires the player to hold the button down to remain sprinting.
+@export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0
+## Enables or disables crouching.
+@export var crouch_enabled : bool = true
+## Toggles the crouch state when button is pressed or requires the player to hold the button down to remain crouched.
+@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
+## Wether sprinting should effect FOV.
+@export var dynamic_fov : bool = true
+## If the player holds down the jump button, should the player keep hopping.
+@export var continuous_jumping : bool = true
+## Enables the view bobbing animation.
+@export var view_bobbing : bool = true
+## Enables an immersive animation when the player jumps and hits the ground.
+@export var jump_animation : bool = true
+## This determines wether the player can use the pause button, not wether the game will actually pause.
+@export var pausing_enabled : bool = true
+## Use with caution.
+@export var gravity_enabled : bool = true
+## If your game changes the gravity value during gameplay, check this property to allow the player to experience the change in gravity.
+@export var dynamic_gravity : bool = false
+
+#endregion
+
+#region Member Variable Initialization
+
+# These are variables used in this script that don't need to be exposed in the editor.
+var speed : float = base_speed
+var current_speed : float = 0.0
+# States: normal, crouching, sprinting
+var state : String = "normal"
+var low_ceiling : bool = false # This is for when the ceiling is too low and the player needs to crouch.
+var was_on_floor : bool = true # Was the player on the floor last frame (for landing animation)
+
+# The reticle should always have a Control node as the root
+var RETICLE : Control
+
+# Get the gravity from the project settings to be synced with RigidBody nodes
+var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process
+
+# Stores mouse input for rotating the camera in the physics process
+var mouseInput : Vector2 = Vector2(0,0)
+
+#endregion
+
+
+
+#region Main Control Flow
+
+func _ready():
+ #It is safe to comment this line if your game doesn't start with the mouse captured
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+
+ # If the controller is rotated in a certain direction for game design purposes, redirect this rotation into the head.
+ HEAD.rotation.y = rotation.y
+ rotation.y = 0
+
+ if default_reticle:
+ change_reticle(default_reticle)
+
+ initialize_animations()
+ check_controls()
+ enter_normal_state()
+
+
+func _process(_delta):
+ if pausing_enabled:
+ handle_pausing()
+
+ update_debug_menu_per_frame()
+
+
+func _physics_process(delta): # Most things happen here.
+ # Gravity
+ if dynamic_gravity:
+ gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
+ if not is_on_floor() and gravity and gravity_enabled:
+ velocity.y -= gravity * delta
+
+ handle_jumping()
+
+ var input_dir = Vector2.ZERO
+
+ if not immobile: # Immobility works by interrupting user input, so other forces can still be applied to the player
+ input_dir = Input.get_vector(controls.LEFT, controls.RIGHT, controls.FORWARD, controls.BACKWARD)
+
+ handle_movement(delta, input_dir)
+
+ handle_head_rotation()
+
+ # The player is not able to stand up if the ceiling is too low
+ low_ceiling = $CrouchCeilingDetection.is_colliding()
+
+ handle_state(input_dir)
+ if dynamic_fov: # This may be changed to an AnimationPlayer
+ update_camera_fov()
+
+ if view_bobbing:
+ play_headbob_animation(input_dir)
+
+ if jump_animation:
+ play_jump_animation()
+
+ update_debug_menu_per_tick()
+
+ was_on_floor = is_on_floor() # This must always be at the end of physics_process
+
+#endregion
+
+#region Input Handling
+
+func handle_jumping():
+ if jumping_enabled:
+ if continuous_jumping: # Hold down the jump button
+ if Input.is_action_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity # Adding instead of setting so jumping on slopes works properly
+ else:
+ if Input.is_action_just_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity
+
+
+func handle_movement(delta, input_dir):
+ var direction = input_dir.rotated(-HEAD.rotation.y)
+ direction = Vector3(direction.x, 0, direction.y)
+ move_and_slide()
+
+ if in_air_momentum:
+ if is_on_floor():
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+ else:
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+
+
+func handle_head_rotation():
+ if invert_camera_x_axis:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity
+
+ if invert_camera_y_axis:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity
+
+ if controller_support:
+ var controller_view_rotation = Input.get_vector(controller_controls.LOOK_DOWN, controller_controls.LOOK_UP, controller_controls.LOOK_RIGHT, controller_controls.LOOK_LEFT) * look_sensitivity # These are inverted because of the nature of 3D rotation.
+ if invert_camera_x_axis:
+ HEAD.rotation.x += controller_view_rotation.x * -1
+ else:
+ HEAD.rotation.x += controller_view_rotation.x
+
+ if invert_camera_y_axis:
+ HEAD.rotation.y += controller_view_rotation.y * -1
+ else:
+ HEAD.rotation.y += controller_view_rotation.y
+
+ mouseInput = Vector2(0,0)
+ HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
+
+
+func check_controls(): # If you add a control, you might want to add a check for it here.
+ # The actions are being disabled so the engine doesn't halt the entire project in debug mode
+ if !InputMap.has_action(controls.JUMP):
+ push_error("No control mapped for jumping. Please add an input map control. Disabling jump.")
+ jumping_enabled = false
+ if !InputMap.has_action(controls.LEFT):
+ push_error("No control mapped for move left. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.RIGHT):
+ push_error("No control mapped for move right. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.FORWARD):
+ push_error("No control mapped for move forward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.BACKWARD):
+ push_error("No control mapped for move backward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.PAUSE):
+ push_error("No control mapped for pause. Please add an input map control. Disabling pausing.")
+ pausing_enabled = false
+ if !InputMap.has_action(controls.CROUCH):
+ push_error("No control mapped for crouch. Please add an input map control. Disabling crouching.")
+ crouch_enabled = false
+ if !InputMap.has_action(controls.SPRINT):
+ push_error("No control mapped for sprint. Please add an input map control. Disabling sprinting.")
+ sprint_enabled = false
+
+#endregion
+
+#region State Handling
+
+func handle_state(moving):
+ if sprint_enabled:
+ if sprint_mode == 0:
+ if Input.is_action_pressed(controls.SPRINT) and state != "crouching":
+ if moving:
+ if state != "sprinting":
+ enter_sprint_state()
+ else:
+ if state == "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+ elif sprint_mode == 1:
+ if moving:
+ # If the player is holding sprint before moving, handle that scenario
+ if Input.is_action_pressed(controls.SPRINT) and state == "normal":
+ enter_sprint_state()
+ if Input.is_action_just_pressed(controls.SPRINT):
+ match state:
+ "normal":
+ enter_sprint_state()
+ "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+
+ if crouch_enabled:
+ if crouch_mode == 0:
+ if Input.is_action_pressed(controls.CROUCH) and state != "sprinting":
+ if state != "crouching":
+ enter_crouch_state()
+ elif state == "crouching" and !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+ elif crouch_mode == 1:
+ if Input.is_action_just_pressed(controls.CROUCH):
+ match state:
+ "normal":
+ enter_crouch_state()
+ "crouching":
+ if !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+
+
+# Any enter state function should only be called once when you want to enter that state, not every frame.
+func enter_normal_state():
+ #print("entering normal state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "normal"
+ speed = base_speed
+
+func enter_crouch_state():
+ #print("entering crouch state")
+ state = "crouching"
+ speed = crouch_speed
+ CROUCH_ANIMATION.play("crouch")
+
+func enter_sprint_state():
+ #print("entering sprint state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "sprinting"
+ speed = sprint_speed
+
+#endregion
+
+#region Animation Handling
+
+func initialize_animations():
+ # Reset the camera position
+ # If you want to change the default head height, change these animations.
+ HEADBOB_ANIMATION.play("RESET")
+ JUMP_ANIMATION.play("RESET")
+ CROUCH_ANIMATION.play("RESET")
+
+func play_headbob_animation(moving):
+ if moving and is_on_floor():
+ var use_headbob_animation : String
+ match state:
+ "normal","crouching":
+ use_headbob_animation = "walk"
+ "sprinting":
+ use_headbob_animation = "sprint"
+
+ var was_playing : bool = false
+ if HEADBOB_ANIMATION.current_animation == use_headbob_animation:
+ was_playing = true
+
+ HEADBOB_ANIMATION.play(use_headbob_animation, 0.25)
+ HEADBOB_ANIMATION.speed_scale = (current_speed / base_speed) * 1.75
+ if !was_playing:
+ HEADBOB_ANIMATION.seek(float(randi() % 2)) # Randomize the initial headbob direction
+ # Let me explain that piece of code because it looks like it does the opposite of what it actually does.
+ # The headbob animation has two starting positions. One is at 0 and the other is at 1.
+ # randi() % 2 returns either 0 or 1, and so the animation randomly starts at one of the starting positions.
+ # This code is extremely performant but it makes no sense.
+
+ else:
+ if HEADBOB_ANIMATION.current_animation == "sprint" or HEADBOB_ANIMATION.current_animation == "walk":
+ HEADBOB_ANIMATION.speed_scale = 1
+ HEADBOB_ANIMATION.play("RESET", 1)
+
+func play_jump_animation():
+ if !was_on_floor and is_on_floor(): # The player just landed
+ var facing_direction : Vector3 = CAMERA.get_global_transform().basis.x
+ var facing_direction_2D : Vector2 = Vector2(facing_direction.x, facing_direction.z).normalized()
+ var velocity_2D : Vector2 = Vector2(velocity.x, velocity.z).normalized()
+
+ # Compares velocity direction against the camera direction (via dot product) to determine which landing animation to play.
+ var side_landed : int = round(velocity_2D.dot(facing_direction_2D))
+
+ if side_landed > 0:
+ JUMP_ANIMATION.play("land_right", 0.25)
+ elif side_landed < 0:
+ JUMP_ANIMATION.play("land_left", 0.25)
+ else:
+ JUMP_ANIMATION.play("land_center", 0.25)
+
+#endregion
+
+#region Debug Menu
+
+func update_debug_menu_per_frame():
+ $UserInterface/DebugPanel.add_property("FPS", Performance.get_monitor(Performance.TIME_FPS), 0)
+ var status : String = state
+ if !is_on_floor():
+ status += " in the air"
+ $UserInterface/DebugPanel.add_property("State", status, 4)
+
+
+func update_debug_menu_per_tick():
+ # Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
+ current_speed = Vector3.ZERO.distance_to(get_real_velocity())
+ $UserInterface/DebugPanel.add_property("Speed", snappedf(current_speed, 0.001), 1)
+ $UserInterface/DebugPanel.add_property("Target speed", speed, 2)
+ var cv : Vector3 = get_real_velocity()
+ var vd : Array[float] = [
+ snappedf(cv.x, 0.001),
+ snappedf(cv.y, 0.001),
+ snappedf(cv.z, 0.001)
+ ]
+ var readable_velocity : String = "X: " + str(vd[0]) + " Y: " + str(vd[1]) + " Z: " + str(vd[2])
+ $UserInterface/DebugPanel.add_property("Velocity", readable_velocity, 3)
+
+
+func _unhandled_input(event : InputEvent):
+ if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
+ mouseInput.x += event.relative.x
+ mouseInput.y += event.relative.y
+ # Toggle debug menu
+ elif event is InputEventKey:
+ if event.is_released():
+ # Where we're going, we don't need InputMap
+ if event.keycode == 4194338: # F7
+ $UserInterface/DebugPanel.visible = !$UserInterface/DebugPanel.visible
+
+#endregion
+
+#region Misc Functions
+
+func change_reticle(reticle): # Yup, this function is kinda strange
+ if RETICLE:
+ RETICLE.queue_free()
+
+ RETICLE = load(reticle).instantiate()
+ RETICLE.character = self
+ $UserInterface.add_child(RETICLE)
+
+
+func update_camera_fov():
+ if state == "sprinting":
+ CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3)
+ else:
+ CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3)
+
+func handle_pausing():
+ if Input.is_action_just_pressed(controls.PAUSE):
+ # You may want another node to handle pausing, because this player may get paused too.
+ match Input.mouse_mode:
+ Input.MOUSE_MODE_CAPTURED:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ #get_tree().paused = false
+ Input.MOUSE_MODE_VISIBLE:
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+ #get_tree().paused = false
+
+#endregion
diff --git a/quality-godot-first-person-2-2.6.1/addons/fpc/character.tscn b/quality-godot-first-person-2-2.6.1/addons/fpc/character.tscn
new file mode 100644
index 0000000..3cfd7c8
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/addons/fpc/character.tscn
@@ -0,0 +1,455 @@
+[gd_scene load_steps=22 format=3 uid="uid://cc1m2a1obsyn4"]
+
+[ext_resource type="Script" path="res://addons/fpc/character.gd" id="1_0t4e8"]
+[ext_resource type="Script" path="res://addons/fpc/EditorModule.gd" id="3_v3ckk"]
+[ext_resource type="Script" path="res://addons/fpc/debug.gd" id="3_x1wcc"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kp17n"]
+albedo_color = Color(0.909804, 0.596078, 0, 1)
+clearcoat_enabled = true
+clearcoat_roughness = 0.2
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_jw1de"]
+material = SubResource("StandardMaterial3D_kp17n")
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uy03j"]
+
+[sub_resource type="Animation" id="Animation_j8cx7"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+
+[sub_resource type="Animation" id="Animation_5ec5e"]
+resource_name = "crouch"
+length = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0), Vector3(0, 1.12508, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 2
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 2
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_5e5t5"]
+_data = {
+"RESET": SubResource("Animation_j8cx7"),
+"crouch": SubResource("Animation_5ec5e")
+}
+
+[sub_resource type="Animation" id="Animation_gh776"]
+resource_name = "RESET"
+length = 0.001
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+
+[sub_resource type="Animation" id="Animation_8ku67"]
+resource_name = "sprint"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.06, -0.25, 0, 0.25, -0.01, 0, 0, 0, 0, 0, -0.06, -0.25, 0.01, 0.25, 0.01, 0, 0, 0, 0, 0, 0.06, -0.25, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(0.05, -0.25, 0, 0.2, -0.01, 0, -0.2, 0.000186046, 0.2, 0.000186046, 0.05, -0.2, -0.01, 0.2, -0.01, 0, -0.2, 0, 0.2, 0, 0.05, -0.2, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="Animation" id="Animation_lrqmv"]
+resource_name = "walk"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, -0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.04, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(-0.05, -0.25, 0, 0.2, 0.005, 0, -0.2, 0.000186046, 0.2, 0.000186046, -0.05, -0.2, 0.005, 0.2, 0.005, 0, -0.2, 0, 0.2, 0, -0.05, -0.2, 0.005, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_o0unb"]
+_data = {
+"RESET": SubResource("Animation_gh776"),
+"sprint": SubResource("Animation_8ku67"),
+"walk": SubResource("Animation_lrqmv")
+}
+
+[sub_resource type="Animation" id="Animation_fvvjq"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_s07ye"]
+resource_name = "jump"
+length = 3.0
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.6, 3),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_3eyjl"]
+resource_name = "land_center"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_l1rph"]
+resource_name = "land_left"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_vsknp"]
+resource_name = "land_right"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, -0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_qeg5r"]
+_data = {
+"RESET": SubResource("Animation_fvvjq"),
+"jump": SubResource("Animation_s07ye"),
+"land_center": SubResource("Animation_3eyjl"),
+"land_left": SubResource("Animation_l1rph"),
+"land_right": SubResource("Animation_vsknp")
+}
+
+[sub_resource type="Theme" id="Theme_wdf0f"]
+MarginContainer/constants/margin_bottom = 10
+MarginContainer/constants/margin_left = 10
+MarginContainer/constants/margin_right = 10
+MarginContainer/constants/margin_top = 10
+
+[sub_resource type="SphereShape3D" id="SphereShape3D_k4wwl"]
+
+[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "HEADBOB_ANIMATION", "JUMP_ANIMATION", "CROUCH_ANIMATION", "COLLISION_MESH")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0, 0)
+script = ExtResource("1_0t4e8")
+default_reticle = "res://addons/fpc/reticles/reticle_1.tscn"
+HEAD = NodePath("Head")
+CAMERA = NodePath("Head/Camera")
+HEADBOB_ANIMATION = NodePath("Head/HeadbobAnimation")
+JUMP_ANIMATION = NodePath("Head/JumpAnimation")
+CROUCH_ANIMATION = NodePath("CrouchAnimation")
+COLLISION_MESH = NodePath("Collision")
+
+[node name="Mesh" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("CapsuleMesh_jw1de")
+
+[node name="Collision" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("CapsuleShape3D_uy03j")
+
+[node name="CrouchAnimation" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_5e5t5")
+}
+
+[node name="Head" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
+
+[node name="Camera" type="Camera3D" parent="Head"]
+
+[node name="HeadbobAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+"": SubResource("AnimationLibrary_o0unb")
+}
+blend_times = [&"RESET", &"RESET", 0.5, &"RESET", &"walk", 0.5, &"walk", &"RESET", 0.5]
+
+[node name="JumpAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+"": SubResource("AnimationLibrary_qeg5r")
+}
+speed_scale = 4.0
+
+[node name="UserInterface" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 1
+
+[node name="DebugPanel" type="PanelContainer" parent="UserInterface"]
+visible = false
+layout_mode = 0
+offset_left = 10.0
+offset_top = 10.0
+offset_right = 453.0
+offset_bottom = 50.0
+theme = SubResource("Theme_wdf0f")
+script = ExtResource("3_x1wcc")
+
+[node name="MarginContainer" type="MarginContainer" parent="UserInterface/DebugPanel"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="UserInterface/DebugPanel/MarginContainer"]
+layout_mode = 2
+
+[node name="CrouchCeilingDetection" type="ShapeCast3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("SphereShape3D_k4wwl")
+target_position = Vector3(0, 0.5, 0)
+
+[node name="EditorModule" type="Node" parent="."]
+script = ExtResource("3_v3ckk")
diff --git a/quality-godot-first-person-2-2.6.1/addons/fpc/debug.gd b/quality-godot-first-person-2-2.6.1/addons/fpc/debug.gd
new file mode 100644
index 0000000..efdb7a4
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/addons/fpc/debug.gd
@@ -0,0 +1,18 @@
+extends PanelContainer
+
+
+func _process(delta):
+ if visible:
+ pass
+
+func add_property(title : String, value, order : int): # This can either be called once for a static property or called every frame for a dynamic property
+ var target
+ target = $MarginContainer/VBoxContainer.find_child(title, true, false) # I have no idea what true and false does here, the function should be more specific
+ if !target:
+ target = Label.new() # Debug lines are of type Label
+ $MarginContainer/VBoxContainer.add_child(target)
+ target.name = title
+ target.text = title + ": " + str(value)
+ elif visible:
+ target.text = title + ": " + str(value)
+ $MarginContainer/VBoxContainer.move_child(target, order)
diff --git a/quality-godot-first-person-2-2.6.1/addons/fpc/reticles/reticle_0.tscn b/quality-godot-first-person-2-2.6.1/addons/fpc/reticles/reticle_0.tscn
new file mode 100644
index 0000000..2828124
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/addons/fpc/reticles/reticle_0.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=2 format=3 uid="uid://coqpusufa8a6k"]
+
+[sub_resource type="GDScript" id="GDScript_10f85"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var character : CharacterBody3D
+
+@export_group(\"Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+
+func update_reticle_settings():
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+"
+
+[node name="Reticle" type="CenterContainer"]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_10f85")
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
diff --git a/quality-godot-first-person-2-2.6.1/addons/fpc/reticles/reticle_1.tscn b/quality-godot-first-person-2-2.6.1/addons/fpc/reticles/reticle_1.tscn
new file mode 100644
index 0000000..bb83b83
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/addons/fpc/reticles/reticle_1.tscn
@@ -0,0 +1,104 @@
+[gd_scene load_steps=2 format=3 uid="uid://3mij3cjhkwsm"]
+
+[sub_resource type="GDScript" id="GDScript_a8kpl"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var reticle_lines : Array[Line2D]
+@export var character : CharacterBody3D
+
+@export_group(\"Animate\")
+@export var animated_reticle : bool = true
+@export var reticle_speed : float = 0.5
+@export var reticle_spread : float = 4.0
+
+@export_group(\"Dot Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+@export_group(\"Line Settings\")
+@export var line_color : Color = Color.WHITE
+@export var line_width : int = 2
+@export var line_length : int = 10
+@export var line_distance : int = 5
+@export_enum(\"None\", \"Round\") var cap_mode : int = 0
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+ if animated_reticle:
+ animate_reticle_lines()
+
+
+func animate_reticle_lines():
+ var vel = character.get_real_velocity()
+ var origin = Vector3(0,0,0)
+ var pos = Vector2(0,0)
+ var speed = origin.distance_to(vel)
+
+ reticle_lines[0].position = lerp(reticle_lines[0].position, pos + Vector2(0, -speed * reticle_spread), reticle_speed)
+ reticle_lines[1].position = lerp(reticle_lines[1].position, pos + Vector2(-speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[2].position = lerp(reticle_lines[2].position, pos + Vector2(speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[3].position = lerp(reticle_lines[3].position, pos + Vector2(0, speed * reticle_spread), reticle_speed)
+
+
+func update_reticle_settings():
+ # Dot
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+
+ # Lines
+ for line in reticle_lines:
+ line.default_color = line_color
+ line.width = line_width
+ if cap_mode == 0:
+ line.begin_cap_mode = Line2D.LINE_CAP_NONE
+ line.end_cap_mode = Line2D.LINE_CAP_NONE
+ elif cap_mode == 1:
+ line.begin_cap_mode = Line2D.LINE_CAP_ROUND
+ line.end_cap_mode = Line2D.LINE_CAP_ROUND
+
+ # Please someone find a better way to do this
+ reticle_lines[0].points[0].y = -line_distance
+ reticle_lines[0].points[1].y = -line_length - line_distance
+ reticle_lines[1].points[0].x = -line_distance
+ reticle_lines[1].points[1].x = -line_length - line_distance
+ reticle_lines[2].points[0].x = line_distance
+ reticle_lines[2].points[1].x = line_length + line_distance
+ reticle_lines[3].points[0].y = line_distance
+ reticle_lines[3].points[1].y = line_length + line_distance
+"
+
+[node name="Reticle" type="CenterContainer" node_paths=PackedStringArray("reticle_lines")]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_a8kpl")
+reticle_lines = [NodePath("top"), NodePath("left"), NodePath("right"), NodePath("bottom")]
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
+
+[node name="top" type="Line2D" parent="."]
+points = PackedVector2Array(0, -5, 0, -15)
+width = 2.0
+
+[node name="left" type="Line2D" parent="."]
+points = PackedVector2Array(-5, 0, -15, 0)
+width = 2.0
+
+[node name="right" type="Line2D" parent="."]
+points = PackedVector2Array(5, 0, 15, 0)
+width = 2.0
+
+[node name="bottom" type="Line2D" parent="."]
+points = PackedVector2Array(0, 5, 0, 15)
+width = 2.0
diff --git a/quality-godot-first-person-2-2.6.1/icon.svg b/quality-godot-first-person-2-2.6.1/icon.svg
new file mode 100644
index 0000000..ea6ce87
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/icon.svg
@@ -0,0 +1,126 @@
+
+
+
+
diff --git a/quality-godot-first-person-2-2.6.1/icon.svg.import b/quality-godot-first-person-2-2.6.1/icon.svg.import
new file mode 100644
index 0000000..9e0a681
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://64npoko7rqya"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/quality-godot-first-person-2-2.6.1/project.godot b/quality-godot-first-person-2-2.6.1/project.godot
new file mode 100644
index 0000000..4626ad9
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/project.godot
@@ -0,0 +1,65 @@
+; 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
+
+[application]
+
+config/name="first person controller 2.0"
+run/main_scene="res://test_world.tscn"
+config/features=PackedStringArray("4.3", "Forward Plus")
+config/icon="res://icon.svg"
+
+[display]
+
+window/size/mode=2
+
+[input]
+
+ui_left={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
+, 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)
+]
+}
+ui_right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
+, 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)
+]
+}
+ui_up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
+, 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)
+]
+}
+ui_down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
+, 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)
+]
+}
+crouch={
+"deadzone": 0.5,
+"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)
+]
+}
+sprint={
+"deadzone": 0.5,
+"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)
+]
+}
diff --git a/quality-godot-first-person-2-2.6.1/test_world.tscn b/quality-godot-first-person-2-2.6.1/test_world.tscn
new file mode 100644
index 0000000..b6901b9
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/test_world.tscn
@@ -0,0 +1,106 @@
+[gd_scene load_steps=15 format=3 uid="uid://cs4drhmc1bql5"]
+
+[ext_resource type="PackedScene" uid="uid://cc1m2a1obsyn4" path="res://addons/fpc/character.tscn" id="1_e18vq"]
+[ext_resource type="Texture2D" uid="uid://pe7a4weirj2g" path="res://textures/dark.png" id="2_08fdt"]
+[ext_resource type="Texture2D" uid="uid://cxjxvqmf4boxq" path="res://textures/green.png" id="3_q4clv"]
+[ext_resource type="Texture2D" uid="uid://dsv4jm4vydflb" path="res://textures/orange.png" id="4_1ns5t"]
+
+[sub_resource type="PhysicalSkyMaterial" id="PhysicalSkyMaterial_edcox"]
+ground_color = Color(0.160784, 0.815686, 0.905882, 1)
+
+[sub_resource type="Sky" id="Sky_2iust"]
+sky_material = SubResource("PhysicalSkyMaterial_edcox")
+
+[sub_resource type="Environment" id="Environment_20rw3"]
+background_mode = 2
+sky = SubResource("Sky_2iust")
+tonemap_mode = 1
+ssao_enabled = true
+
+[sub_resource type="Gradient" id="Gradient_ur0vy"]
+colors = PackedColorArray(0, 0.476245, 0.0193456, 1, 0.360494, 0.612721, 0.119744, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_jd3pw"]
+frequency = 0.0027
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_7akuf"]
+width = 1024
+height = 1024
+in_3d_space = true
+seamless = true
+color_ramp = SubResource("Gradient_ur0vy")
+noise = SubResource("FastNoiseLite_jd3pw")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_67ysu"]
+albedo_texture = SubResource("NoiseTexture2D_7akuf")
+uv1_scale = Vector3(0.1, 0.1, 0.1)
+uv1_triplanar = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gomnb"]
+albedo_texture = ExtResource("2_08fdt")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_u0sbk"]
+albedo_texture = ExtResource("3_q4clv")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7j4uu"]
+albedo_texture = ExtResource("4_1ns5t")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[node name="test_world" type="Node3D"]
+
+[node name="Character" parent="." instance=ExtResource("1_e18vq")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_20rw3")
+
+[node name="sun" type="DirectionalLight3D" parent="."]
+transform = Transform3D(0.87959, -0.436605, 0.188936, 0, 0.397148, 0.917755, -0.475732, -0.807248, 0.349328, 0, 0, 0)
+light_energy = 2.0
+shadow_enabled = true
+
+[node name="terrain" type="Node3D" parent="."]
+
+[node name="CSGBox3D" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -0.5, 10)
+use_collision = true
+size = Vector3(30, 1, 30)
+material = SubResource("StandardMaterial3D_67ysu")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, -10)
+use_collision = true
+size = Vector3(10, 1, 10)
+material = SubResource("StandardMaterial3D_gomnb")
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 1.8, -13)
+use_collision = true
+size = Vector3(4, 0.5, 4)
+material = SubResource("StandardMaterial3D_gomnb")
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(0.939693, 0.34202, 0, -0.34202, 0.939693, 0, 0, 0, 1, -9.5, 1.2, -10)
+use_collision = true
+size = Vector3(10, 1, 10)
+material = SubResource("StandardMaterial3D_u0sbk")
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.5, 3, -15.5)
+use_collision = true
+size = Vector3(19, 8, 1)
+material = SubResource("StandardMaterial3D_7j4uu")
diff --git a/quality-godot-first-person-2-2.6.1/textures/dark.png b/quality-godot-first-person-2-2.6.1/textures/dark.png
new file mode 100644
index 0000000..69be211
Binary files /dev/null and b/quality-godot-first-person-2-2.6.1/textures/dark.png differ
diff --git a/quality-godot-first-person-2-2.6.1/textures/dark.png.import b/quality-godot-first-person-2-2.6.1/textures/dark.png.import
new file mode 100644
index 0000000..2a82a3f
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/textures/dark.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pe7a4weirj2g"
+path.s3tc="res://.godot/imported/dark.png-6d46f668c80e231a58e570df85aad257.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/dark.png"
+dest_files=["res://.godot/imported/dark.png-6d46f668c80e231a58e570df85aad257.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/quality-godot-first-person-2-2.6.1/textures/green.png b/quality-godot-first-person-2-2.6.1/textures/green.png
new file mode 100644
index 0000000..7bc7cf8
Binary files /dev/null and b/quality-godot-first-person-2-2.6.1/textures/green.png differ
diff --git a/quality-godot-first-person-2-2.6.1/textures/green.png.import b/quality-godot-first-person-2-2.6.1/textures/green.png.import
new file mode 100644
index 0000000..7c7e044
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/textures/green.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxjxvqmf4boxq"
+path.s3tc="res://.godot/imported/green.png-b4f8ddc6b00d4e627f0e027e2e1193bf.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/green.png"
+dest_files=["res://.godot/imported/green.png-b4f8ddc6b00d4e627f0e027e2e1193bf.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/quality-godot-first-person-2-2.6.1/textures/orange.png b/quality-godot-first-person-2-2.6.1/textures/orange.png
new file mode 100644
index 0000000..dec5b59
Binary files /dev/null and b/quality-godot-first-person-2-2.6.1/textures/orange.png differ
diff --git a/quality-godot-first-person-2-2.6.1/textures/orange.png.import b/quality-godot-first-person-2-2.6.1/textures/orange.png.import
new file mode 100644
index 0000000..311f8ac
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/textures/orange.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dsv4jm4vydflb"
+path.s3tc="res://.godot/imported/orange.png-6785d3f8216fd22318e8ea839823715b.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/orange.png"
+dest_files=["res://.godot/imported/orange.png-6785d3f8216fd22318e8ea839823715b.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/quality-godot-first-person-2-2.6.1/textures/purple.png b/quality-godot-first-person-2-2.6.1/textures/purple.png
new file mode 100644
index 0000000..48a51c1
Binary files /dev/null and b/quality-godot-first-person-2-2.6.1/textures/purple.png differ
diff --git a/quality-godot-first-person-2-2.6.1/textures/purple.png.import b/quality-godot-first-person-2-2.6.1/textures/purple.png.import
new file mode 100644
index 0000000..9dc0969
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/textures/purple.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cl4kewig3pk7s"
+path="res://.godot/imported/purple.png-23488e84f4f0a47488be2c78494f2155.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://textures/purple.png"
+dest_files=["res://.godot/imported/purple.png-23488e84f4f0a47488be2c78494f2155.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/quality-godot-first-person-2-2.6.1/textures/red.png b/quality-godot-first-person-2-2.6.1/textures/red.png
new file mode 100644
index 0000000..bf1cb17
Binary files /dev/null and b/quality-godot-first-person-2-2.6.1/textures/red.png differ
diff --git a/quality-godot-first-person-2-2.6.1/textures/red.png.import b/quality-godot-first-person-2-2.6.1/textures/red.png.import
new file mode 100644
index 0000000..c8c15aa
--- /dev/null
+++ b/quality-godot-first-person-2-2.6.1/textures/red.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1h161t0v6hau"
+path="res://.godot/imported/red.png-3cad0ca19141406d60f5fd2311159a86.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://textures/red.png"
+dest_files=["res://.godot/imported/red.png-3cad0ca19141406d60f5fd2311159a86.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/quality-godot-first-person-2-main/.gitattributes b/quality-godot-first-person-2-main/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/quality-godot-first-person-2-main/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/quality-godot-first-person-2-main/.gitignore b/quality-godot-first-person-2-main/.gitignore
new file mode 100644
index 0000000..4709183
--- /dev/null
+++ b/quality-godot-first-person-2-main/.gitignore
@@ -0,0 +1,2 @@
+# Godot 4+ specific ignores
+.godot/
diff --git a/quality-godot-first-person-2-main/LICENSE b/quality-godot-first-person-2-main/LICENSE
new file mode 100644
index 0000000..f34733d
--- /dev/null
+++ b/quality-godot-first-person-2-main/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Colormatic
+
+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/quality-godot-first-person-2-main/README.md b/quality-godot-first-person-2-main/README.md
new file mode 100644
index 0000000..0acb1cb
--- /dev/null
+++ b/quality-godot-first-person-2-main/README.md
@@ -0,0 +1,67 @@
+# quality-godot-first-person-2
+Actually good first person controller for the Godot Engine.
+MIT License (credit Colormatic Studios)
+
+This first person controller was made because there aren't many first person controllers for Godot, and the ones that do exist are pretty bad.
+It is highly customizable and comes with many features, QOL, and clean code.
+
+Some parts came from StayAtHomeDev's FPS tutorial. You can find that [here](https://www.youtube.com/playlist?list=PLEHvj4yeNfeF6s-UVs5Zx5TfNYmeCiYwf).
+
+# Directions
+Move with WASD, space to jump, shift to sprint, C to crouch.
+
+**FEATURES:**
+- Extremely configurable
+- In-air momentum
+- Motion smoothing
+- FOV smoothing
+- Movement animations
+- Crouching
+- Sprinting
+- 2 crosshairs/reticles, one is animated (more to come?)
+- Controller/GamePad support (enabled through code, see wiki)
+- In-editor tools (enable editable children to use)
+
+If you make a cool game with this addon, I would love to hear about it!
+
+# Wiki
+**To start out**, you should probably remap all of the movement keys to your own control set.
+
+You can make this a super basic controller by just disabling everything.
+
+**How to add controller/GamePad support**
+- In the controls export group, there is a commented section at the end that says "Uncomment this if you want full controller support". Uncomment that block.
+- Make a key map for each direction (left, right, up, down) and map them to your joystick.
+- Write in these keymaps in the controls section of the player settings.
+- In the `handle_head_rotation` function, there is another block of commented code that says the same thing. Uncomment that too.
+- You should now be able to look around with the joystick. Make sure you add the other controls to the input map. (movement, jumping, crouching, sprinting, etc.)
+
+**Slope/staircase:**
+Credit to [roberto-urbani23](https://github.com/roberto-urbani23)
+In the character inspector, you can uncheck Stop on Slope and set the max angle to 89 (for some reason, 90 will make the player stuck). Also Snap Length to 1 otherwise your character will not remain attached to stairs if you sprint while going downstairs.
+
+**How to change settings:**
+Click on the character node and there should be settings in the "Feature Settings" group.
+
+**How to add animations for a mesh:**
+- Create a function for your animation and attach it to `_physics_process` to call it every frame.
+- Use `input_dir` as a boolean (it is actually a `Vector2`) to know if the player is walking.
+- Use the `state` member variable to tell if the player is sprinting or crouching.
+- Use the `is_on_floor` function to tell if the player is standing or falling.
+
+**How to change reticles (crosshairs):**
+Change the "Default Reticle" setting to your reticle file.
+During runtime:
+Use the `change_reticle` function on the character.
+
+**How to create a new reticle:**
+- Choose a reticle to base it off of.
+- Open that reticle and save it as a new reticle.
+- Remove the script from the reticle and create a new one. (for some reason you have to do this)
+- Edit the reticle to your needs.
+- Follow the "how to change reticles" directions to use it.
+
+**How to use the editor tools:**
+- Enable editable children on the `CharacterBody` node
+- Use the options in the Properties tab to change things
+- These changes apply in runtime as well
diff --git a/quality-godot-first-person-2-main/addons/fpc/EditorModule.gd b/quality-godot-first-person-2-main/addons/fpc/EditorModule.gd
new file mode 100644
index 0000000..0a3a74f
--- /dev/null
+++ b/quality-godot-first-person-2-main/addons/fpc/EditorModule.gd
@@ -0,0 +1,49 @@
+@tool
+extends Node
+
+# This does not effect runtime yet but will in the future.
+
+@export_category("Controller Editor Module")
+@export_range(-360.0, 360.0, 0.01, "or_greater", "or_less") var head_y_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_y_rotation = new_rotation
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ update_configuration_warnings()
+@export_range(-90.0, 90.0, 0.01, "or_greater", "or_less") var head_x_rotation : float = 0.0:
+ set(new_rotation):
+ if HEAD:
+ head_x_rotation = new_rotation
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+ update_configuration_warnings()
+
+@export_group("Nodes")
+@export var CHARACTER : CharacterBody3D
+@export var head_path : String = "Head" # Relative to the parent node
+#@export var CAMERA : Camera3D
+#@export var HEADBOB_ANIMATION : AnimationPlayer
+#@export var JUMP_ANIMATION : AnimationPlayer
+#@export var CROUCH_ANIMATION : AnimationPlayer
+#@export var COLLISION_MESH : CollisionShape3D
+
+@onready var HEAD = get_node("../" + head_path)
+
+
+func _ready():
+ if !Engine.is_editor_hint():
+ #print("not editor")
+ HEAD.rotation.y = deg_to_rad(head_y_rotation)
+ HEAD.rotation.x = deg_to_rad(head_x_rotation)
+
+
+func _get_configuration_warnings():
+ var warnings = []
+
+ if head_y_rotation > 360:
+ warnings.append("The head rotation is greater than 360")
+
+ if head_y_rotation < -360:
+ warnings.append("The head rotation is less than -360")
+
+ # Returning an empty array gives no warnings
+ return warnings
diff --git a/quality-godot-first-person-2-main/addons/fpc/character.gd b/quality-godot-first-person-2-main/addons/fpc/character.gd
new file mode 100644
index 0000000..5a5f7f7
--- /dev/null
+++ b/quality-godot-first-person-2-main/addons/fpc/character.gd
@@ -0,0 +1,488 @@
+# COPYRIGHT Colormatic Studios
+# MIT license
+# Quality Godot First Person Controller v2
+
+
+extends CharacterBody3D
+
+
+#region Character Export Group
+
+## The settings for the character's movement and feel.
+@export_category("Character")
+## The speed that the character moves at without crouching or sprinting.
+@export var base_speed : float = 3.0
+## The speed that the character moves at when sprinting.
+@export var sprint_speed : float = 6.0
+## The speed that the character moves at when crouching.
+@export var crouch_speed : float = 1.0
+
+## How fast the character speeds up and slows down when Motion Smoothing is on.
+@export var acceleration : float = 10.0
+## How high the player jumps.
+@export var jump_velocity : float = 4.5
+## How far the player turns when the mouse is moved.
+@export var mouse_sensitivity : float = 0.1
+## Invert the X axis input for the camera.
+@export var invert_camera_x_axis : bool = false
+## Invert the Y axis input for the camera.
+@export var invert_camera_y_axis : bool = false
+## Whether the player can use movement inputs. Does not stop outside forces or jumping. See Jumping Enabled.
+@export var immobile : bool = false
+## The reticle file to import at runtime. By default are in res://addons/fpc/reticles/. Set to an empty string to remove.
+@export_file var default_reticle
+
+#endregion
+
+#region Nodes Export Group
+
+@export_group("Nodes")
+## A reference to the camera for use in the character script. This is the parent node to the camera and is rotated instead of the camera for mouse input.
+@export var HEAD : Node3D
+## A reference to the camera for use in the character script.
+@export var CAMERA : Camera3D
+## A reference to the headbob animation for use in the character script.
+@export var HEADBOB_ANIMATION : AnimationPlayer
+## A reference to the jump animation for use in the character script.
+@export var JUMP_ANIMATION : AnimationPlayer
+## A reference to the crouch animation for use in the character script.
+@export var CROUCH_ANIMATION : AnimationPlayer
+## A reference to the the player's collision shape for use in the character script.
+@export var COLLISION_MESH : CollisionShape3D
+
+#endregion
+
+#region Controls Export Group
+
+# We are using UI controls because they are built into Godot Engine so they can be used right away
+@export_group("Controls")
+## Use the Input Map to map a mouse/keyboard input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controls : Dictionary = {
+ LEFT = "ui_left",
+ RIGHT = "ui_right",
+ FORWARD = "ui_up",
+ BACKWARD = "ui_down",
+ JUMP = "ui_accept",
+ CROUCH = "crouch",
+ SPRINT = "sprint",
+ PAUSE = "ui_cancel"
+ }
+@export_subgroup("Controller Specific")
+## This only affects how the camera is handled, the rest should be covered by adding controller inputs to the existing actions in the Input Map.
+@export var controller_support : bool = false
+## Use the Input Map to map a controller input to an action and add a reference to it to this dictionary to be used in the script.
+@export var controller_controls : Dictionary = {
+ LOOK_LEFT = "look_left",
+ LOOK_RIGHT = "look_right",
+ LOOK_UP = "look_up",
+ LOOK_DOWN = "look_down"
+ }
+## The sensitivity of the analog stick that controls camera rotation. Lower is less sensitive and higher is more sensitive.
+@export_range(0.001, 1, 0.001) var look_sensitivity : float = 0.035
+
+#endregion
+
+#region Feature Settings Export Group
+
+@export_group("Feature Settings")
+## Enable or disable jumping. Useful for restrictive storytelling environments.
+@export var jumping_enabled : bool = true
+## Whether the player can move in the air or not.
+@export var in_air_momentum : bool = true
+## Smooths the feel of walking.
+@export var motion_smoothing : bool = true
+## Enables or disables sprinting.
+@export var sprint_enabled : bool = true
+## Toggles the sprinting state when button is pressed or requires the player to hold the button down to remain sprinting.
+@export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0
+## Enables or disables crouching.
+@export var crouch_enabled : bool = true
+## Toggles the crouch state when button is pressed or requires the player to hold the button down to remain crouched.
+@export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0
+## Wether sprinting should effect FOV.
+@export var dynamic_fov : bool = true
+## If the player holds down the jump button, should the player keep hopping.
+@export var continuous_jumping : bool = true
+## Enables the view bobbing animation.
+@export var view_bobbing : bool = true
+## Enables an immersive animation when the player jumps and hits the ground.
+@export var jump_animation : bool = true
+## This determines wether the player can use the pause button, not wether the game will actually pause.
+@export var pausing_enabled : bool = true
+## Use with caution.
+@export var gravity_enabled : bool = true
+## If your game changes the gravity value during gameplay, check this property to allow the player to experience the change in gravity.
+@export var dynamic_gravity : bool = false
+
+#endregion
+
+#region Member Variable Initialization
+
+# These are variables used in this script that don't need to be exposed in the editor.
+var speed : float = base_speed
+var current_speed : float = 0.0
+# States: normal, crouching, sprinting
+var state : String = "normal"
+var low_ceiling : bool = false # This is for when the ceiling is too low and the player needs to crouch.
+var was_on_floor : bool = true # Was the player on the floor last frame (for landing animation)
+
+# The reticle should always have a Control node as the root
+var RETICLE : Control
+
+# Get the gravity from the project settings to be synced with RigidBody nodes
+var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process
+
+# Stores mouse input for rotating the camera in the physics process
+var mouseInput : Vector2 = Vector2(0,0)
+
+#endregion
+
+
+
+#region Main Control Flow
+
+func _ready():
+ #It is safe to comment this line if your game doesn't start with the mouse captured
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+
+ # If the controller is rotated in a certain direction for game design purposes, redirect this rotation into the head.
+ HEAD.rotation.y = rotation.y
+ rotation.y = 0
+
+ if default_reticle:
+ change_reticle(default_reticle)
+
+ initialize_animations()
+ check_controls()
+ enter_normal_state()
+
+
+func _process(_delta):
+ if pausing_enabled:
+ handle_pausing()
+
+ update_debug_menu_per_frame()
+
+
+func _physics_process(delta): # Most things happen here.
+ # Gravity
+ if dynamic_gravity:
+ gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
+ if not is_on_floor() and gravity and gravity_enabled:
+ velocity.y -= gravity * delta
+
+ handle_jumping()
+
+ var input_dir = Vector2.ZERO
+
+ if not immobile: # Immobility works by interrupting user input, so other forces can still be applied to the player
+ input_dir = Input.get_vector(controls.LEFT, controls.RIGHT, controls.FORWARD, controls.BACKWARD)
+
+ handle_movement(delta, input_dir)
+
+ handle_head_rotation()
+
+ # The player is not able to stand up if the ceiling is too low
+ low_ceiling = $CrouchCeilingDetection.is_colliding()
+
+ handle_state(input_dir)
+ if dynamic_fov: # This may be changed to an AnimationPlayer
+ update_camera_fov()
+
+ if view_bobbing:
+ play_headbob_animation(input_dir)
+
+ if jump_animation:
+ play_jump_animation()
+
+ update_debug_menu_per_tick()
+
+ was_on_floor = is_on_floor() # This must always be at the end of physics_process
+
+#endregion
+
+#region Input Handling
+
+func handle_jumping():
+ if jumping_enabled:
+ if continuous_jumping: # Hold down the jump button
+ if Input.is_action_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity # Adding instead of setting so jumping on slopes works properly
+ else:
+ if Input.is_action_just_pressed(controls.JUMP) and is_on_floor() and !low_ceiling:
+ if jump_animation:
+ JUMP_ANIMATION.play("jump", 0.25)
+ velocity.y += jump_velocity
+
+
+func handle_movement(delta, input_dir):
+ var direction = input_dir.rotated(-HEAD.rotation.y)
+ direction = Vector3(direction.x, 0, direction.y)
+ move_and_slide()
+
+ if in_air_momentum:
+ if is_on_floor():
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+ else:
+ if motion_smoothing:
+ velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
+ velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
+ else:
+ velocity.x = direction.x * speed
+ velocity.z = direction.z * speed
+
+
+func handle_head_rotation():
+ if invert_camera_x_axis:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity
+
+ if invert_camera_y_axis:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity * -1
+ else:
+ HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity
+
+ if controller_support:
+ var controller_view_rotation = Input.get_vector(controller_controls.LOOK_DOWN, controller_controls.LOOK_UP, controller_controls.LOOK_RIGHT, controller_controls.LOOK_LEFT) * look_sensitivity # These are inverted because of the nature of 3D rotation.
+ if invert_camera_x_axis:
+ HEAD.rotation.x += controller_view_rotation.x * -1
+ else:
+ HEAD.rotation.x += controller_view_rotation.x
+
+ if invert_camera_y_axis:
+ HEAD.rotation.y += controller_view_rotation.y * -1
+ else:
+ HEAD.rotation.y += controller_view_rotation.y
+
+ mouseInput = Vector2(0,0)
+ HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
+
+
+func check_controls(): # If you add a control, you might want to add a check for it here.
+ # The actions are being disabled so the engine doesn't halt the entire project in debug mode
+ if !InputMap.has_action(controls.JUMP):
+ push_error("No control mapped for jumping. Please add an input map control. Disabling jump.")
+ jumping_enabled = false
+ if !InputMap.has_action(controls.LEFT):
+ push_error("No control mapped for move left. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.RIGHT):
+ push_error("No control mapped for move right. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.FORWARD):
+ push_error("No control mapped for move forward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.BACKWARD):
+ push_error("No control mapped for move backward. Please add an input map control. Disabling movement.")
+ immobile = true
+ if !InputMap.has_action(controls.PAUSE):
+ push_error("No control mapped for pause. Please add an input map control. Disabling pausing.")
+ pausing_enabled = false
+ if !InputMap.has_action(controls.CROUCH):
+ push_error("No control mapped for crouch. Please add an input map control. Disabling crouching.")
+ crouch_enabled = false
+ if !InputMap.has_action(controls.SPRINT):
+ push_error("No control mapped for sprint. Please add an input map control. Disabling sprinting.")
+ sprint_enabled = false
+
+#endregion
+
+#region State Handling
+
+func handle_state(moving):
+ if sprint_enabled:
+ if sprint_mode == 0:
+ if Input.is_action_pressed(controls.SPRINT) and state != "crouching":
+ if moving:
+ if state != "sprinting":
+ enter_sprint_state()
+ else:
+ if state == "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+ elif sprint_mode == 1:
+ if moving:
+ # If the player is holding sprint before moving, handle that scenario
+ if Input.is_action_pressed(controls.SPRINT) and state == "normal":
+ enter_sprint_state()
+ if Input.is_action_just_pressed(controls.SPRINT):
+ match state:
+ "normal":
+ enter_sprint_state()
+ "sprinting":
+ enter_normal_state()
+ elif state == "sprinting":
+ enter_normal_state()
+
+ if crouch_enabled:
+ if crouch_mode == 0:
+ if Input.is_action_pressed(controls.CROUCH) and state != "sprinting":
+ if state != "crouching":
+ enter_crouch_state()
+ elif state == "crouching" and !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+ elif crouch_mode == 1:
+ if Input.is_action_just_pressed(controls.CROUCH):
+ match state:
+ "normal":
+ enter_crouch_state()
+ "crouching":
+ if !$CrouchCeilingDetection.is_colliding():
+ enter_normal_state()
+
+
+# Any enter state function should only be called once when you want to enter that state, not every frame.
+func enter_normal_state():
+ #print("entering normal state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "normal"
+ speed = base_speed
+
+func enter_crouch_state():
+ #print("entering crouch state")
+ state = "crouching"
+ speed = crouch_speed
+ CROUCH_ANIMATION.play("crouch")
+
+func enter_sprint_state():
+ #print("entering sprint state")
+ var prev_state = state
+ if prev_state == "crouching":
+ CROUCH_ANIMATION.play_backwards("crouch")
+ state = "sprinting"
+ speed = sprint_speed
+
+#endregion
+
+#region Animation Handling
+
+func initialize_animations():
+ # Reset the camera position
+ # If you want to change the default head height, change these animations.
+ HEADBOB_ANIMATION.play("RESET")
+ JUMP_ANIMATION.play("RESET")
+ CROUCH_ANIMATION.play("RESET")
+
+func play_headbob_animation(moving):
+ if moving and is_on_floor():
+ var use_headbob_animation : String
+ match state:
+ "normal","crouching":
+ use_headbob_animation = "walk"
+ "sprinting":
+ use_headbob_animation = "sprint"
+
+ var was_playing : bool = false
+ if HEADBOB_ANIMATION.current_animation == use_headbob_animation:
+ was_playing = true
+
+ HEADBOB_ANIMATION.play(use_headbob_animation, 0.25)
+ HEADBOB_ANIMATION.speed_scale = (current_speed / base_speed) * 1.75
+ if !was_playing:
+ HEADBOB_ANIMATION.seek(float(randi() % 2)) # Randomize the initial headbob direction
+ # Let me explain that piece of code because it looks like it does the opposite of what it actually does.
+ # The headbob animation has two starting positions. One is at 0 and the other is at 1.
+ # randi() % 2 returns either 0 or 1, and so the animation randomly starts at one of the starting positions.
+ # This code is extremely performant but it makes no sense.
+
+ else:
+ if HEADBOB_ANIMATION.current_animation == "sprint" or HEADBOB_ANIMATION.current_animation == "walk":
+ HEADBOB_ANIMATION.speed_scale = 1
+ HEADBOB_ANIMATION.play("RESET", 1)
+
+func play_jump_animation():
+ if !was_on_floor and is_on_floor(): # The player just landed
+ var facing_direction : Vector3 = CAMERA.get_global_transform().basis.x
+ var facing_direction_2D : Vector2 = Vector2(facing_direction.x, facing_direction.z).normalized()
+ var velocity_2D : Vector2 = Vector2(velocity.x, velocity.z).normalized()
+
+ # Compares velocity direction against the camera direction (via dot product) to determine which landing animation to play.
+ var side_landed : int = round(velocity_2D.dot(facing_direction_2D))
+
+ if side_landed > 0:
+ JUMP_ANIMATION.play("land_right", 0.25)
+ elif side_landed < 0:
+ JUMP_ANIMATION.play("land_left", 0.25)
+ else:
+ JUMP_ANIMATION.play("land_center", 0.25)
+
+#endregion
+
+#region Debug Menu
+
+func update_debug_menu_per_frame():
+ $UserInterface/DebugPanel.add_property("FPS", Performance.get_monitor(Performance.TIME_FPS), 0)
+ var status : String = state
+ if !is_on_floor():
+ status += " in the air"
+ $UserInterface/DebugPanel.add_property("State", status, 4)
+
+
+func update_debug_menu_per_tick():
+ # Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
+ current_speed = Vector3.ZERO.distance_to(get_real_velocity())
+ $UserInterface/DebugPanel.add_property("Speed", snappedf(current_speed, 0.001), 1)
+ $UserInterface/DebugPanel.add_property("Target speed", speed, 2)
+ var cv : Vector3 = get_real_velocity()
+ var vd : Array[float] = [
+ snappedf(cv.x, 0.001),
+ snappedf(cv.y, 0.001),
+ snappedf(cv.z, 0.001)
+ ]
+ var readable_velocity : String = "X: " + str(vd[0]) + " Y: " + str(vd[1]) + " Z: " + str(vd[2])
+ $UserInterface/DebugPanel.add_property("Velocity", readable_velocity, 3)
+
+
+func _unhandled_input(event : InputEvent):
+ if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
+ mouseInput.x += event.relative.x
+ mouseInput.y += event.relative.y
+ # Toggle debug menu
+ elif event is InputEventKey:
+ if event.is_released():
+ # Where we're going, we don't need InputMap
+ if event.keycode == 4194338: # F7
+ $UserInterface/DebugPanel.visible = !$UserInterface/DebugPanel.visible
+
+#endregion
+
+#region Misc Functions
+
+func change_reticle(reticle): # Yup, this function is kinda strange
+ if RETICLE:
+ RETICLE.queue_free()
+
+ RETICLE = load(reticle).instantiate()
+ RETICLE.character = self
+ $UserInterface.add_child(RETICLE)
+
+
+func update_camera_fov():
+ if state == "sprinting":
+ CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3)
+ else:
+ CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3)
+
+func handle_pausing():
+ if Input.is_action_just_pressed(controls.PAUSE):
+ # You may want another node to handle pausing, because this player may get paused too.
+ match Input.mouse_mode:
+ Input.MOUSE_MODE_CAPTURED:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ #get_tree().paused = false
+ Input.MOUSE_MODE_VISIBLE:
+ Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
+ #get_tree().paused = false
+
+#endregion
diff --git a/quality-godot-first-person-2-main/addons/fpc/character.tscn b/quality-godot-first-person-2-main/addons/fpc/character.tscn
new file mode 100644
index 0000000..3cfd7c8
--- /dev/null
+++ b/quality-godot-first-person-2-main/addons/fpc/character.tscn
@@ -0,0 +1,455 @@
+[gd_scene load_steps=22 format=3 uid="uid://cc1m2a1obsyn4"]
+
+[ext_resource type="Script" path="res://addons/fpc/character.gd" id="1_0t4e8"]
+[ext_resource type="Script" path="res://addons/fpc/EditorModule.gd" id="3_v3ckk"]
+[ext_resource type="Script" path="res://addons/fpc/debug.gd" id="3_x1wcc"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kp17n"]
+albedo_color = Color(0.909804, 0.596078, 0, 1)
+clearcoat_enabled = true
+clearcoat_roughness = 0.2
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_jw1de"]
+material = SubResource("StandardMaterial3D_kp17n")
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uy03j"]
+
+[sub_resource type="Animation" id="Animation_j8cx7"]
+resource_name = "RESET"
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [2.0]
+}
+
+[sub_resource type="Animation" id="Animation_5ec5e"]
+resource_name = "crouch"
+length = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Mesh:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Collision:position")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Head:position")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 1.5, 0), Vector3(0, 1.12508, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Mesh:mesh:height")
+tracks/3/interp = 2
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Collision:shape:height")
+tracks/4/interp = 2
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0, 0.2),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [2.0, 1.5]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_5e5t5"]
+_data = {
+"RESET": SubResource("Animation_j8cx7"),
+"crouch": SubResource("Animation_5ec5e")
+}
+
+[sub_resource type="Animation" id="Animation_gh776"]
+resource_name = "RESET"
+length = 0.001
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+
+[sub_resource type="Animation" id="Animation_8ku67"]
+resource_name = "sprint"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.06, -0.25, 0, 0.25, -0.01, 0, 0, 0, 0, 0, -0.06, -0.25, 0.01, 0.25, 0.01, 0, 0, 0, 0, 0, 0.06, -0.25, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(0.05, -0.25, 0, 0.2, -0.01, 0, -0.2, 0.000186046, 0.2, 0.000186046, 0.05, -0.2, -0.01, 0.2, -0.01, 0, -0.2, 0, 0.2, 0, 0.05, -0.2, -0.01, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="Animation" id="Animation_lrqmv"]
+resource_name = "walk"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:position:x")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
+"points": PackedFloat32Array(0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, -0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.04, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+tracks/1/type = "bezier"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position:y")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
+"points": PackedFloat32Array(-0.05, -0.25, 0, 0.2, 0.005, 0, -0.2, 0.000186046, 0.2, 0.000186046, -0.05, -0.2, 0.005, 0.2, 0.005, 0, -0.2, 0, 0.2, 0, -0.05, -0.2, 0.005, 0.25, 0),
+"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_o0unb"]
+_data = {
+"RESET": SubResource("Animation_gh776"),
+"sprint": SubResource("Animation_8ku67"),
+"walk": SubResource("Animation_lrqmv")
+}
+
+[sub_resource type="Animation" id="Animation_fvvjq"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_s07ye"]
+resource_name = "jump"
+length = 3.0
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.6, 3),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_3eyjl"]
+resource_name = "land_center"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_l1rph"]
+resource_name = "land_left"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_vsknp"]
+resource_name = "land_right"
+length = 1.5
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Camera:rotation")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, -0.0174533), Vector3(0, 0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Camera:position")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.5, 1.5),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_qeg5r"]
+_data = {
+"RESET": SubResource("Animation_fvvjq"),
+"jump": SubResource("Animation_s07ye"),
+"land_center": SubResource("Animation_3eyjl"),
+"land_left": SubResource("Animation_l1rph"),
+"land_right": SubResource("Animation_vsknp")
+}
+
+[sub_resource type="Theme" id="Theme_wdf0f"]
+MarginContainer/constants/margin_bottom = 10
+MarginContainer/constants/margin_left = 10
+MarginContainer/constants/margin_right = 10
+MarginContainer/constants/margin_top = 10
+
+[sub_resource type="SphereShape3D" id="SphereShape3D_k4wwl"]
+
+[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "HEADBOB_ANIMATION", "JUMP_ANIMATION", "CROUCH_ANIMATION", "COLLISION_MESH")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0, 0)
+script = ExtResource("1_0t4e8")
+default_reticle = "res://addons/fpc/reticles/reticle_1.tscn"
+HEAD = NodePath("Head")
+CAMERA = NodePath("Head/Camera")
+HEADBOB_ANIMATION = NodePath("Head/HeadbobAnimation")
+JUMP_ANIMATION = NodePath("Head/JumpAnimation")
+CROUCH_ANIMATION = NodePath("CrouchAnimation")
+COLLISION_MESH = NodePath("Collision")
+
+[node name="Mesh" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("CapsuleMesh_jw1de")
+
+[node name="Collision" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("CapsuleShape3D_uy03j")
+
+[node name="CrouchAnimation" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_5e5t5")
+}
+
+[node name="Head" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
+
+[node name="Camera" type="Camera3D" parent="Head"]
+
+[node name="HeadbobAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+"": SubResource("AnimationLibrary_o0unb")
+}
+blend_times = [&"RESET", &"RESET", 0.5, &"RESET", &"walk", 0.5, &"walk", &"RESET", 0.5]
+
+[node name="JumpAnimation" type="AnimationPlayer" parent="Head"]
+libraries = {
+"": SubResource("AnimationLibrary_qeg5r")
+}
+speed_scale = 4.0
+
+[node name="UserInterface" type="Control" parent="."]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 1
+
+[node name="DebugPanel" type="PanelContainer" parent="UserInterface"]
+visible = false
+layout_mode = 0
+offset_left = 10.0
+offset_top = 10.0
+offset_right = 453.0
+offset_bottom = 50.0
+theme = SubResource("Theme_wdf0f")
+script = ExtResource("3_x1wcc")
+
+[node name="MarginContainer" type="MarginContainer" parent="UserInterface/DebugPanel"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="UserInterface/DebugPanel/MarginContainer"]
+layout_mode = 2
+
+[node name="CrouchCeilingDetection" type="ShapeCast3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("SphereShape3D_k4wwl")
+target_position = Vector3(0, 0.5, 0)
+
+[node name="EditorModule" type="Node" parent="."]
+script = ExtResource("3_v3ckk")
diff --git a/quality-godot-first-person-2-main/addons/fpc/debug.gd b/quality-godot-first-person-2-main/addons/fpc/debug.gd
new file mode 100644
index 0000000..efdb7a4
--- /dev/null
+++ b/quality-godot-first-person-2-main/addons/fpc/debug.gd
@@ -0,0 +1,18 @@
+extends PanelContainer
+
+
+func _process(delta):
+ if visible:
+ pass
+
+func add_property(title : String, value, order : int): # This can either be called once for a static property or called every frame for a dynamic property
+ var target
+ target = $MarginContainer/VBoxContainer.find_child(title, true, false) # I have no idea what true and false does here, the function should be more specific
+ if !target:
+ target = Label.new() # Debug lines are of type Label
+ $MarginContainer/VBoxContainer.add_child(target)
+ target.name = title
+ target.text = title + ": " + str(value)
+ elif visible:
+ target.text = title + ": " + str(value)
+ $MarginContainer/VBoxContainer.move_child(target, order)
diff --git a/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_0.tscn b/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_0.tscn
new file mode 100644
index 0000000..2828124
--- /dev/null
+++ b/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_0.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=2 format=3 uid="uid://coqpusufa8a6k"]
+
+[sub_resource type="GDScript" id="GDScript_10f85"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var character : CharacterBody3D
+
+@export_group(\"Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+
+func update_reticle_settings():
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+"
+
+[node name="Reticle" type="CenterContainer"]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_10f85")
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
diff --git a/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_1.tscn b/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_1.tscn
new file mode 100644
index 0000000..bb83b83
--- /dev/null
+++ b/quality-godot-first-person-2-main/addons/fpc/reticles/reticle_1.tscn
@@ -0,0 +1,104 @@
+[gd_scene load_steps=2 format=3 uid="uid://3mij3cjhkwsm"]
+
+[sub_resource type="GDScript" id="GDScript_a8kpl"]
+script/source = "extends CenterContainer
+
+
+@export_category(\"Reticle\")
+@export_group(\"Nodes\")
+@export var reticle_lines : Array[Line2D]
+@export var character : CharacterBody3D
+
+@export_group(\"Animate\")
+@export var animated_reticle : bool = true
+@export var reticle_speed : float = 0.5
+@export var reticle_spread : float = 4.0
+
+@export_group(\"Dot Settings\")
+@export var dot_size : int = 1
+@export var dot_color : Color = Color.WHITE
+
+@export_group(\"Line Settings\")
+@export var line_color : Color = Color.WHITE
+@export var line_width : int = 2
+@export var line_length : int = 10
+@export var line_distance : int = 5
+@export_enum(\"None\", \"Round\") var cap_mode : int = 0
+
+
+func _process(_delta):
+ if visible: # If the reticle is disabled (not visible), don't bother updating it
+ update_reticle_settings()
+ if animated_reticle:
+ animate_reticle_lines()
+
+
+func animate_reticle_lines():
+ var vel = character.get_real_velocity()
+ var origin = Vector3(0,0,0)
+ var pos = Vector2(0,0)
+ var speed = origin.distance_to(vel)
+
+ reticle_lines[0].position = lerp(reticle_lines[0].position, pos + Vector2(0, -speed * reticle_spread), reticle_speed)
+ reticle_lines[1].position = lerp(reticle_lines[1].position, pos + Vector2(-speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[2].position = lerp(reticle_lines[2].position, pos + Vector2(speed * reticle_spread, 0), reticle_speed)
+ reticle_lines[3].position = lerp(reticle_lines[3].position, pos + Vector2(0, speed * reticle_spread), reticle_speed)
+
+
+func update_reticle_settings():
+ # Dot
+ $dot.scale.x = dot_size
+ $dot.scale.y = dot_size
+ $dot.color = dot_color
+
+ # Lines
+ for line in reticle_lines:
+ line.default_color = line_color
+ line.width = line_width
+ if cap_mode == 0:
+ line.begin_cap_mode = Line2D.LINE_CAP_NONE
+ line.end_cap_mode = Line2D.LINE_CAP_NONE
+ elif cap_mode == 1:
+ line.begin_cap_mode = Line2D.LINE_CAP_ROUND
+ line.end_cap_mode = Line2D.LINE_CAP_ROUND
+
+ # Please someone find a better way to do this
+ reticle_lines[0].points[0].y = -line_distance
+ reticle_lines[0].points[1].y = -line_length - line_distance
+ reticle_lines[1].points[0].x = -line_distance
+ reticle_lines[1].points[1].x = -line_length - line_distance
+ reticle_lines[2].points[0].x = line_distance
+ reticle_lines[2].points[1].x = line_length + line_distance
+ reticle_lines[3].points[0].y = line_distance
+ reticle_lines[3].points[1].y = line_length + line_distance
+"
+
+[node name="Reticle" type="CenterContainer" node_paths=PackedStringArray("reticle_lines")]
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+script = SubResource("GDScript_a8kpl")
+reticle_lines = [NodePath("top"), NodePath("left"), NodePath("right"), NodePath("bottom")]
+
+[node name="dot" type="Polygon2D" parent="."]
+polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
+
+[node name="top" type="Line2D" parent="."]
+points = PackedVector2Array(0, -5, 0, -15)
+width = 2.0
+
+[node name="left" type="Line2D" parent="."]
+points = PackedVector2Array(-5, 0, -15, 0)
+width = 2.0
+
+[node name="right" type="Line2D" parent="."]
+points = PackedVector2Array(5, 0, 15, 0)
+width = 2.0
+
+[node name="bottom" type="Line2D" parent="."]
+points = PackedVector2Array(0, 5, 0, 15)
+width = 2.0
diff --git a/quality-godot-first-person-2-main/icon.svg b/quality-godot-first-person-2-main/icon.svg
new file mode 100644
index 0000000..ea6ce87
--- /dev/null
+++ b/quality-godot-first-person-2-main/icon.svg
@@ -0,0 +1,126 @@
+
+
+
+
diff --git a/quality-godot-first-person-2-main/icon.svg.import b/quality-godot-first-person-2-main/icon.svg.import
new file mode 100644
index 0000000..9e0a681
--- /dev/null
+++ b/quality-godot-first-person-2-main/icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://64npoko7rqya"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/quality-godot-first-person-2-main/project.godot b/quality-godot-first-person-2-main/project.godot
new file mode 100644
index 0000000..4626ad9
--- /dev/null
+++ b/quality-godot-first-person-2-main/project.godot
@@ -0,0 +1,65 @@
+; 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
+
+[application]
+
+config/name="first person controller 2.0"
+run/main_scene="res://test_world.tscn"
+config/features=PackedStringArray("4.3", "Forward Plus")
+config/icon="res://icon.svg"
+
+[display]
+
+window/size/mode=2
+
+[input]
+
+ui_left={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
+, 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)
+]
+}
+ui_right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
+, 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)
+]
+}
+ui_up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
+, 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)
+]
+}
+ui_down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
+, 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)
+]
+}
+crouch={
+"deadzone": 0.5,
+"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)
+]
+}
+sprint={
+"deadzone": 0.5,
+"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)
+]
+}
diff --git a/quality-godot-first-person-2-main/test_world.tscn b/quality-godot-first-person-2-main/test_world.tscn
new file mode 100644
index 0000000..b6901b9
--- /dev/null
+++ b/quality-godot-first-person-2-main/test_world.tscn
@@ -0,0 +1,106 @@
+[gd_scene load_steps=15 format=3 uid="uid://cs4drhmc1bql5"]
+
+[ext_resource type="PackedScene" uid="uid://cc1m2a1obsyn4" path="res://addons/fpc/character.tscn" id="1_e18vq"]
+[ext_resource type="Texture2D" uid="uid://pe7a4weirj2g" path="res://textures/dark.png" id="2_08fdt"]
+[ext_resource type="Texture2D" uid="uid://cxjxvqmf4boxq" path="res://textures/green.png" id="3_q4clv"]
+[ext_resource type="Texture2D" uid="uid://dsv4jm4vydflb" path="res://textures/orange.png" id="4_1ns5t"]
+
+[sub_resource type="PhysicalSkyMaterial" id="PhysicalSkyMaterial_edcox"]
+ground_color = Color(0.160784, 0.815686, 0.905882, 1)
+
+[sub_resource type="Sky" id="Sky_2iust"]
+sky_material = SubResource("PhysicalSkyMaterial_edcox")
+
+[sub_resource type="Environment" id="Environment_20rw3"]
+background_mode = 2
+sky = SubResource("Sky_2iust")
+tonemap_mode = 1
+ssao_enabled = true
+
+[sub_resource type="Gradient" id="Gradient_ur0vy"]
+colors = PackedColorArray(0, 0.476245, 0.0193456, 1, 0.360494, 0.612721, 0.119744, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_jd3pw"]
+frequency = 0.0027
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_7akuf"]
+width = 1024
+height = 1024
+in_3d_space = true
+seamless = true
+color_ramp = SubResource("Gradient_ur0vy")
+noise = SubResource("FastNoiseLite_jd3pw")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_67ysu"]
+albedo_texture = SubResource("NoiseTexture2D_7akuf")
+uv1_scale = Vector3(0.1, 0.1, 0.1)
+uv1_triplanar = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gomnb"]
+albedo_texture = ExtResource("2_08fdt")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_u0sbk"]
+albedo_texture = ExtResource("3_q4clv")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7j4uu"]
+albedo_texture = ExtResource("4_1ns5t")
+metallic = 0.81
+metallic_specular = 0.2
+roughness = 0.5
+uv1_triplanar = true
+uv1_triplanar_sharpness = 0.000850145
+
+[node name="test_world" type="Node3D"]
+
+[node name="Character" parent="." instance=ExtResource("1_e18vq")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_20rw3")
+
+[node name="sun" type="DirectionalLight3D" parent="."]
+transform = Transform3D(0.87959, -0.436605, 0.188936, 0, 0.397148, 0.917755, -0.475732, -0.807248, 0.349328, 0, 0, 0)
+light_energy = 2.0
+shadow_enabled = true
+
+[node name="terrain" type="Node3D" parent="."]
+
+[node name="CSGBox3D" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -0.5, 10)
+use_collision = true
+size = Vector3(30, 1, 30)
+material = SubResource("StandardMaterial3D_67ysu")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, -10)
+use_collision = true
+size = Vector3(10, 1, 10)
+material = SubResource("StandardMaterial3D_gomnb")
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 1.8, -13)
+use_collision = true
+size = Vector3(4, 0.5, 4)
+material = SubResource("StandardMaterial3D_gomnb")
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(0.939693, 0.34202, 0, -0.34202, 0.939693, 0, 0, 0, 1, -9.5, 1.2, -10)
+use_collision = true
+size = Vector3(10, 1, 10)
+material = SubResource("StandardMaterial3D_u0sbk")
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="terrain"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.5, 3, -15.5)
+use_collision = true
+size = Vector3(19, 8, 1)
+material = SubResource("StandardMaterial3D_7j4uu")
diff --git a/quality-godot-first-person-2-main/textures/dark.png b/quality-godot-first-person-2-main/textures/dark.png
new file mode 100644
index 0000000..69be211
Binary files /dev/null and b/quality-godot-first-person-2-main/textures/dark.png differ
diff --git a/quality-godot-first-person-2-main/textures/dark.png.import b/quality-godot-first-person-2-main/textures/dark.png.import
new file mode 100644
index 0000000..2a82a3f
--- /dev/null
+++ b/quality-godot-first-person-2-main/textures/dark.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pe7a4weirj2g"
+path.s3tc="res://.godot/imported/dark.png-6d46f668c80e231a58e570df85aad257.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/dark.png"
+dest_files=["res://.godot/imported/dark.png-6d46f668c80e231a58e570df85aad257.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/quality-godot-first-person-2-main/textures/green.png b/quality-godot-first-person-2-main/textures/green.png
new file mode 100644
index 0000000..7bc7cf8
Binary files /dev/null and b/quality-godot-first-person-2-main/textures/green.png differ
diff --git a/quality-godot-first-person-2-main/textures/green.png.import b/quality-godot-first-person-2-main/textures/green.png.import
new file mode 100644
index 0000000..7c7e044
--- /dev/null
+++ b/quality-godot-first-person-2-main/textures/green.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxjxvqmf4boxq"
+path.s3tc="res://.godot/imported/green.png-b4f8ddc6b00d4e627f0e027e2e1193bf.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/green.png"
+dest_files=["res://.godot/imported/green.png-b4f8ddc6b00d4e627f0e027e2e1193bf.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/quality-godot-first-person-2-main/textures/orange.png b/quality-godot-first-person-2-main/textures/orange.png
new file mode 100644
index 0000000..dec5b59
Binary files /dev/null and b/quality-godot-first-person-2-main/textures/orange.png differ
diff --git a/quality-godot-first-person-2-main/textures/orange.png.import b/quality-godot-first-person-2-main/textures/orange.png.import
new file mode 100644
index 0000000..311f8ac
--- /dev/null
+++ b/quality-godot-first-person-2-main/textures/orange.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dsv4jm4vydflb"
+path.s3tc="res://.godot/imported/orange.png-6785d3f8216fd22318e8ea839823715b.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://textures/orange.png"
+dest_files=["res://.godot/imported/orange.png-6785d3f8216fd22318e8ea839823715b.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+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/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=0
diff --git a/quality-godot-first-person-2-main/textures/purple.png b/quality-godot-first-person-2-main/textures/purple.png
new file mode 100644
index 0000000..48a51c1
Binary files /dev/null and b/quality-godot-first-person-2-main/textures/purple.png differ
diff --git a/quality-godot-first-person-2-main/textures/purple.png.import b/quality-godot-first-person-2-main/textures/purple.png.import
new file mode 100644
index 0000000..9dc0969
--- /dev/null
+++ b/quality-godot-first-person-2-main/textures/purple.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cl4kewig3pk7s"
+path="res://.godot/imported/purple.png-23488e84f4f0a47488be2c78494f2155.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://textures/purple.png"
+dest_files=["res://.godot/imported/purple.png-23488e84f4f0a47488be2c78494f2155.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/quality-godot-first-person-2-main/textures/red.png b/quality-godot-first-person-2-main/textures/red.png
new file mode 100644
index 0000000..bf1cb17
Binary files /dev/null and b/quality-godot-first-person-2-main/textures/red.png differ
diff --git a/quality-godot-first-person-2-main/textures/red.png.import b/quality-godot-first-person-2-main/textures/red.png.import
new file mode 100644
index 0000000..c8c15aa
--- /dev/null
+++ b/quality-godot-first-person-2-main/textures/red.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1h161t0v6hau"
+path="res://.godot/imported/red.png-3cad0ca19141406d60f5fd2311159a86.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://textures/red.png"
+dest_files=["res://.godot/imported/red.png-3cad0ca19141406d60f5fd2311159a86.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/steam_appid.txt b/steam_appid.txt
new file mode 100644
index 0000000..36e0826
--- /dev/null
+++ b/steam_appid.txt
@@ -0,0 +1 @@
+480