diff --git a/.gitea/workflows/deploy-inventory.yml b/.gitea/workflows/deploy-inventory.yml new file mode 100644 index 0000000..24e370d --- /dev/null +++ b/.gitea/workflows/deploy-inventory.yml @@ -0,0 +1,112 @@ +name: Deploy Promiscuity Inventory API + +on: + push: + branches: + - main + workflow_dispatch: {} + +jobs: + deploy: + runs-on: self-hosted + + env: + IMAGE_NAME: promiscuity-inventory:latest + IMAGE_TAR: /tmp/promiscuity-inventory.tar + # All nodes that might run the pod (control-plane + workers) + NODES: "192.168.86.72 192.168.86.73 192.168.86.74" + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + # ----------------------------- + # Build Docker image + # ----------------------------- + - name: Build Docker image + run: | + cd microservices/InventoryApi + docker build -t "${IMAGE_NAME}" . + + # ----------------------------- + # Save image as TAR on runner + # ----------------------------- + - name: Save Docker image to TAR + run: | + docker save "${IMAGE_NAME}" -o "${IMAGE_TAR}" + + # ----------------------------- + # Copy TAR to each Kubernetes node + # ----------------------------- + - name: Copy TAR to nodes + run: | + for node in ${NODES}; do + echo "Copying image tar to $node ..." + scp -o StrictHostKeyChecking=no "${IMAGE_TAR}" hz@"$node":/tmp/promiscuity-inventory.tar + done + + # ----------------------------- + # Import image into containerd on each node + # ----------------------------- + - name: Import image on nodes + run: | + for node in ${NODES}; do + echo "Importing image on $node ..." + ssh -o StrictHostKeyChecking=no hz@"$node" "sudo ctr -n k8s.io images import /tmp/promiscuity-inventory.tar" + done + + # ----------------------------- + # CLEANUP: delete TAR from nodes + # ----------------------------- + - name: Clean TAR from nodes + run: | + for node in ${NODES}; do + echo "Removing image tar on $node ..." + ssh -o StrictHostKeyChecking=no hz@"$node" "rm -f /tmp/promiscuity-inventory.tar" + done + + # ----------------------------- + # CLEANUP: delete TAR from runner + # ----------------------------- + - name: Clean TAR on runner + run: | + rm -f "${IMAGE_TAR}" + + # ----------------------------- + # Write kubeconfig from secret + # ----------------------------- + - name: Write kubeconfig from secret + env: + KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }} + run: | + mkdir -p /tmp/kube + printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config + + # ----------------------------- + # Ensure namespace exists + # ----------------------------- + - name: Create namespace if missing + env: + KUBECONFIG: /tmp/kube/config + run: | + kubectl create namespace promiscuity-inventory --dry-run=client -o yaml | kubectl apply -f - + + # ----------------------------- + # Apply Kubernetes manifests + # ----------------------------- + - name: Apply Inventory deployment & service + env: + KUBECONFIG: /tmp/kube/config + run: | + kubectl apply -f microservices/InventoryApi/k8s/deployment.yaml -n promiscuity-inventory + kubectl apply -f microservices/InventoryApi/k8s/service.yaml -n promiscuity-inventory + + # ----------------------------- + # Rollout restart & wait + # ----------------------------- + - name: Restart Inventory deployment + env: + KUBECONFIG: /tmp/kube/config + run: | + kubectl rollout restart deployment/promiscuity-inventory -n promiscuity-inventory + kubectl rollout status deployment/promiscuity-inventory -n promiscuity-inventory diff --git a/game/assets/materials/kenney_prototype_block_dark.tres b/game/assets/materials/kenney_prototype_block_dark.tres new file mode 100644 index 0000000..6ffbb83 --- /dev/null +++ b/game/assets/materials/kenney_prototype_block_dark.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3] + +[ext_resource type="Texture2D" path="res://assets/textures/kenney_prototype/PNG/Dark/texture_01.png" id="1_block"] + +[resource] +albedo_texture = ExtResource("1_block") +uv1_triplanar = true +uv1_scale = Vector3(1, 1, 1) diff --git a/game/assets/materials/kenney_prototype_gateway_orange.tres b/game/assets/materials/kenney_prototype_gateway_orange.tres new file mode 100644 index 0000000..f2caac3 --- /dev/null +++ b/game/assets/materials/kenney_prototype_gateway_orange.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3] + +[ext_resource type="Texture2D" path="res://assets/textures/kenney_prototype/PNG/Orange/texture_08.png" id="1_gateway"] + +[resource] +albedo_texture = ExtResource("1_gateway") +uv1_triplanar = true +uv1_scale = Vector3(1, 1, 1) diff --git a/game/assets/materials/kenney_prototype_ground_green.tres b/game/assets/materials/kenney_prototype_ground_green.tres new file mode 100644 index 0000000..06090cc --- /dev/null +++ b/game/assets/materials/kenney_prototype_ground_green.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3] + +[ext_resource type="Texture2D" path="res://assets/textures/kenney_prototype/PNG/Green/texture_01.png" id="1_ground"] + +[resource] +albedo_texture = ExtResource("1_ground") +uv1_triplanar = true +uv1_scale = Vector3(0.25, 0.25, 0.25) diff --git a/game/assets/materials/kenney_prototype_prop_red.tres b/game/assets/materials/kenney_prototype_prop_red.tres new file mode 100644 index 0000000..cfff784 --- /dev/null +++ b/game/assets/materials/kenney_prototype_prop_red.tres @@ -0,0 +1,8 @@ +[gd_resource type="StandardMaterial3D" load_steps=2 format=3] + +[ext_resource type="Texture2D" path="res://assets/textures/kenney_prototype/PNG/Red/texture_01.png" id="1_prop"] + +[resource] +albedo_texture = ExtResource("1_prop") +uv1_triplanar = true +uv1_scale = Vector3(1, 1, 1) diff --git a/game/assets/textures/kenney_prototype/License.txt b/game/assets/textures/kenney_prototype/License.txt new file mode 100644 index 0000000..bda3f93 --- /dev/null +++ b/game/assets/textures/kenney_prototype/License.txt @@ -0,0 +1,23 @@ + + + Prototype Textures 1.0 + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 08-04-2020 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_01.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_01.png new file mode 100644 index 0000000..69be211 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_01.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_01.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_01.png.import new file mode 100644 index 0000000..b690b77 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_01.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8i4pqjyuvjtg" +path.s3tc="res://.godot/imported/texture_01.png-48911e4a61e64b91b096e3230a1d043c.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-48911e4a61e64b91b096e3230a1d043c.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_02.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_02.png new file mode 100644 index 0000000..6fb471b Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_02.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_02.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_02.png.import new file mode 100644 index 0000000..c0ed22c --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_02.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dads6gh1t660d" +path="res://.godot/imported/texture_02.png-960eceabe386ca9f851706002042b1c9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-960eceabe386ca9f851706002042b1c9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_03.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_03.png new file mode 100644 index 0000000..3f8b186 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_03.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_03.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_03.png.import new file mode 100644 index 0000000..844f217 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_03.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csxunghpwjxd2" +path="res://.godot/imported/texture_03.png-039c6543f305e252ad7acb009d0edcd6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-039c6543f305e252ad7acb009d0edcd6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_04.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_04.png new file mode 100644 index 0000000..e2bc22b Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_04.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_04.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_04.png.import new file mode 100644 index 0000000..a3c2691 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_04.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmby2f03vt8ek" +path="res://.godot/imported/texture_04.png-fe95fa0099eff4ffe433a81bff8f6494.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-fe95fa0099eff4ffe433a81bff8f6494.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_05.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_05.png new file mode 100644 index 0000000..3fd2e56 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_05.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_05.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_05.png.import new file mode 100644 index 0000000..43711b7 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_05.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djywrk2qmin8g" +path="res://.godot/imported/texture_05.png-58959b3cc62d5629180858c0f421ac90.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-58959b3cc62d5629180858c0f421ac90.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_06.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_06.png new file mode 100644 index 0000000..45d4a34 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_06.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_06.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_06.png.import new file mode 100644 index 0000000..e2a10e9 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_06.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://i8pevhcyh3lf" +path="res://.godot/imported/texture_06.png-6482d6b88e2ba98ab34cabcb2745ac1e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-6482d6b88e2ba98ab34cabcb2745ac1e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_07.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_07.png new file mode 100644 index 0000000..adf5e6f Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_07.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_07.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_07.png.import new file mode 100644 index 0000000..1c61f7c --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_07.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfpgo2ae3qc80" +path="res://.godot/imported/texture_07.png-807a615333ca9664a3684e233f80fa5d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-807a615333ca9664a3684e233f80fa5d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_08.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_08.png new file mode 100644 index 0000000..a5a9f24 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_08.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_08.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_08.png.import new file mode 100644 index 0000000..4fe4465 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_08.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ncixgg70mtah" +path="res://.godot/imported/texture_08.png-399dbb29d96119a8cc5a4ae628bce06d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-399dbb29d96119a8cc5a4ae628bce06d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_09.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_09.png new file mode 100644 index 0000000..57cc607 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_09.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_09.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_09.png.import new file mode 100644 index 0000000..1b92de2 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_09.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djh6n7ybqkdc1" +path="res://.godot/imported/texture_09.png-fab03b04ef6858a7d3cc969ba6b2df07.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-fab03b04ef6858a7d3cc969ba6b2df07.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_10.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_10.png new file mode 100644 index 0000000..4c737d0 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_10.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_10.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_10.png.import new file mode 100644 index 0000000..43d049f --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_10.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csyx7j6uhn7pj" +path="res://.godot/imported/texture_10.png-a0427e6988aab61871aedaf8b1dc17fe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-a0427e6988aab61871aedaf8b1dc17fe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_11.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_11.png new file mode 100644 index 0000000..424ba71 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_11.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_11.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_11.png.import new file mode 100644 index 0000000..7869120 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_11.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtufr2xr676oi" +path="res://.godot/imported/texture_11.png-704a8c7388b99eff391bad1145e5719f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-704a8c7388b99eff391bad1145e5719f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_12.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_12.png new file mode 100644 index 0000000..32169db Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_12.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_12.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_12.png.import new file mode 100644 index 0000000..b21e5b4 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_12.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjovin3jiwoxi" +path="res://.godot/imported/texture_12.png-8d1b0886e4e4d4bb83b9635e5b00be7d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-8d1b0886e4e4d4bb83b9635e5b00be7d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_13.png b/game/assets/textures/kenney_prototype/PNG/Dark/texture_13.png new file mode 100644 index 0000000..13c4388 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Dark/texture_13.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Dark/texture_13.png.import b/game/assets/textures/kenney_prototype/PNG/Dark/texture_13.png.import new file mode 100644 index 0000000..6d033e6 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Dark/texture_13.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6bmoir20ynik" +path="res://.godot/imported/texture_13.png-7698c24bc4ccaa000bc01ab906cd2e5f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Dark/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-7698c24bc4ccaa000bc01ab906cd2e5f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_01.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_01.png new file mode 100644 index 0000000..d576514 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_01.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_01.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_01.png.import new file mode 100644 index 0000000..a601dcf --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_01.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6ixhluav3j7r" +path.s3tc="res://.godot/imported/texture_01.png-89479c56308e7d4336c829b935d72f77.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-89479c56308e7d4336c829b935d72f77.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_02.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_02.png new file mode 100644 index 0000000..7bc7cf8 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_02.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_02.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_02.png.import new file mode 100644 index 0000000..2c95204 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_02.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwpfmh3kvlemk" +path="res://.godot/imported/texture_02.png-6a82345168fec8d21c2cf52543b07d63.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-6a82345168fec8d21c2cf52543b07d63.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_03.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_03.png new file mode 100644 index 0000000..e2a3889 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_03.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_03.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_03.png.import new file mode 100644 index 0000000..34fe8af --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_03.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxeer1osxqfhx" +path="res://.godot/imported/texture_03.png-ee34a765b74abf2ef9e67b4a70da62b5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-ee34a765b74abf2ef9e67b4a70da62b5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_04.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_04.png new file mode 100644 index 0000000..3952bef Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_04.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_04.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_04.png.import new file mode 100644 index 0000000..882075e --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_04.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbf3slco12d2p" +path="res://.godot/imported/texture_04.png-ed20497316f610b3d35ab67c79ba9928.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-ed20497316f610b3d35ab67c79ba9928.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_05.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_05.png new file mode 100644 index 0000000..84976ba Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_05.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_05.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_05.png.import new file mode 100644 index 0000000..f3fcf47 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_05.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fxpjus6cbowb" +path="res://.godot/imported/texture_05.png-c0468820fc54369295a4059072eb210b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-c0468820fc54369295a4059072eb210b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_06.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_06.png new file mode 100644 index 0000000..1128198 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_06.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_06.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_06.png.import new file mode 100644 index 0000000..6148842 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_06.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0rfol3r2d38d" +path="res://.godot/imported/texture_06.png-a9a520fb199450c2c8abe4c624e0a5b3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-a9a520fb199450c2c8abe4c624e0a5b3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_07.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_07.png new file mode 100644 index 0000000..3160860 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_07.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_07.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_07.png.import new file mode 100644 index 0000000..5bf66e3 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_07.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tnna4xkff51h" +path="res://.godot/imported/texture_07.png-d54a79d279a55bef5e5d0957c53a2032.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-d54a79d279a55bef5e5d0957c53a2032.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_08.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_08.png new file mode 100644 index 0000000..386293d Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_08.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_08.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_08.png.import new file mode 100644 index 0000000..cffee45 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_08.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsl3y63xqi1ck" +path="res://.godot/imported/texture_08.png-237c756eafffd5cc50b0172949f9e834.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-237c756eafffd5cc50b0172949f9e834.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_09.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_09.png new file mode 100644 index 0000000..48234f6 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_09.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_09.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_09.png.import new file mode 100644 index 0000000..d4d9da4 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_09.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cw354sq5klwmd" +path="res://.godot/imported/texture_09.png-1f5b7247f2456b24bace5d20fddde9c5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-1f5b7247f2456b24bace5d20fddde9c5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_10.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_10.png new file mode 100644 index 0000000..e4ab057 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_10.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_10.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_10.png.import new file mode 100644 index 0000000..5a9d456 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_10.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qpmiu0ubthx4" +path="res://.godot/imported/texture_10.png-d12a87bb602d176bef794b9bd568c8fc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-d12a87bb602d176bef794b9bd568c8fc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_11.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_11.png new file mode 100644 index 0000000..82ad458 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_11.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_11.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_11.png.import new file mode 100644 index 0000000..17ff8ea --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_11.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ce2gscdnwql6i" +path="res://.godot/imported/texture_11.png-1acc0733a994cb0b7388853de591ee94.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-1acc0733a994cb0b7388853de591ee94.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_12.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_12.png new file mode 100644 index 0000000..a15000d Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_12.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_12.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_12.png.import new file mode 100644 index 0000000..a974c21 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_12.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dc57gb74s3ip4" +path="res://.godot/imported/texture_12.png-b3cd9232f244006ce6aca462ad1452c6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-b3cd9232f244006ce6aca462ad1452c6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_13.png b/game/assets/textures/kenney_prototype/PNG/Green/texture_13.png new file mode 100644 index 0000000..930fdf0 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Green/texture_13.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Green/texture_13.png.import b/game/assets/textures/kenney_prototype/PNG/Green/texture_13.png.import new file mode 100644 index 0000000..fb5e973 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Green/texture_13.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dao8x3dcubt4t" +path="res://.godot/imported/texture_13.png-9c579c2792017ac2572adb06ed4be9d9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Green/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-9c579c2792017ac2572adb06ed4be9d9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_01.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_01.png new file mode 100644 index 0000000..60b632b Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_01.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_01.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_01.png.import new file mode 100644 index 0000000..5113ab1 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_01.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dolg1kgbvbc0i" +path="res://.godot/imported/texture_01.png-e7546efb61390ffcb487b954608b513f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-e7546efb61390ffcb487b954608b513f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_02.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_02.png new file mode 100644 index 0000000..19aad62 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_02.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_02.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_02.png.import new file mode 100644 index 0000000..7fc1c49 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_02.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dh358ryyyh5te" +path="res://.godot/imported/texture_02.png-0a03687d762dd0b17d41845950fd082e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-0a03687d762dd0b17d41845950fd082e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_03.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_03.png new file mode 100644 index 0000000..a8a6c06 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_03.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_03.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_03.png.import new file mode 100644 index 0000000..db46a3c --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_03.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnph3c4ag2euh" +path="res://.godot/imported/texture_03.png-d0f5a60e42bd37456d3c346266206ccd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-d0f5a60e42bd37456d3c346266206ccd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_04.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_04.png new file mode 100644 index 0000000..b8270e1 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_04.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_04.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_04.png.import new file mode 100644 index 0000000..b7b863a --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_04.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwadve5ugvngk" +path="res://.godot/imported/texture_04.png-a03c1a6379a93e4e97abb9a54fb864dc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-a03c1a6379a93e4e97abb9a54fb864dc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_05.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_05.png new file mode 100644 index 0000000..5b985bf Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_05.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_05.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_05.png.import new file mode 100644 index 0000000..2b7e28b --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_05.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kx7f2kenlsaa" +path="res://.godot/imported/texture_05.png-25f8455e8ed56fc6c1ad5b4a6e15befd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-25f8455e8ed56fc6c1ad5b4a6e15befd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_06.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_06.png new file mode 100644 index 0000000..195ad77 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_06.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_06.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_06.png.import new file mode 100644 index 0000000..bc1a6d5 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_06.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmrwqkfrgvl4x" +path="res://.godot/imported/texture_06.png-782c4b10026cbcd42040942dbeca2bb1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-782c4b10026cbcd42040942dbeca2bb1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_07.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_07.png new file mode 100644 index 0000000..fbf92e0 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_07.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_07.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_07.png.import new file mode 100644 index 0000000..6d55bbe --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_07.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://boyc8ex5h27n8" +path="res://.godot/imported/texture_07.png-7570284e3001f1960a1043e76fd88def.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-7570284e3001f1960a1043e76fd88def.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_08.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_08.png new file mode 100644 index 0000000..72e1a07 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_08.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_08.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_08.png.import new file mode 100644 index 0000000..0f3959f --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_08.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bueuw1b7pf8l6" +path="res://.godot/imported/texture_08.png-415ded57289e289f252a178fe471d729.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-415ded57289e289f252a178fe471d729.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_09.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_09.png new file mode 100644 index 0000000..9ada5dd Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_09.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_09.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_09.png.import new file mode 100644 index 0000000..98683e9 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_09.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jq60qtqu48fm" +path="res://.godot/imported/texture_09.png-61eb069b63a7e70cc28369bd1e62ea10.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-61eb069b63a7e70cc28369bd1e62ea10.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_10.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_10.png new file mode 100644 index 0000000..4fbcc80 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_10.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_10.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_10.png.import new file mode 100644 index 0000000..924da29 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_10.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c740qguwtwydx" +path="res://.godot/imported/texture_10.png-7f63eb22e8ac8d37530dd26472b94469.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-7f63eb22e8ac8d37530dd26472b94469.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_11.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_11.png new file mode 100644 index 0000000..840ecec Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_11.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_11.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_11.png.import new file mode 100644 index 0000000..7d5a498 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_11.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c67rolf0v538x" +path="res://.godot/imported/texture_11.png-0624e257a8e03b774111a153ebfc4233.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-0624e257a8e03b774111a153ebfc4233.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_12.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_12.png new file mode 100644 index 0000000..25c2e78 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_12.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_12.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_12.png.import new file mode 100644 index 0000000..b630f05 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_12.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kfcpptfsbuec" +path="res://.godot/imported/texture_12.png-81f5e392d901a56cbaf160fc5e856913.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-81f5e392d901a56cbaf160fc5e856913.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_13.png b/game/assets/textures/kenney_prototype/PNG/Light/texture_13.png new file mode 100644 index 0000000..a235965 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Light/texture_13.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Light/texture_13.png.import b/game/assets/textures/kenney_prototype/PNG/Light/texture_13.png.import new file mode 100644 index 0000000..d149267 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Light/texture_13.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tb4aifsnjgw2" +path="res://.godot/imported/texture_13.png-7f0c183e444e41ae4d0eabb58b6f909a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Light/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-7f0c183e444e41ae4d0eabb58b6f909a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_01.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_01.png new file mode 100644 index 0000000..4f5bf92 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_01.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_01.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_01.png.import new file mode 100644 index 0000000..4b94bc2 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_01.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0hpwwujblbf6" +path="res://.godot/imported/texture_01.png-fb420d45cb04a154afa72ffe12d81894.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-fb420d45cb04a154afa72ffe12d81894.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_02.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_02.png new file mode 100644 index 0000000..dec5b59 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_02.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_02.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_02.png.import new file mode 100644 index 0000000..aff26e1 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_02.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfs5l88ygbrfi" +path="res://.godot/imported/texture_02.png-492da03299e722dcf9c0137443e8d877.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-492da03299e722dcf9c0137443e8d877.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_03.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_03.png new file mode 100644 index 0000000..666197f Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_03.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_03.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_03.png.import new file mode 100644 index 0000000..ebd422a --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_03.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3i2v378bnbjm" +path="res://.godot/imported/texture_03.png-342b114778ba1eddf0f539473f7a3ce3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-342b114778ba1eddf0f539473f7a3ce3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_04.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_04.png new file mode 100644 index 0000000..23d6fc4 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_04.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_04.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_04.png.import new file mode 100644 index 0000000..70f3cc4 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_04.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqroo5l0ttgl8" +path="res://.godot/imported/texture_04.png-1a8e75f1dc8be63ae393a3ebe8f69539.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-1a8e75f1dc8be63ae393a3ebe8f69539.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_05.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_05.png new file mode 100644 index 0000000..1e0448a Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_05.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_05.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_05.png.import new file mode 100644 index 0000000..1514e81 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_05.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cuh5kv1kdwko2" +path="res://.godot/imported/texture_05.png-8bf071994dd168984fa51b4bb5e01eef.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-8bf071994dd168984fa51b4bb5e01eef.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_06.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_06.png new file mode 100644 index 0000000..5486825 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_06.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_06.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_06.png.import new file mode 100644 index 0000000..6354792 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_06.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://diohjhmc5jre6" +path="res://.godot/imported/texture_06.png-a043840f324cef1c1d8332b72657762d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-a043840f324cef1c1d8332b72657762d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_07.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_07.png new file mode 100644 index 0000000..95f2790 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_07.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_07.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_07.png.import new file mode 100644 index 0000000..01d30c0 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_07.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjykvmhkqx17w" +path="res://.godot/imported/texture_07.png-2bce0bbd83499d9b5052f3e8d137ca4e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-2bce0bbd83499d9b5052f3e8d137ca4e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_08.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_08.png new file mode 100644 index 0000000..5a500d9 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_08.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_08.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_08.png.import new file mode 100644 index 0000000..62fb5b9 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_08.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgeqtevr8mq0q" +path.s3tc="res://.godot/imported/texture_08.png-6def30fa8b996cc3afd9972a61b642e8.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-6def30fa8b996cc3afd9972a61b642e8.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_09.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_09.png new file mode 100644 index 0000000..adcfa33 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_09.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_09.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_09.png.import new file mode 100644 index 0000000..cc4e6e1 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_09.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlwh26lrrtxyu" +path="res://.godot/imported/texture_09.png-07b42476a439b9aee3016d8e212f7534.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-07b42476a439b9aee3016d8e212f7534.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_10.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_10.png new file mode 100644 index 0000000..aa227e5 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_10.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_10.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_10.png.import new file mode 100644 index 0000000..dac6730 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_10.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbtpv5s1up3ir" +path="res://.godot/imported/texture_10.png-ec0a9a256fbd60f1c174db650022982c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-ec0a9a256fbd60f1c174db650022982c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_11.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_11.png new file mode 100644 index 0000000..dc94567 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_11.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_11.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_11.png.import new file mode 100644 index 0000000..5e12552 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_11.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d04g85b80udnd" +path="res://.godot/imported/texture_11.png-acf1389cfd46786de7f0d1a3454e4d3a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-acf1389cfd46786de7f0d1a3454e4d3a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_12.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_12.png new file mode 100644 index 0000000..b730544 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_12.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_12.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_12.png.import new file mode 100644 index 0000000..6806f26 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_12.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bil6g5fcdofha" +path="res://.godot/imported/texture_12.png-dbd2ccd5022482214c76aa18350c92b8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-dbd2ccd5022482214c76aa18350c92b8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_13.png b/game/assets/textures/kenney_prototype/PNG/Orange/texture_13.png new file mode 100644 index 0000000..01f4aa8 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Orange/texture_13.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Orange/texture_13.png.import b/game/assets/textures/kenney_prototype/PNG/Orange/texture_13.png.import new file mode 100644 index 0000000..f93a327 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Orange/texture_13.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dphpr66n7cv13" +path="res://.godot/imported/texture_13.png-fa85c6a0c33ba49033d65d403af78b03.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Orange/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-fa85c6a0c33ba49033d65d403af78b03.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_01.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_01.png new file mode 100644 index 0000000..d501875 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_01.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_01.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_01.png.import new file mode 100644 index 0000000..a6229a7 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_01.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dolx5sa0cxuci" +path="res://.godot/imported/texture_01.png-2533652906fd6c188a64ea25d0158276.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-2533652906fd6c188a64ea25d0158276.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_02.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_02.png new file mode 100644 index 0000000..48a51c1 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_02.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_02.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_02.png.import new file mode 100644 index 0000000..f0d3230 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_02.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5ucfhxus7qhv" +path="res://.godot/imported/texture_02.png-c747129582448a69ce05670e2bbddea2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-c747129582448a69ce05670e2bbddea2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_03.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_03.png new file mode 100644 index 0000000..5f97f24 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_03.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_03.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_03.png.import new file mode 100644 index 0000000..4959c2e --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_03.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://w6oki3adihve" +path="res://.godot/imported/texture_03.png-567fae9d6dd4af34e6526c25b40b2020.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-567fae9d6dd4af34e6526c25b40b2020.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_04.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_04.png new file mode 100644 index 0000000..b81951c Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_04.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_04.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_04.png.import new file mode 100644 index 0000000..b4efa73 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_04.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdeu6y3m43hpj" +path="res://.godot/imported/texture_04.png-0cb6c6fb7215e630ffd369654a0115d0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-0cb6c6fb7215e630ffd369654a0115d0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_05.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_05.png new file mode 100644 index 0000000..52352c8 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_05.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_05.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_05.png.import new file mode 100644 index 0000000..b4e4a8a --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_05.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7ri11frsg7fc" +path="res://.godot/imported/texture_05.png-ef4a017243972413bd35ab4cde773c93.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-ef4a017243972413bd35ab4cde773c93.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_06.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_06.png new file mode 100644 index 0000000..86deeb3 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_06.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_06.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_06.png.import new file mode 100644 index 0000000..70d9793 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_06.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://da3n7ntfgyuuc" +path="res://.godot/imported/texture_06.png-dbd9b3c0cc103c99d7c97418eec406ec.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-dbd9b3c0cc103c99d7c97418eec406ec.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_07.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_07.png new file mode 100644 index 0000000..b71ce4e Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_07.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_07.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_07.png.import new file mode 100644 index 0000000..23eb9a1 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_07.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjjhenmemfa8f" +path="res://.godot/imported/texture_07.png-60422afc6ddacac994c30400cfe6db1f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-60422afc6ddacac994c30400cfe6db1f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_08.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_08.png new file mode 100644 index 0000000..470cc5c Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_08.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_08.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_08.png.import new file mode 100644 index 0000000..94d1649 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_08.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dh24lgsew55r2" +path="res://.godot/imported/texture_08.png-76485ebf48adfbd14b539454c181fcb4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-76485ebf48adfbd14b539454c181fcb4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_09.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_09.png new file mode 100644 index 0000000..4a3f689 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_09.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_09.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_09.png.import new file mode 100644 index 0000000..7720030 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_09.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhyx16rlos1d7" +path="res://.godot/imported/texture_09.png-7a2f4b0bb0fcee3211d49dbbf079ba2e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-7a2f4b0bb0fcee3211d49dbbf079ba2e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_10.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_10.png new file mode 100644 index 0000000..ab7e84b Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_10.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_10.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_10.png.import new file mode 100644 index 0000000..ef38e5d --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_10.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcsggsf33ue5x" +path="res://.godot/imported/texture_10.png-651d51fdf5cede652ae65f03d71c8335.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-651d51fdf5cede652ae65f03d71c8335.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_11.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_11.png new file mode 100644 index 0000000..ad7bebc Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_11.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_11.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_11.png.import new file mode 100644 index 0000000..c908a05 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_11.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c58lqvxgahxiv" +path="res://.godot/imported/texture_11.png-115b6e8468e65b6c0fc6a0bee7624fdc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-115b6e8468e65b6c0fc6a0bee7624fdc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_12.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_12.png new file mode 100644 index 0000000..979ac5a Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_12.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_12.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_12.png.import new file mode 100644 index 0000000..f0bd767 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_12.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3inljjn1dar8" +path="res://.godot/imported/texture_12.png-da0360b498f0cc20461ee7d37e76396f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-da0360b498f0cc20461ee7d37e76396f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_13.png b/game/assets/textures/kenney_prototype/PNG/Purple/texture_13.png new file mode 100644 index 0000000..2a9df22 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Purple/texture_13.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Purple/texture_13.png.import b/game/assets/textures/kenney_prototype/PNG/Purple/texture_13.png.import new file mode 100644 index 0000000..70aaa97 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Purple/texture_13.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddvcmbqq0ygpr" +path="res://.godot/imported/texture_13.png-5b08ea32d5979960b8a88aac58ab1e51.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Purple/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-5b08ea32d5979960b8a88aac58ab1e51.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_01.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_01.png new file mode 100644 index 0000000..1aaab41 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_01.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_01.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_01.png.import new file mode 100644 index 0000000..54180f0 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_01.png.import @@ -0,0 +1,41 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://obvqs5j7ktas" +path.s3tc="res://.godot/imported/texture_01.png-59f0738b795d1de18217b3c8dbde69c8.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-59f0738b795d1de18217b3c8dbde69c8.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_02.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_02.png new file mode 100644 index 0000000..bf1cb17 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_02.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_02.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_02.png.import new file mode 100644 index 0000000..fce5104 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_02.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wcyl2f5pkwr2" +path="res://.godot/imported/texture_02.png-2675158ec58674de8125e440cd805fdd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-2675158ec58674de8125e440cd805fdd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_03.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_03.png new file mode 100644 index 0000000..ff09c22 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_03.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_03.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_03.png.import new file mode 100644 index 0000000..4551133 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_03.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dybj163h6bb27" +path="res://.godot/imported/texture_03.png-5a870c4bbb35c520362d877c5128cb8e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-5a870c4bbb35c520362d877c5128cb8e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_04.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_04.png new file mode 100644 index 0000000..b5b77ff Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_04.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_04.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_04.png.import new file mode 100644 index 0000000..f862948 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_04.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcysuh5pf4a46" +path="res://.godot/imported/texture_04.png-097a57695a228b64e379ff06f4c97dfc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-097a57695a228b64e379ff06f4c97dfc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_05.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_05.png new file mode 100644 index 0000000..7827035 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_05.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_05.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_05.png.import new file mode 100644 index 0000000..dfe4840 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_05.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://by2o1oydwmorb" +path="res://.godot/imported/texture_05.png-53c0dfef02ec13aba7e455a457e8870a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-53c0dfef02ec13aba7e455a457e8870a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_06.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_06.png new file mode 100644 index 0000000..914a6f1 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_06.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_06.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_06.png.import new file mode 100644 index 0000000..b129245 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_06.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dv8yi804matr4" +path="res://.godot/imported/texture_06.png-58405bde1f5c3e8a5b8c94c4d9c0e280.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-58405bde1f5c3e8a5b8c94c4d9c0e280.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_07.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_07.png new file mode 100644 index 0000000..47d517b Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_07.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_07.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_07.png.import new file mode 100644 index 0000000..a1323d8 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_07.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bo0ddr681udpa" +path="res://.godot/imported/texture_07.png-5b6d528d43851780d61972577e71d746.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-5b6d528d43851780d61972577e71d746.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_08.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_08.png new file mode 100644 index 0000000..07cfc41 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_08.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_08.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_08.png.import new file mode 100644 index 0000000..bd34e48 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_08.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfck0428jqx8n" +path="res://.godot/imported/texture_08.png-50000f576c51dcb1fde6ea11a8619350.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-50000f576c51dcb1fde6ea11a8619350.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_09.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_09.png new file mode 100644 index 0000000..86d67d8 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_09.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_09.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_09.png.import new file mode 100644 index 0000000..32b72fa --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_09.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://372bh5fyek7y" +path="res://.godot/imported/texture_09.png-52d139b7ce0c8179d82c0a338adba45a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-52d139b7ce0c8179d82c0a338adba45a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_10.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_10.png new file mode 100644 index 0000000..a9266d1 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_10.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_10.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_10.png.import new file mode 100644 index 0000000..8d1f8ff --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_10.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vbxv0wpgyavo" +path="res://.godot/imported/texture_10.png-4af4040ef1a09b6dec402975d7048bcd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-4af4040ef1a09b6dec402975d7048bcd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_11.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_11.png new file mode 100644 index 0000000..2acc544 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_11.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_11.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_11.png.import new file mode 100644 index 0000000..d548cf8 --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_11.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://curyyr46mm17g" +path="res://.godot/imported/texture_11.png-2efd38aeed2a42bef39f9b7fe5558dc2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-2efd38aeed2a42bef39f9b7fe5558dc2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_12.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_12.png new file mode 100644 index 0000000..7054c4d Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_12.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_12.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_12.png.import new file mode 100644 index 0000000..6526cfe --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_12.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlsxiqd34kceq" +path="res://.godot/imported/texture_12.png-46f405e6a14076fad5024d6740c63b6c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-46f405e6a14076fad5024d6740c63b6c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_13.png b/game/assets/textures/kenney_prototype/PNG/Red/texture_13.png new file mode 100644 index 0000000..ab8c7b9 Binary files /dev/null and b/game/assets/textures/kenney_prototype/PNG/Red/texture_13.png differ diff --git a/game/assets/textures/kenney_prototype/PNG/Red/texture_13.png.import b/game/assets/textures/kenney_prototype/PNG/Red/texture_13.png.import new file mode 100644 index 0000000..7b46d9c --- /dev/null +++ b/game/assets/textures/kenney_prototype/PNG/Red/texture_13.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dejlhsj3a6lnb" +path="res://.godot/imported/texture_13.png-fd7cb86e05607e70b74988c38c00d4bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/kenney_prototype/PNG/Red/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-fd7cb86e05607e70b74988c38c00d4bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/scenes/Characters/location_player.gd b/game/scenes/Characters/location_player.gd new file mode 100644 index 0000000..36ad8df --- /dev/null +++ b/game/scenes/Characters/location_player.gd @@ -0,0 +1,249 @@ +extends RigidBody3D +# Initially I used a CharacterBody3D, however, I wanted the player to bounce off +# other objects in the environment and that would have required manual handling +# of collisions. So that's why we're using a RigidBody3D instead. +signal vehicle_entered(vehicle: Node) +signal vehicle_exited(vehicle: Node) + +const MOVE_SPEED := 8.0 +const SPRINT_MOVE_SPEED :=13 +const ACCELLERATION := 30.0 +const DECELLERATION := 40.0 +const JUMP_SPEED := 4.0 +const MAX_NUMBER_OF_JUMPS := 2 +const MIN_FOV := 10.0 +const MAX_FOV := 179.0 +const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1 +var mouse_sensitivity := 0.005 +var rotation_x := 0.0 +var rotation_y := 0.0 +var cameraMoveMode := false +var current_number_of_jumps := 0 +var _pending_mouse_delta := Vector2.ZERO +var _last_move_forward := Vector3(0, 0, 1) +var _last_move_right := Vector3(1, 0, 0) +var _camera_offset_local := Vector3.ZERO +var _camera_yaw := 0.0 +var _camera_pitch := 0.0 +var _in_vehicle := false +var _vehicle_collision_layer := 0 +var _vehicle_collision_mask := 0 +var _vehicle_original_parent: Node = null +var _light_was_on := false +var _jump_triggered := false +@onready var _flashlight: SpotLight3D = $SpotLight3D +@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer +@onready var _anim_tree: AnimationTree = find_child("AnimationTree", true, false) as AnimationTree +@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D + +@export var camera_follow_speed := 10.0 +@export var anim_idle_name := "Idle" +@export var anim_walk_name := "Walk" +@export var anim_jump_name := "Jump" +@export var anim_run_name := "Run" +@export var anim_walk_speed_threshold := 0.25 +@export var anim_sprint_speed_threshold := 10.0 + +var jump_sound = preload("res://assets/audio/jump.ogg") +var audio_player = AudioStreamPlayer.new() + +@export var camera_path: NodePath +@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null +@export var phone_path: NodePath +@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null +var phone_visible := false + +func _ready() -> void: + add_to_group("player") + if _anim_tree: + _anim_tree.active = false + axis_lock_angular_x = true + axis_lock_angular_z = true + angular_damp = 6.0 + contact_monitor = true + max_contacts_reported = 4 + add_child(audio_player) + audio_player.stream = jump_sound + audio_player.volume_db = -20 + if cam: + _camera_offset_local = cam.transform.origin + _camera_pitch = cam.rotation.x + _camera_yaw = global_transform.basis.get_euler().y + cam.set_as_top_level(true) + cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local) + cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) + var move_basis := cam.global_transform.basis if cam else global_transform.basis + var forward := move_basis.z + var right := move_basis.x + forward.y = 0.0 + right.y = 0.0 + if forward.length() > 0.0001: + _last_move_forward = forward.normalized() + if right.length() > 0.0001: + _last_move_right = right.normalized() + _vehicle_collision_layer = collision_layer + _vehicle_collision_mask = collision_mask + +func _integrate_forces(state): + if _in_vehicle: + linear_velocity = Vector3.ZERO + return + if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO: + rotation_x -= _pending_mouse_delta.y * mouse_sensitivity + rotation_y -= _pending_mouse_delta.x * mouse_sensitivity + rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90)) + _camera_pitch = rotation_x + rotation.y = rotation_y + _pending_mouse_delta = Vector2.ZERO + + var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") + var forward := Vector3.FORWARD * -1.0 + var right := Vector3.RIGHT + if cam: + forward = cam.global_transform.basis.z + right = cam.global_transform.basis.x + forward.y = 0.0 + right.y = 0.0 + if forward.length() > 0.0001: + forward = forward.normalized() + _last_move_forward = forward + else: + forward = _last_move_forward + if right.length() > 0.0001: + right = right.normalized() + _last_move_right = right + else: + right = _last_move_right + + var dir := (right * input2v.x + forward * input2v.y).normalized() + var target_v := dir * MOVE_SPEED + if Input.is_key_pressed(KEY_SHIFT): + target_v = dir * SPRINT_MOVE_SPEED + + var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION + linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step) + linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step) + + var on_floor = false + for i in state.get_contact_count(): + var normal = state.get_contact_local_normal(i) + if normal.y > 0.5: + on_floor = true + break + + if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1): + current_number_of_jumps = (current_number_of_jumps + 1) % 2 + linear_velocity.y = JUMP_SPEED + audio_player.play() + _jump_triggered = true + + if cam: + var target_yaw := global_transform.basis.get_euler().y + _camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step) + var target_basis := Basis(Vector3.UP, _camera_yaw) + var target_pos := global_position + (target_basis * _camera_offset_local) + cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step) + cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0) + + _update_animation(on_floor, state.linear_velocity) + _jump_triggered = false + +func _input(event): + if event.is_action_pressed("player_phone"): + phone_visible = !phone_visible + if phone: + phone.visible = phone_visible + return + + if _in_vehicle: + return + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_MIDDLE: + if event.pressed: + cameraMoveMode = true + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + else: + cameraMoveMode = false + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + + if event is InputEventMouseMotion and cameraMoveMode: + _pending_mouse_delta += event.relative + + if event is InputEventMouseButton and event.pressed: + if event.button_index == MOUSE_BUTTON_WHEEL_UP: + zoom_camera(1.0 / ZOOM_FACTOR) + elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: + zoom_camera(ZOOM_FACTOR) + + if event.is_action_pressed("player_light"): + _flashlight.visible = !_flashlight.visible + +func zoom_camera(factor): + if cam == null: + return + var new_fov = cam.fov * factor + cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV) + +func _update_animation(on_floor: bool, velocity: Vector3) -> void: + if _anim_player == null: + return + var horizontal_speed := Vector3(velocity.x, 0.0, velocity.z).length() + if _jump_triggered and _anim_player.has_animation(anim_jump_name): + if _anim_player.current_animation != anim_jump_name: + _anim_player.play(anim_jump_name) + return + if not on_floor and _anim_player.has_animation(anim_jump_name): + if _anim_player.current_animation != anim_jump_name: + _anim_player.play(anim_jump_name) + return + if on_floor and horizontal_speed > anim_sprint_speed_threshold and _anim_player.has_animation(anim_run_name): + if _anim_player.current_animation != anim_run_name: + _anim_player.play(anim_run_name) + return + if horizontal_speed > anim_walk_speed_threshold and _anim_player.has_animation(anim_walk_name): + if _anim_player.current_animation != anim_walk_name: + _anim_player.play(anim_walk_name) + return + if _anim_player.has_animation(anim_idle_name): + if _anim_player.current_animation != anim_idle_name: + _anim_player.play(anim_idle_name) + +func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void: + _in_vehicle = true + freeze = true + sleeping = true + collision_layer = 0 + collision_mask = 0 + _vehicle_original_parent = get_parent() + _light_was_on = _flashlight.visible + _flashlight.visible = false + if _model_root: + _model_root.visible = false + if seat: + reparent(seat, true) + global_transform = seat.global_transform + if cam: + cam.current = false + if vehicle_camera: + vehicle_camera.current = true + vehicle_entered.emit(_vehicle) + +func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void: + _in_vehicle = false + freeze = false + sleeping = false + collision_layer = _vehicle_collision_layer + collision_mask = _vehicle_collision_mask + if _vehicle_original_parent: + reparent(_vehicle_original_parent, true) + _vehicle_original_parent = null + _flashlight.visible = _light_was_on + if _model_root: + _model_root.visible = true + if exit_point: + global_transform = exit_point.global_transform + if vehicle_camera: + vehicle_camera.current = false + if cam: + cam.current = true + vehicle_exited.emit(null) diff --git a/game/scenes/Characters/location_player.gd.uid b/game/scenes/Characters/location_player.gd.uid new file mode 100644 index 0000000..df1190e --- /dev/null +++ b/game/scenes/Characters/location_player.gd.uid @@ -0,0 +1 @@ +uid://kgqaeqappow3 diff --git a/game/scenes/Characters/location_player.tscn b/game/scenes/Characters/location_player.tscn new file mode 100644 index 0000000..84e4288 --- /dev/null +++ b/game/scenes/Characters/location_player.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=4 format=3] + +[ext_resource type="Script" path="res://scenes/Characters/location_player.gd" id="1_player_script"] +[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="2_model"] + +[sub_resource type="SphereShape3D" id="SphereShape3D_player"] + +[node name="LocationPlayer" type="RigidBody3D"] +script = ExtResource("1_player_script") +camera_path = NodePath("Camera3D") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) +shape = SubResource("SphereShape3D_player") + +[node name="TestCharAnimated" parent="." instance=ExtResource("2_model")] +transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0) + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(0.9998477, 0, -0.017452406, 0.0066714617, 0.9238795, 0.38262552, 0.016124869, -0.38268343, 0.92373866, 0, 6, 10) +current = true +fov = 49.0 + +[node name="SpotLight3D" type="SpotLight3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.906308, -0.422618, 0, 0.422618, 0.906308, 0, 1.7, -0.35) +visible = false +spot_range = 30.0 +spot_angle = 25.0 diff --git a/game/scenes/Interaction/prototype_gateway.gd b/game/scenes/Interaction/prototype_gateway.gd new file mode 100644 index 0000000..c9fb601 --- /dev/null +++ b/game/scenes/Interaction/prototype_gateway.gd @@ -0,0 +1,31 @@ +@tool +extends Node3D + +@export_file("*.tscn") var target_scene_path := "res://scenes/Levels/transportation_level.tscn": + set(value): + target_scene_path = value + _sync_teleporter() + +@export var target_group: StringName = &"player": + set(value): + target_group = value + _sync_teleporter() + +@export var one_shot := true: + set(value): + one_shot = value + _sync_teleporter() + +@onready var teleporter: Area3D = $Teleporter + + +func _ready() -> void: + _sync_teleporter() + + +func _sync_teleporter() -> void: + if teleporter == null: + return + teleporter.set("target_scene_path", target_scene_path) + teleporter.set("target_group", target_group) + teleporter.set("one_shot", one_shot) diff --git a/game/scenes/Interaction/prototype_gateway.gd.uid b/game/scenes/Interaction/prototype_gateway.gd.uid new file mode 100644 index 0000000..5c8c348 --- /dev/null +++ b/game/scenes/Interaction/prototype_gateway.gd.uid @@ -0,0 +1 @@ +uid://1c0reto6vt6m diff --git a/game/scenes/Interaction/prototype_gateway.tscn b/game/scenes/Interaction/prototype_gateway.tscn new file mode 100644 index 0000000..e7e9be5 --- /dev/null +++ b/game/scenes/Interaction/prototype_gateway.tscn @@ -0,0 +1,117 @@ +[gd_scene load_steps=6 format=3] + +[ext_resource type="Script" path="res://scenes/Interaction/prototype_gateway.gd" id="1_gateway_script"] +[ext_resource type="PackedScene" path="res://scenes/Interaction/scene_teleporter.tscn" id="2_teleporter"] +[ext_resource type="Material" path="res://assets/materials/kenney_prototype_gateway_orange.tres" id="3_gateway_mat"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_gateway"] +size = Vector3(1, 1, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_gateway"] +material = ExtResource("3_gateway_mat") +size = Vector3(1, 1, 1) + +[node name="PrototypeGateway" type="Node3D"] +script = ExtResource("1_gateway_script") + +[node name="LeftBase" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftBase"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftBase"] +mesh = SubResource("BoxMesh_gateway") + +[node name="LeftMidLow" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 1.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftMidLow"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftMidLow"] +mesh = SubResource("BoxMesh_gateway") + +[node name="LeftMidHigh" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftMidHigh"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftMidHigh"] +mesh = SubResource("BoxMesh_gateway") + +[node name="LeftTop" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 3.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="LeftTop"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="LeftTop"] +mesh = SubResource("BoxMesh_gateway") + +[node name="RightBase" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RightBase"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="RightBase"] +mesh = SubResource("BoxMesh_gateway") + +[node name="RightMidLow" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RightMidLow"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="RightMidLow"] +mesh = SubResource("BoxMesh_gateway") + +[node name="RightMidHigh" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 2.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RightMidHigh"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="RightMidHigh"] +mesh = SubResource("BoxMesh_gateway") + +[node name="RightTop" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 3.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RightTop"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="RightTop"] +mesh = SubResource("BoxMesh_gateway") + +[node name="TopLeft" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 4.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="TopLeft"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="TopLeft"] +mesh = SubResource("BoxMesh_gateway") + +[node name="TopCenter" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="TopCenter"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="TopCenter"] +mesh = SubResource("BoxMesh_gateway") + +[node name="TopRight" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 4.5, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="TopRight"] +shape = SubResource("BoxShape3D_gateway") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="TopRight"] +mesh = SubResource("BoxMesh_gateway") + +[node name="Teleporter" parent="." instance=ExtResource("2_teleporter")] +transform = Transform3D(1.15, 0, 0, 0, 1.33, 0, 0, 0, 0.7, 0, 1.5, 0) diff --git a/game/scenes/Interaction/scene_teleporter.gd b/game/scenes/Interaction/scene_teleporter.gd new file mode 100644 index 0000000..692d43b --- /dev/null +++ b/game/scenes/Interaction/scene_teleporter.gd @@ -0,0 +1,39 @@ +extends Area3D + +@export_file("*.tscn") var target_scene_path := "res://scenes/Levels/transportation_level.tscn" +@export var target_group: StringName = &"player" +@export var one_shot := true + +var _is_transitioning := false + + +func _ready() -> void: + body_entered.connect(_on_body_entered) + + +func _on_body_entered(body: Node) -> void: + if _is_transitioning: + return + if target_group != StringName() and not body.is_in_group(target_group): + return + if target_scene_path.strip_edges() == "": + push_warning("Teleporter target scene is empty.") + return + if not ResourceLoader.exists(target_scene_path): + push_warning("Teleporter target scene does not exist: %s" % target_scene_path) + return + + _is_transitioning = true + if one_shot: + set_deferred("monitoring", false) + call_deferred("_deferred_change_scene") + + +func _deferred_change_scene() -> void: + var err := get_tree().change_scene_to_file(target_scene_path) + if err == OK: + return + push_warning("Failed to change scene to '%s' (%s)." % [target_scene_path, err]) + _is_transitioning = false + if one_shot: + set_deferred("monitoring", true) diff --git a/game/scenes/Interaction/scene_teleporter.gd.uid b/game/scenes/Interaction/scene_teleporter.gd.uid new file mode 100644 index 0000000..6f3537b --- /dev/null +++ b/game/scenes/Interaction/scene_teleporter.gd.uid @@ -0,0 +1 @@ +uid://dyvldjfan2beq diff --git a/game/scenes/Interaction/scene_teleporter.tscn b/game/scenes/Interaction/scene_teleporter.tscn new file mode 100644 index 0000000..8aa2de6 --- /dev/null +++ b/game/scenes/Interaction/scene_teleporter.tscn @@ -0,0 +1,32 @@ +[gd_scene load_steps=4 format=3] + +[ext_resource type="Script" path="res://scenes/Interaction/scene_teleporter.gd" id="1_tele"] + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_tele"] +height = 3.0 +radius = 1.5 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tele"] +transparency = 1 +shading_mode = 0 +albedo_color = Color(0.15, 0.95, 1, 0.25) +emission_enabled = true +emission = Color(0.1, 0.9, 1, 1) +emission_energy_multiplier = 1.5 + +[sub_resource type="CylinderMesh" id="CylinderMesh_tele"] +material = SubResource("StandardMaterial3D_tele") +top_radius = 1.6 +bottom_radius = 1.6 +height = 3.0 + +[node name="SceneTeleporter" type="Area3D"] +collision_layer = 2 +collision_mask = 1 +script = ExtResource("1_tele") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("CylinderShape3D_tele") + +[node name="Visual" type="MeshInstance3D" parent="."] +mesh = SubResource("CylinderMesh_tele") diff --git a/game/scenes/Levels/level.gd b/game/scenes/Levels/level.gd index 6d2b901..d3ebef6 100644 --- a/game/scenes/Levels/level.gd +++ b/game/scenes/Levels/level.gd @@ -13,10 +13,12 @@ var time := 0.0 @onready var _player: Node = $Player @onready var _quest_text: RichTextLabel = $PhoneUI/Control/PhoneFrame/QuestText -const FIRST_QUEST_ID := "first_drive" -const FIRST_QUEST := { - "id": FIRST_QUEST_ID, - "title": "RepoBot's First Task", +const FIRST_QUEST_ID := "first_drive" +const QUEST_PROMPT_META_PREFIX := "quest_intro_prompt_shown_" +const SPAWN_DIALOG_META_KEY := "level_spawn_dialog_shown" +const FIRST_QUEST := { + "id": FIRST_QUEST_ID, + "title": "RepoBot's First Task", "description": "Get familiar with movement and vehicles.", "steps": [ { @@ -32,16 +34,17 @@ const FIRST_QUEST := { ], } -func _ready() -> void: - _setup_quests() - if show_spawn_dialog and DialogSystem and DialogSystem.has_method("show_text"): - await get_tree().process_frame - DialogSystem.show_text(spawn_dialog_text) - if spawn_dialog_auto_close_seconds > 0.0: - await get_tree().create_timer(spawn_dialog_auto_close_seconds).timeout - if DialogSystem and DialogSystem.has_method("close_if_text"): - DialogSystem.close_if_text(spawn_dialog_text) - _show_quest_intro_dialog() +func _ready() -> void: + _setup_quests() + if _should_show_spawn_dialog() and DialogSystem and DialogSystem.has_method("show_text"): + await get_tree().process_frame + DialogSystem.show_text(spawn_dialog_text) + _mark_spawn_dialog_shown() + if spawn_dialog_auto_close_seconds > 0.0: + await get_tree().create_timer(spawn_dialog_auto_close_seconds).timeout + if DialogSystem and DialogSystem.has_method("close_if_text"): + DialogSystem.close_if_text(spawn_dialog_text) + _show_quest_intro_dialog() func _process(delta): time = fmod((time + delta), day_length) @@ -94,14 +97,36 @@ func _refresh_quest_ui() -> void: _quest_text.text = "[b]%s[/b]\nStep %d/%d\n%s" % [title, step_index + 1, total_steps, step_text] -func _show_quest_intro_dialog() -> void: - if QuestManager == null: - return - var state: Dictionary = QuestManager.get_active_quest_state() - if not bool(state.get("active", false)) or bool(state.get("completed", false)): - return - var step_text := String(state.get("current_step_text", "")) - if step_text.is_empty(): - return - if DialogSystem and DialogSystem.has_method("show_text"): - DialogSystem.show_text("RepoBot: New task assigned.\n\n%s" % step_text) +func _show_quest_intro_dialog() -> void: + if QuestManager == null: + return + var state: Dictionary = QuestManager.get_active_quest_state() + if not bool(state.get("active", false)) or bool(state.get("completed", false)): + return + var quest_id := String(state.get("quest_id", "")).strip_edges() + var step_id := String(state.get("current_step_id", "")).strip_edges() + var step_text := String(state.get("current_step_text", "")) + if quest_id.is_empty() or step_id.is_empty() or step_text.is_empty(): + return + var prompt_key := "%s%s_%s" % [QUEST_PROMPT_META_PREFIX, quest_id, step_id] + if QuestManager.has_meta(prompt_key) and bool(QuestManager.get_meta(prompt_key)): + return + if DialogSystem and DialogSystem.has_method("show_text"): + DialogSystem.show_text("RepoBot: New task assigned.\n\n%s" % step_text) + QuestManager.set_meta(prompt_key, true) + + +func _should_show_spawn_dialog() -> bool: + if not show_spawn_dialog: + return false + if QuestManager == null: + return true + if not QuestManager.has_meta(SPAWN_DIALOG_META_KEY): + return true + return not bool(QuestManager.get_meta(SPAWN_DIALOG_META_KEY)) + + +func _mark_spawn_dialog_shown() -> void: + if QuestManager == null: + return + QuestManager.set_meta(SPAWN_DIALOG_META_KEY, true) diff --git a/game/scenes/Levels/level.tscn b/game/scenes/Levels/level.tscn index e3ab767..4365ab2 100644 --- a/game/scenes/Levels/level.tscn +++ b/game/scenes/Levels/level.tscn @@ -10,6 +10,8 @@ [ext_resource type="PackedScene" uid="uid://bnqaqbgynoyys" path="res://assets/models/TestCharAnimated.glb" id="5_fi66n"] [ext_resource type="Script" uid="uid://bk53njt7i3kmv" path="res://scenes/Interaction/dialog_trigger_area.gd" id="6_dialog"] [ext_resource type="Script" uid="uid://cshtdpjp4xy2f" path="res://scenes/Quests/quest_trigger_area.gd" id="7_qtrigger"] +[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="8_teleporter"] +[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="9_ground_mat"] [ext_resource type="Shader" uid="uid://bi3o8elbtqoni" path="res://addons/simplegrasstextured/shaders/grass.gdshader" id="9_43ksg"] [ext_resource type="Texture2D" uid="uid://c4ggdp0kg5wjk" path="res://addons/simplegrasstextured/textures/grassbushcc008.png" id="10_loupo"] [ext_resource type="Script" uid="uid://2juaclm8gc1n" path="res://addons/simplegrasstextured/grass.gd" id="11_1meta"] @@ -276,6 +278,7 @@ height = 6.0 size = Vector3(1080, 2, 1080) [sub_resource type="BoxMesh" id="BoxMesh_w7c3h"] +material = ExtResource("9_ground_mat") size = Vector3(1080, 2, 1080) [sub_resource type="ShaderMaterial" id="ShaderMaterial_i35yb"] @@ -564,6 +567,10 @@ scroll_active = false [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_a4mo8") +[node name="LevelExitTeleporter" parent="." instance=ExtResource("8_teleporter")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.5, 0, 0) +target_scene_path = "res://scenes/Levels/transportation_level.tscn" + [connection signal="pressed" from="Menu/Control/VBoxContainer/ContinueButton" to="Menu" method="_on_continue_button_pressed"] [connection signal="pressed" from="Menu/Control/VBoxContainer/MainMenuButton" to="Menu" method="_on_main_menu_button_pressed"] [connection signal="pressed" from="Menu/Control/VBoxContainer/QuitButton" to="Menu" method="_on_quit_button_pressed"] diff --git a/game/scenes/Levels/location_level.gd b/game/scenes/Levels/location_level.gd index fa67264..48d01b2 100644 --- a/game/scenes/Levels/location_level.gd +++ b/game/scenes/Levels/location_level.gd @@ -1,15 +1,332 @@ extends Node3D -@export var tile_size := 4.0 +const CHARACTER_API_URL := "https://pchar.ranaze.com/api/Characters" + +@export var tile_size := 16.0 @export var block_height := 1.0 +@export_range(1, 8, 1) var tile_radius := 3 +@export var tracked_node_path: NodePath +@export var player_spawn_height := 2.0 +@export var border_color: Color = Color(0.05, 0.05, 0.05, 1.0) +@export var border_height_bias := 0.005 +@export var show_tile_labels := true +@export var tile_label_height := 0.01 +@export var tile_label_color: Color = Color(1, 1, 1, 1) @onready var _block: MeshInstance3D = $TerrainBlock -@onready var _camera: Camera3D = $Camera3D +@onready var _player: RigidBody3D = $Player +@onready var _camera: Camera3D = $Player/Camera3D + +var _center_coord := Vector2i.ZERO +var _tiles_root: Node3D +var _tracked_node: Node3D +var _tile_nodes: Dictionary = {} +var _camera_start_offset := Vector3(0.0, 6.0, 10.0) +var _border_material: StandardMaterial3D +var _known_locations: Dictionary = {} +var _locations_loaded := false +var _character_id := "" +var _persisted_coord := Vector2i.ZERO +var _coord_sync_in_flight := false +var _queued_coord_sync: Variant = null +var _locations_refresh_in_flight := false +var _queued_locations_refresh := false + func _ready() -> void: - var coord := SelectedCharacter.get_coord() - var block_pos := Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size) - _block.position = block_pos - _block.scale = Vector3(tile_size, block_height, tile_size) + _tiles_root = Node3D.new() + _tiles_root.name = "GeneratedTiles" + add_child(_tiles_root) + if _camera: - _camera.look_at(block_pos, Vector3.UP) + _camera_start_offset = _camera.position + + _tracked_node = get_node_or_null(tracked_node_path) as Node3D + if _tracked_node == null: + _tracked_node = _player + + var start_coord := SelectedCharacter.get_coord() + _center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y)) + _persisted_coord = _center_coord + _character_id = String(SelectedCharacter.character.get("id", SelectedCharacter.character.get("Id", ""))).strip_edges() + + _block.visible = false + await _load_existing_locations() + _ensure_selected_location_exists(_center_coord) + _rebuild_tiles(_center_coord) + _move_player_to_coord(_center_coord) + + +func _process(_delta: float) -> void: + if not _locations_loaded: + return + var target_world_pos := _get_stream_position() + var target_coord := _world_to_coord(target_world_pos) + if target_coord == _center_coord: + return + _center_coord = target_coord + _queue_coord_sync(_center_coord) + _queue_locations_refresh() + + +func _get_stream_position() -> Vector3: + if _tracked_node: + return _tracked_node.global_position + return _coord_to_world(_center_coord) + + +func _world_to_coord(world_pos: Vector3) -> Vector2i: + return Vector2i( + roundi(world_pos.x / tile_size), + roundi(world_pos.z / tile_size) + ) + + +func _coord_to_world(coord: Vector2i) -> Vector3: + return Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size) + + +func _move_player_to_coord(coord: Vector2i) -> void: + if _player == null: + return + _player.global_position = Vector3(coord.x * tile_size, player_spawn_height, coord.y * tile_size) + _player.linear_velocity = Vector3.ZERO + _player.angular_velocity = Vector3.ZERO + + +func _rebuild_tiles(center: Vector2i) -> void: + var wanted_keys: Dictionary = {} + for x in range(center.x - tile_radius, center.x + tile_radius + 1): + for y in range(center.y - tile_radius, center.y + tile_radius + 1): + var coord := Vector2i(x, y) + if not _known_locations.has(coord): + continue + wanted_keys[coord] = true + if _tile_nodes.has(coord): + continue + _spawn_tile(coord, String(_known_locations[coord])) + + for key in _tile_nodes.keys(): + if wanted_keys.has(key): + continue + var tile_node := _tile_nodes[key] as Node3D + if tile_node: + tile_node.queue_free() + _tile_nodes.erase(key) + + +func _spawn_tile(coord: Vector2i, location_name: String) -> void: + var tile_root := Node3D.new() + tile_root.name = "Tile_%d_%d" % [coord.x, coord.y] + tile_root.position = _coord_to_world(coord) + _tiles_root.add_child(tile_root) + + var tile_body := StaticBody3D.new() + tile_body.name = "TileBody" + tile_body.scale = Vector3(tile_size, block_height, tile_size) + tile_root.add_child(tile_body) + + var collision_shape := CollisionShape3D.new() + collision_shape.name = "CollisionShape3D" + collision_shape.shape = BoxShape3D.new() + tile_body.add_child(collision_shape) + + var tile := _block.duplicate() as MeshInstance3D + tile.name = "TileMesh" + tile.visible = true + tile_body.add_child(tile) + + tile.add_child(_create_tile_border()) + if show_tile_labels: + tile_root.add_child(_create_tile_label(location_name)) + + _tile_nodes[coord] = tile_root + + +func _create_tile_border() -> MeshInstance3D: + var top_y := 0.5 + border_height_bias + var corners := [ + Vector3(-0.5, top_y, -0.5), + Vector3(0.5, top_y, -0.5), + Vector3(0.5, top_y, 0.5), + Vector3(-0.5, top_y, 0.5), + ] + + var border_mesh := ImmediateMesh.new() + border_mesh.surface_begin(Mesh.PRIMITIVE_LINES, _get_border_material()) + for idx in range(corners.size()): + var current: Vector3 = corners[idx] + var next: Vector3 = corners[(idx + 1) % corners.size()] + border_mesh.surface_add_vertex(current) + border_mesh.surface_add_vertex(next) + border_mesh.surface_end() + + var border := MeshInstance3D.new() + border.name = "TileBorder" + border.mesh = border_mesh + return border + + +func _get_border_material() -> StandardMaterial3D: + if _border_material: + return _border_material + + _border_material = StandardMaterial3D.new() + _border_material.albedo_color = border_color + _border_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + _border_material.disable_receive_shadows = true + _border_material.no_depth_test = true + return _border_material + + +func _create_tile_label(location_name: String) -> Label3D: + var label := Label3D.new() + label.name = "LocationNameLabel" + label.text = location_name + label.position = Vector3(0.0, (block_height * 0.5) + border_height_bias + tile_label_height, 0.0) + label.rotation_degrees = Vector3(-90.0, 0.0, 0.0) + label.billboard = BaseMaterial3D.BILLBOARD_DISABLED + label.modulate = tile_label_color + label.pixel_size = 0.01 + label.outline_size = 12 + label.no_depth_test = false + return label + + +func _ensure_selected_location_exists(coord: Vector2i) -> void: + if _known_locations.has(coord): + return + _known_locations[coord] = _selected_location_name(coord) + + +func _selected_location_name(coord: Vector2i) -> String: + var selected_name := String(SelectedCharacter.character.get("locationName", "")).strip_edges() + if not selected_name.is_empty(): + return selected_name + var character_name := String(SelectedCharacter.character.get("name", "")).strip_edges() + if not character_name.is_empty(): + return "%s's Location" % character_name + return "Location %d,%d" % [coord.x, coord.y] + + +func _load_existing_locations() -> void: + _locations_refresh_in_flight = true + _locations_loaded = false + _known_locations.clear() + + if _character_id.is_empty(): + push_warning("Selected character is missing an id; cannot load visible locations.") + _locations_loaded = true + return + + var request := HTTPRequest.new() + add_child(request) + + var headers := PackedStringArray() + if not AuthState.access_token.is_empty(): + headers.append("Authorization: Bearer %s" % AuthState.access_token) + + var err := request.request("%s/%s/visible-locations" % [CHARACTER_API_URL, _character_id], headers, HTTPClient.METHOD_GET) + if err != OK: + push_warning("Failed to request visible locations: %s" % err) + request.queue_free() + _locations_loaded = true + return + + var result: Array = await request.request_completed + request.queue_free() + + var result_code: int = result[0] + var response_code: int = result[1] + var response_body: String = result[3].get_string_from_utf8() + if result_code != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300: + push_warning("Failed to load visible locations (%s/%s): %s" % [result_code, response_code, response_body]) + _locations_loaded = true + return + + var parsed: Variant = JSON.parse_string(response_body) + if typeof(parsed) != TYPE_ARRAY: + push_warning("Visible locations response was not an array.") + _locations_loaded = true + return + + var loaded_count := 0 + for item in parsed: + if typeof(item) != TYPE_DICTIONARY: + continue + var location := item as Dictionary + var coord_variant: Variant = location.get("coord", {}) + if typeof(coord_variant) != TYPE_DICTIONARY: + continue + var coord_dict := coord_variant as Dictionary + var coord := Vector2i(int(coord_dict.get("x", 0)), int(coord_dict.get("y", 0))) + var location_name := String(location.get("name", "")).strip_edges() + if location_name.is_empty(): + location_name = "Location %d,%d" % [coord.x, coord.y] + _known_locations[coord] = location_name + loaded_count += 1 + + print("LocationLevel loaded %d visible locations for character %s." % [loaded_count, _character_id]) + if loaded_count == 0: + push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id) + + _locations_loaded = true + _locations_refresh_in_flight = false + _rebuild_tiles(_center_coord) + if _queued_locations_refresh: + _queued_locations_refresh = false + _queue_locations_refresh() + + +func _queue_locations_refresh() -> void: + if _locations_refresh_in_flight: + _queued_locations_refresh = true + return + _refresh_visible_locations() + + +func _refresh_visible_locations() -> void: + if _character_id.is_empty(): + return + _refresh_visible_locations_async() + + +func _refresh_visible_locations_async() -> void: + await _load_existing_locations() + + +func _queue_coord_sync(coord: Vector2i) -> void: + if coord == _persisted_coord: + return + if _coord_sync_in_flight: + _queued_coord_sync = coord + return + _sync_character_coord(coord) + + +func _sync_character_coord(coord: Vector2i) -> void: + if _character_id.is_empty(): + return + _coord_sync_in_flight = true + _queued_coord_sync = null + _sync_character_coord_async(coord) + + +func _sync_character_coord_async(coord: Vector2i) -> void: + var response := await CharacterService.update_character_coord(_character_id, coord) + if response.get("ok", false): + _persisted_coord = coord + SelectedCharacter.set_coord(coord) + else: + push_warning("Failed to persist character coord to %s,%s: status=%s error=%s body=%s" % [ + coord.x, + coord.y, + response.get("status", "n/a"), + response.get("error", ""), + response.get("body", "") + ]) + + _coord_sync_in_flight = false + if _queued_coord_sync != null and _queued_coord_sync is Vector2i and _queued_coord_sync != _persisted_coord: + var queued_coord: Vector2i = _queued_coord_sync + _sync_character_coord(queued_coord) diff --git a/game/scenes/Levels/location_level.gd.uid b/game/scenes/Levels/location_level.gd.uid index 6befa8a..b39fb3a 100644 --- a/game/scenes/Levels/location_level.gd.uid +++ b/game/scenes/Levels/location_level.gd.uid @@ -1 +1 @@ -uid://1fico5npv6dy +uid://ctbyn1gws2ahj diff --git a/game/scenes/Levels/location_level.tscn b/game/scenes/Levels/location_level.tscn index c62e156..acba1dd 100644 --- a/game/scenes/Levels/location_level.tscn +++ b/game/scenes/Levels/location_level.tscn @@ -1,23 +1,32 @@ -[gd_scene load_steps=4 format=3 uid="uid://b7p7k1i4t0m2l"] +[gd_scene load_steps=8 format=3] -[ext_resource type="Script" path="res://scenes/Levels/location_level.gd" id="1_6y4q1"] +[ext_resource type="Script" path="res://scenes/Levels/location_level.gd" id="1_level_script"] +[ext_resource type="PackedScene" path="res://scenes/Characters/location_player.tscn" id="2_player_scene"] +[ext_resource type="Material" path="res://assets/materials/kenney_prototype_block_dark.tres" id="3_block_mat"] -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yu2x4"] -albedo_color = Color(0.2, 0.6, 0.2, 1) +[sub_resource type="BoxMesh" id="BoxMesh_tile"] +material = ExtResource("3_block_mat") +size = Vector3(1, 1, 1) -[sub_resource type="BoxMesh" id="BoxMesh_t2a5k"] -material = SubResource("StandardMaterial3D_yu2x4") +[sub_resource type="Environment" id="Environment_location"] +background_mode = 1 +background_color = Color(0.55, 0.72, 0.92, 1) +ambient_light_source = 2 +ambient_light_color = Color(1, 1, 1, 1) +ambient_light_energy = 0.8 [node name="LocationLevel" type="Node3D"] -script = ExtResource("1_6y4q1") +script = ExtResource("1_level_script") +tracked_node_path = NodePath("Player") [node name="TerrainBlock" type="MeshInstance3D" parent="."] -mesh = SubResource("BoxMesh_t2a5k") +mesh = SubResource("BoxMesh_tile") + +[node name="Player" parent="." instance=ExtResource("2_player_scene")] [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0) shadow_enabled = true -[node name="Camera3D" type="Camera3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 0.92388, 0.382683, 0, -0.382683, 0.92388, 0, 6, 10) -current = true +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_location") diff --git a/game/scenes/Levels/transportation_level.gd b/game/scenes/Levels/transportation_level.gd new file mode 100644 index 0000000..c6d89cd --- /dev/null +++ b/game/scenes/Levels/transportation_level.gd @@ -0,0 +1,37 @@ +extends Node3D + +@export var player_spawn_position := Vector3(0.0, 0.0, 0.0) +@export var day_length := 120.0 +@export var start_light_angle := -90.0 + +@onready var _player: RigidBody3D = get_node_or_null("Player") as RigidBody3D +@onready var _sun: DirectionalLight3D = $DirectionalLight3D + +var _time := 0.0 + + +func _ready() -> void: + _move_player_to_spawn() + + +func _process(delta: float) -> void: + _update_day_night(delta) + + +func _move_player_to_spawn() -> void: + if _player == null: + return + _player.global_position = player_spawn_position + _player.linear_velocity = Vector3.ZERO + _player.angular_velocity = Vector3.ZERO + + +func _update_day_night(delta: float) -> void: + if _sun == null or day_length <= 0.0: + return + _time = fmod(_time + delta, day_length) + var t: float = _time / day_length + var angle: float = lerp(start_light_angle, start_light_angle + 360.0, t) + _sun.rotation_degrees.x = angle + var energy_curve: float = -sin((t * TAU) + (start_light_angle * PI / 180.0)) + _sun.light_energy = clamp((energy_curve * 1.0) + 0.2, 0.0, 1.2) diff --git a/game/scenes/Levels/transportation_level.gd.uid b/game/scenes/Levels/transportation_level.gd.uid new file mode 100644 index 0000000..ecbc775 --- /dev/null +++ b/game/scenes/Levels/transportation_level.gd.uid @@ -0,0 +1 @@ +uid://c2vm651r4nepy diff --git a/game/scenes/Levels/transportation_level.tscn b/game/scenes/Levels/transportation_level.tscn new file mode 100644 index 0000000..d3fd0ef --- /dev/null +++ b/game/scenes/Levels/transportation_level.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=9 format=3 uid="uid://b7p7k1i4t0m2l"] + +[ext_resource type="Script" path="res://scenes/Levels/transportation_level.gd" id="1_6y4q1"] +[ext_resource type="Script" path="res://scenes/player.gd" id="2_player"] +[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="3_model"] +[ext_resource type="PackedScene" path="res://scenes/Interaction/prototype_gateway.tscn" id="4_teleporter"] +[ext_resource type="Material" path="res://assets/materials/kenney_prototype_ground_green.tres" id="5_ground_mat"] + +[sub_resource type="SphereShape3D" id="SphereShape3D_player"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_ground"] +size = Vector3(1080, 2, 1080) + +[sub_resource type="BoxMesh" id="BoxMesh_ground"] +material = ExtResource("5_ground_mat") +size = Vector3(1080, 2, 1080) + +[node name="TransportationLevel" type="Node3D"] +script = ExtResource("1_6y4q1") + +[node name="Ground" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Ground"] +shape = SubResource("BoxShape3D_ground") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00053596497, 0.0075991154, -0.0019865036) +mesh = SubResource("BoxMesh_ground") + +[node name="Player" type="RigidBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) +script = ExtResource("2_player") +camera_path = NodePath("Camera3D") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) +shape = SubResource("SphereShape3D_player") + +[node name="TestCharAnimated" parent="Player" instance=ExtResource("3_model")] +transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0) + +[node name="Camera3D" type="Camera3D" parent="Player"] +transform = Transform3D(0.9989785, -4.651856e-10, -0.045188628, 0.006969331, 0.9880354, 0.15407, 0.044647958, -0.15422754, 0.9870261, 0.22036135, 1.8988357, 0.64972365) +current = true +fov = 49.0 + +[node name="SpotLight3D" type="SpotLight3D" parent="Player"] +transform = Transform3D(1, 0, 0, 0, 0.906308, -0.422618, 0, 0.422618, 0.906308, 0, 1.7, -0.35) +visible = false +spot_range = 30.0 +spot_angle = 25.0 + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0) +shadow_enabled = true + +[node name="ReturnTeleporter" parent="." instance=ExtResource("4_teleporter")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 0, 0) +target_scene_path = "res://scenes/Levels/level.tscn" diff --git a/game/scenes/UI/character_service.gd b/game/scenes/UI/character_service.gd index 56a05e5..ea57882 100644 --- a/game/scenes/UI/character_service.gd +++ b/game/scenes/UI/character_service.gd @@ -11,9 +11,19 @@ func create_character(character_name: String) -> Dictionary: }) return await _request(HTTPClient.METHOD_POST, CHARACTER_API_URL, payload) -func delete_character(character_id: String) -> Dictionary: - var url := "%s/%s" % [CHARACTER_API_URL, character_id] - return await _request(HTTPClient.METHOD_DELETE, url) +func delete_character(character_id: String) -> Dictionary: + var url := "%s/%s" % [CHARACTER_API_URL, character_id] + return await _request(HTTPClient.METHOD_DELETE, url) + +func update_character_coord(character_id: String, coord: Vector2i) -> Dictionary: + var url := "%s/%s/coord" % [CHARACTER_API_URL, character_id] + var payload := JSON.stringify({ + "coord": { + "x": coord.x, + "y": coord.y + } + }) + return await _request(HTTPClient.METHOD_PUT, url, payload) func _request(method: int, url: String, body: String = "") -> Dictionary: var request := HTTPRequest.new() diff --git a/game/scenes/UI/login_screen.gd b/game/scenes/UI/login_screen.gd index 0d5f699..4008e41 100644 --- a/game/scenes/UI/login_screen.gd +++ b/game/scenes/UI/login_screen.gd @@ -2,16 +2,22 @@ extends Control const AUTH_LOGIN_URL := "https://pauth.ranaze.com/api/Auth/login" -@onready var _username_input: LineEdit = %UsernameInput -@onready var _password_input: LineEdit = %PasswordInput -@onready var _login_request: HTTPRequest = %LoginRequest -@onready var _error_label: Label = %ErrorLabel - -func _on_log_in_button_pressed() -> void: - var username := _username_input.text.strip_edges() - var password := _password_input.text - if username.is_empty() or password.is_empty(): - _show_error("Username and password required.") +@onready var _username_input: LineEdit = %UsernameInput +@onready var _password_input: LineEdit = %PasswordInput +@onready var _login_request: HTTPRequest = %LoginRequest +@onready var _error_label: Label = %ErrorLabel + +func _ready() -> void: + if not _username_input.is_connected("text_submitted", Callable(self, "_on_input_text_submitted")): + _username_input.text_submitted.connect(_on_input_text_submitted) + if not _password_input.is_connected("text_submitted", Callable(self, "_on_input_text_submitted")): + _password_input.text_submitted.connect(_on_input_text_submitted) + +func _on_log_in_button_pressed() -> void: + var username := _username_input.text.strip_edges() + var password := _password_input.text + if username.is_empty() or password.is_empty(): + _show_error("Username and password required.") return var payload := { @@ -44,5 +50,8 @@ func _on_login_request_completed(result: int, response_code: int, _headers: Pack func _on_back_button_pressed() -> void: get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn") -func _show_error(message: String) -> void: - _error_label.text = message +func _show_error(message: String) -> void: + _error_label.text = message + +func _on_input_text_submitted(_new_text: String) -> void: + _on_log_in_button_pressed() diff --git a/game/scenes/UI/selected_character.gd b/game/scenes/UI/selected_character.gd index 179ac30..5a8336e 100644 --- a/game/scenes/UI/selected_character.gd +++ b/game/scenes/UI/selected_character.gd @@ -14,3 +14,9 @@ func get_coord() -> Vector2: float(coord.get("x", 0)), float(coord.get("y", 0)) ) + +func set_coord(coord: Vector2i) -> void: + character["coord"] = { + "x": coord.x, + "y": coord.y + } diff --git a/game/scenes/Vehicles/car.tscn b/game/scenes/Vehicles/car.tscn index 9d1fd51..d22c84b 100644 --- a/game/scenes/Vehicles/car.tscn +++ b/game/scenes/Vehicles/car.tscn @@ -1,6 +1,7 @@ [gd_scene load_steps=6 format=3] [ext_resource type="Script" path="res://scenes/Vehicles/car.gd" id="1_kbd20"] +[ext_resource type="Material" path="res://assets/materials/kenney_prototype_prop_red.tres" id="2_car_mat"] [sub_resource type="BoxShape3D" id="BoxShape3D_7r1j6"] size = Vector3(1.4, 0.9, 2.6) @@ -11,9 +12,6 @@ size = Vector3(2.2, 2.0, 3.8) [sub_resource type="BoxMesh" id="BoxMesh_4y8xk"] size = Vector3(1.4, 0.9, 2.6) -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_red"] -albedo_color = Color(0.85, 0.1, 0.1, 1) - [node name="Car" type="RigidBody3D"] script = ExtResource("1_kbd20") seat_path = NodePath("Seat") @@ -26,7 +24,7 @@ shape = SubResource("BoxShape3D_7r1j6") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] mesh = SubResource("BoxMesh_4y8xk") -surface_material_override/0 = SubResource("StandardMaterial3D_red") +surface_material_override/0 = ExtResource("2_car_mat") [node name="InteractArea" type="Area3D" parent="."] diff --git a/game/scenes/block.tscn b/game/scenes/block.tscn index b96d3be..0f4c680 100644 --- a/game/scenes/block.tscn +++ b/game/scenes/block.tscn @@ -1,21 +1,20 @@ -[gd_scene load_steps=4 format=3 uid="uid://c5of6aaxop1hl"] - -[sub_resource type="BoxShape3D" id="BoxShape3D_4du60"] - -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_alp5v"] -albedo_color = Color(0.290196, 0.698039, 0.227451, 1) - -[sub_resource type="BoxMesh" id="BoxMesh_kryjk"] -material = SubResource("StandardMaterial3D_alp5v") - -[node name="Block" type="Node3D"] - -[node name="RigidBody3D" type="RigidBody3D" parent="."] -collision_layer = 3 -collision_mask = 3 - -[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D"] -shape = SubResource("BoxShape3D_4du60") - -[node name="MeshInstance3D" type="MeshInstance3D" parent="RigidBody3D"] -mesh = SubResource("BoxMesh_kryjk") +[gd_scene load_steps=4 format=3 uid="uid://c5of6aaxop1hl"] + +[ext_resource type="Material" path="res://assets/materials/kenney_prototype_block_dark.tres" id="1_block_mat"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_4du60"] + +[sub_resource type="BoxMesh" id="BoxMesh_kryjk"] +material = ExtResource("1_block_mat") + +[node name="Block" type="Node3D"] + +[node name="RigidBody3D" type="RigidBody3D" parent="."] +collision_layer = 3 +collision_mask = 3 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D"] +shape = SubResource("BoxShape3D_4du60") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="RigidBody3D"] +mesh = SubResource("BoxMesh_kryjk") diff --git a/game/scenes/player.gd b/game/scenes/player.gd index 01225c6..db880cf 100644 --- a/game/scenes/player.gd +++ b/game/scenes/player.gd @@ -11,8 +11,8 @@ const ACCELLERATION := 30.0 const DECELLERATION := 40.0 const JUMP_SPEED := 4.0 const MAX_NUMBER_OF_JUMPS := 2 -const MIN_FOV := 10 -const MAX_FOV := 180 +const MIN_FOV := 10.0 +const MAX_FOV := 179.0 const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1 var mouse_sensitivity := 0.005 var rotation_x := 0.0 @@ -187,6 +187,8 @@ func _input(event): _flashlight.visible = !_flashlight.visible func zoom_camera(factor): + if cam == null: + return var new_fov = cam.fov * factor cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV) diff --git a/microservices/CharacterApi/Controllers/CharactersController.cs b/microservices/CharacterApi/Controllers/CharactersController.cs index 060edc4..f9cc70a 100644 --- a/microservices/CharacterApi/Controllers/CharactersController.cs +++ b/microservices/CharacterApi/Controllers/CharactersController.cs @@ -8,21 +8,23 @@ namespace CharacterApi.Controllers; [ApiController] [Route("api/[controller]")] -public class CharactersController : ControllerBase -{ - private readonly CharacterStore _characters; - - public CharactersController(CharacterStore characters) - { - _characters = characters; - } +public class CharactersController : ControllerBase +{ + private readonly CharacterStore _characters; + private readonly ILogger _logger; + + public CharactersController(CharacterStore characters, ILogger logger) + { + _characters = characters; + _logger = logger; + } [HttpPost] [Authorize(Roles = "USER,SUPER")] - public async Task Create([FromBody] CreateCharacterRequest req) - { - if (string.IsNullOrWhiteSpace(req.Name)) - return BadRequest("Name required"); + public async Task Create([FromBody] CreateCharacterRequest req) + { + if (string.IsNullOrWhiteSpace(req.Name)) + return BadRequest("Name required"); var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrWhiteSpace(userId)) @@ -33,6 +35,7 @@ public class CharactersController : ControllerBase OwnerUserId = userId, Name = req.Name.Trim(), Coord = new Coord { X = 0, Y = 0 }, + VisionRadius = 3, CreatedUtc = DateTime.UtcNow }; @@ -42,19 +45,93 @@ public class CharactersController : ControllerBase [HttpGet] [Authorize(Roles = "USER,SUPER")] - public async Task ListMine() - { - var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - if (string.IsNullOrWhiteSpace(userId)) - return Unauthorized(); + public async Task ListMine() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); - var characters = await _characters.GetForOwnerAsync(userId); - return Ok(characters); - } - - [HttpDelete("{id}")] - [Authorize(Roles = "USER,SUPER")] - public async Task Delete(string id) + var characters = await _characters.GetForOwnerAsync(userId); + return Ok(characters); + } + + [HttpGet("{id}/visible-locations")] + [Authorize(Roles = "USER,SUPER")] + public async Task VisibleLocations(string id) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var allowAnyOwner = User.IsInRole("SUPER"); + var character = await _characters.GetByIdAsync(id); + if (character is null) + { + _logger.LogWarning("Visible locations request failed: character {CharacterId} was not found.", id); + return NotFound(); + } + + if (!allowAnyOwner && character.OwnerUserId != userId) + { + _logger.LogWarning( + "Visible locations request denied: character {CharacterId} belongs to owner {OwnerUserId}, request user was {UserId}", + id, + character.OwnerUserId, + userId + ); + return Forbid(); + } + + _logger.LogInformation( + "Visible locations requested for character {CharacterId} at ({X},{Y}) radius {VisionRadius} by user {UserId}", + character.Id, + character.Coord.X, + character.Coord.Y, + character.VisionRadius, + userId + ); + + var generation = await _characters.GetOrCreateVisibleLocationsAsync(character); + var locations = generation.Locations; + + _logger.LogInformation( + "Visible locations resolved for character {CharacterId}: generated {GeneratedCount}, returned {ReturnedCount}", + character.Id, + generation.GeneratedCount, + locations.Count + ); + + return Ok(locations); + } + + [HttpPut("{id}/coord")] + [Authorize(Roles = "USER,SUPER")] + public async Task UpdateCoord(string id, [FromBody] UpdateCharacterCoordRequest req) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + if (req.Coord is null) + return BadRequest("Coord required"); + + var allowAnyOwner = User.IsInRole("SUPER"); + var character = await _characters.GetByIdAsync(id); + if (character is null) + return NotFound(); + if (!allowAnyOwner && character.OwnerUserId != userId) + return Forbid(); + + character.Coord = req.Coord; + var updated = await _characters.UpdateCoordAsync(id, req.Coord); + if (!updated) + return NotFound(); + + return Ok(character); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "USER,SUPER")] + public async Task Delete(string id) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrWhiteSpace(userId)) diff --git a/microservices/CharacterApi/DOCUMENTS.md b/microservices/CharacterApi/DOCUMENTS.md index 55315cf..cf4733e 100644 --- a/microservices/CharacterApi/DOCUMENTS.md +++ b/microservices/CharacterApi/DOCUMENTS.md @@ -4,24 +4,50 @@ This service expects JSON request bodies for character creation and stores character documents in MongoDB. Inbound JSON documents -- CreateCharacterRequest (`POST /api/characters`) - ```json - { - "name": "string" - } - ``` +- CreateCharacterRequest (`POST /api/characters`) + ```json + { + "name": "string" + } + ``` +- UpdateCharacterCoordRequest (`PUT /api/characters/{id}/coord`) + ```json + { + "coord": { + "x": "number", + "y": "number" + } + } + ``` Stored documents (MongoDB) -- Character - ```json - { - "id": "string (ObjectId)", - "ownerUserId": "string", - "name": "string", - "coord": { - "x": "number", - "y": "number" - }, - "createdUtc": "string (ISO-8601 datetime)" - } - ``` +- Character + ```json + { + "id": "string (ObjectId)", + "ownerUserId": "string", + "name": "string", + "coord": { + "x": "number", + "y": "number" + }, + "visionRadius": "number", + "createdUtc": "string (ISO-8601 datetime)" + } + ``` + +Outbound JSON documents +- VisibleLocation (`GET /api/characters/{id}/visible-locations`) + ```json + [ + { + "id": "string (ObjectId)", + "name": "string", + "coord": { + "x": "number", + "y": "number" + } + } + ] + ``` + Missing locations within the character's vision window are created automatically before this response is returned. diff --git a/microservices/CharacterApi/Models/Character.cs b/microservices/CharacterApi/Models/Character.cs index d5ade03..2055e4a 100644 --- a/microservices/CharacterApi/Models/Character.cs +++ b/microservices/CharacterApi/Models/Character.cs @@ -15,5 +15,7 @@ public class Character public Coord Coord { get; set; } = new(); + public int VisionRadius { get; set; } = 3; + public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; } diff --git a/microservices/CharacterApi/Models/Coord.cs b/microservices/CharacterApi/Models/Coord.cs index d709ac7..d9d7544 100644 --- a/microservices/CharacterApi/Models/Coord.cs +++ b/microservices/CharacterApi/Models/Coord.cs @@ -1,8 +1,8 @@ -namespace CharacterApi.Models; - -public class Coord -{ - public int X { get; set; } - - public int Y { get; set; } -} +namespace CharacterApi.Models; + +public class Coord +{ + public int X { get; set; } + + public int Y { get; set; } +} diff --git a/microservices/CharacterApi/Models/LocationCoord.cs b/microservices/CharacterApi/Models/LocationCoord.cs new file mode 100644 index 0000000..7a58ef5 --- /dev/null +++ b/microservices/CharacterApi/Models/LocationCoord.cs @@ -0,0 +1,12 @@ +using MongoDB.Bson.Serialization.Attributes; + +namespace CharacterApi.Models; + +public class LocationCoord +{ + [BsonElement("x")] + public int X { get; set; } + + [BsonElement("y")] + public int Y { get; set; } +} diff --git a/microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs b/microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs new file mode 100644 index 0000000..b6d265a --- /dev/null +++ b/microservices/CharacterApi/Models/UpdateCharacterCoordRequest.cs @@ -0,0 +1,6 @@ +namespace CharacterApi.Models; + +public class UpdateCharacterCoordRequest +{ + public required Coord Coord { get; set; } +} diff --git a/microservices/CharacterApi/Models/VisibleLocation.cs b/microservices/CharacterApi/Models/VisibleLocation.cs new file mode 100644 index 0000000..5aff51d --- /dev/null +++ b/microservices/CharacterApi/Models/VisibleLocation.cs @@ -0,0 +1,18 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; + +namespace CharacterApi.Models; + +[BsonIgnoreExtraElements] +public class VisibleLocation +{ + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string? Id { get; set; } + + [BsonElement("name")] + public string Name { get; set; } = string.Empty; + + [BsonElement("coord")] + public LocationCoord Coord { get; set; } = new(); +} diff --git a/microservices/CharacterApi/Program.cs b/microservices/CharacterApi/Program.cs index bac4df0..7440d00 100644 --- a/microservices/CharacterApi/Program.cs +++ b/microservices/CharacterApi/Program.cs @@ -1,8 +1,9 @@ -using CharacterApi.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; -using System.Text; +using CharacterApi.Services; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); @@ -57,11 +58,45 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) builder.Services.AddAuthorization(); -var app = builder.Build(); - -app.MapGet("/healthz", () => Results.Ok("ok")); -app.UseSwagger(); -app.UseSwaggerUI(o => +var app = builder.Build(); + +app.UseExceptionHandler(errorApp => +{ + errorApp.Run(async context => + { + var feature = context.Features.Get(); + var exception = feature?.Error; + var logger = context.RequestServices.GetRequiredService().CreateLogger("GlobalException"); + var traceId = context.TraceIdentifier; + + if (exception is not null) + { + logger.LogError( + exception, + "Unhandled exception for {Method} {Path}. TraceId={TraceId}", + context.Request.Method, + context.Request.Path, + traceId + ); + } + + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + context.Response.ContentType = "application/problem+json"; + + await context.Response.WriteAsJsonAsync(new + { + type = "https://httpstatuses.com/500", + title = "Internal Server Error", + status = 500, + detail = exception?.Message ?? "An unexpected server error occurred.", + traceId + }); + }); +}); + +app.MapGet("/healthz", () => Results.Ok("ok")); +app.UseSwagger(); +app.UseSwaggerUI(o => { o.SwaggerEndpoint("/swagger/v1/swagger.json", "Character API v1"); o.RoutePrefix = "swagger"; diff --git a/microservices/CharacterApi/README.md b/microservices/CharacterApi/README.md index 4c9db47..bcbf8df 100644 --- a/microservices/CharacterApi/README.md +++ b/microservices/CharacterApi/README.md @@ -6,4 +6,6 @@ See `DOCUMENTS.md` for request payloads and stored document shapes. ## Endpoints - `POST /api/characters` Create a character. - `GET /api/characters` List characters for the current user. +- `PUT /api/characters/{id}/coord` Update the current location coord for an owned character. +- `GET /api/characters/{id}/visible-locations` Ensure and list locations visible to that owned character. - `DELETE /api/characters/{id}` Delete a character owned by the current user. diff --git a/microservices/CharacterApi/Services/CharacterStore.cs b/microservices/CharacterApi/Services/CharacterStore.cs index 433b438..4705e03 100644 --- a/microservices/CharacterApi/Services/CharacterStore.cs +++ b/microservices/CharacterApi/Services/CharacterStore.cs @@ -1,33 +1,190 @@ -using CharacterApi.Models; -using MongoDB.Driver; +using CharacterApi.Models; +using MongoDB.Bson; +using MongoDB.Driver; namespace CharacterApi.Services; -public class CharacterStore -{ - private readonly IMongoCollection _col; - - public CharacterStore(IConfiguration cfg) - { - var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; - var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb"; - var client = new MongoClient(cs); - var db = client.GetDatabase(dbName); - _col = db.GetCollection("Characters"); - - var ownerIndex = Builders.IndexKeys.Ascending(c => c.OwnerUserId); - _col.Indexes.CreateOne(new CreateIndexModel(ownerIndex)); - } +public class CharacterStore +{ + private readonly IMongoCollection _col; + private readonly IMongoCollection _locations; + private const string CoordIndexName = "coord_x_1_coord_y_1"; + + public sealed record VisibleLocationResult(List Locations, int GeneratedCount); + + public CharacterStore(IConfiguration cfg) + { + var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; + var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb"; + var client = new MongoClient(cs); + var db = client.GetDatabase(dbName); + _col = db.GetCollection("Characters"); + _locations = db.GetCollection("Locations"); + EnsureLocationCoordIndexes(); + + var ownerIndex = Builders.IndexKeys.Ascending(c => c.OwnerUserId); + _col.Indexes.CreateOne(new CreateIndexModel(ownerIndex)); + } public Task CreateAsync(Character character) => _col.InsertOneAsync(character); - public Task> GetForOwnerAsync(string ownerUserId) => - _col.Find(c => c.OwnerUserId == ownerUserId).ToListAsync(); - - public async Task DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner) - { - var filter = Builders.Filter.Eq(c => c.Id, id); - if (!allowAnyOwner) + public Task> GetForOwnerAsync(string ownerUserId) => + _col.Find(c => c.OwnerUserId == ownerUserId).ToListAsync(); + + public async Task GetByIdAsync(string id) => + await _col.Find(c => c.Id == id).FirstOrDefaultAsync(); + + public async Task UpdateCoordAsync(string id, Coord coord) + { + var filter = Builders.Filter.Eq(c => c.Id, id); + var update = Builders.Update.Set(c => c.Coord, coord); + var result = await _col.UpdateOneAsync(filter, update); + return result.ModifiedCount > 0 || result.MatchedCount > 0; + } + + public Task> GetVisibleLocationsAsync(Character character) + { + return GetVisibleLocationsInternalAsync(character, ensureGenerated: false); + } + + public async Task GetOrCreateVisibleLocationsAsync(Character character) + { + var generatedCount = await EnsureVisibleLocationsExistAsync(character); + var locations = await GetVisibleLocationsInternalAsync(character, ensureGenerated: true); + return new VisibleLocationResult(locations, generatedCount); + } + + private async Task> GetVisibleLocationsInternalAsync(Character character, bool ensureGenerated) + { + var radius = character.VisionRadius > 0 ? character.VisionRadius : 3; + var minX = character.Coord.X - radius; + var maxX = character.Coord.X + radius; + var minY = character.Coord.Y - radius; + var maxY = character.Coord.Y + radius; + + var filter = Builders.Filter.And( + Builders.Filter.Gte("coord.x", minX), + Builders.Filter.Lte("coord.x", maxX), + Builders.Filter.Gte("coord.y", minY), + Builders.Filter.Lte("coord.y", maxY) + ); + + var documents = await _locations.Find(filter).ToListAsync(); + return documents.Select(MapVisibleLocation).ToList(); + } + + private async Task EnsureVisibleLocationsExistAsync(Character character) + { + var radius = character.VisionRadius > 0 ? character.VisionRadius : 3; + var generatedCount = 0; + + for (var x = character.Coord.X - radius; x <= character.Coord.X + radius; x++) + { + for (var y = character.Coord.Y - radius; y <= character.Coord.Y + radius; y++) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("coord.x", x), + Builders.Filter.Eq("coord.y", y) + ); + + var update = Builders.Update + .SetOnInsert("_id", ObjectId.GenerateNewId()) + .SetOnInsert("name", DefaultLocationName(x, y)) + .SetOnInsert("coord", new BsonDocument + { + { "x", x }, + { "y", y } + }) + .SetOnInsert("createdUtc", DateTime.UtcNow); + + try + { + var result = await _locations.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + if (result.UpsertedId is not null) + generatedCount += 1; + } + catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + // Another request or service instance created it first. + } + } + } + + return generatedCount; + } + + private static string DefaultLocationName(int x, int y) + { + if (x == 0 && y == 0) + return "Origin"; + return $"Location {x},{y}"; + } + + private static VisibleLocation MapVisibleLocation(BsonDocument document) + { + var coord = document.GetValue("coord", new BsonDocument()).AsBsonDocument; + var idValue = document.GetValue("_id", BsonNull.Value); + string? id = null; + if (!idValue.IsBsonNull) + { + id = idValue.BsonType == BsonType.ObjectId + ? idValue.AsObjectId.ToString() + : idValue.ToString(); + } + + return new VisibleLocation + { + Id = id, + Name = document.GetValue("name", "").AsString, + Coord = new LocationCoord + { + X = coord.GetValue("x", 0).ToInt32(), + Y = coord.GetValue("y", 0).ToInt32() + } + }; + } + + private void EnsureLocationCoordIndexes() + { + var indexes = _locations.Indexes.List().ToList(); + foreach (var index in indexes) + { + var name = index.GetValue("name", "").AsString; + if (name == "_id_") + continue; + + var keyDoc = index.GetValue("key", new BsonDocument()).AsBsonDocument; + if (IsLegacyCoordIndex(keyDoc) || IsUnexpectedCoordIndex(keyDoc)) + _locations.Indexes.DropOne(name); + } + + var coordIndex = new BsonDocument + { + { "coord.x", 1 }, + { "coord.y", 1 } + }; + var coordIndexOptions = new CreateIndexOptions { Unique = true, Name = CoordIndexName }; + _locations.Indexes.CreateOne(new CreateIndexModel(coordIndex, coordIndexOptions)); + } + + private static bool IsLegacyCoordIndex(BsonDocument keyDoc) => + keyDoc.ElementCount == 2 && + keyDoc.TryGetValue("Coord.X", out var xValue) && + xValue.IsInt32 && xValue.AsInt32 == 1 && + keyDoc.TryGetValue("Coord.Y", out var yValue) && + yValue.IsInt32 && yValue.AsInt32 == 1; + + private static bool IsUnexpectedCoordIndex(BsonDocument keyDoc) + { + var hasLower = keyDoc.Contains("coord.x") || keyDoc.Contains("coord.y"); + var hasUpper = keyDoc.Contains("Coord.X") || keyDoc.Contains("Coord.Y"); + return hasUpper || (hasLower && !(keyDoc.Contains("coord.x") && keyDoc.Contains("coord.y"))); + } + + public async Task DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner) + { + var filter = Builders.Filter.Eq(c => c.Id, id); + if (!allowAnyOwner) { filter = Builders.Filter.And( filter, diff --git a/microservices/InventoryApi/Controllers/InventoryController.cs b/microservices/InventoryApi/Controllers/InventoryController.cs new file mode 100644 index 0000000..7dd51c9 --- /dev/null +++ b/microservices/InventoryApi/Controllers/InventoryController.cs @@ -0,0 +1,309 @@ +using InventoryApi.Models; +using InventoryApi.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace InventoryApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class InventoryController : ControllerBase +{ + private readonly InventoryStore _inventory; + + public InventoryController(InventoryStore inventory) + { + _inventory = inventory; + } + + [HttpGet("item-definitions")] + [Authorize(Roles = "USER,SUPER")] + public async Task ListItemDefinitions() + { + var definitions = await _inventory.ListItemDefinitionsAsync(); + return Ok(definitions.Select(ItemDefinitionResponse.FromModel).ToList()); + } + + [HttpGet("item-definitions/{itemKey}")] + [Authorize(Roles = "USER,SUPER")] + public async Task GetItemDefinition(string itemKey) + { + var definition = await _inventory.GetItemDefinitionAsync(itemKey); + if (definition is null) + return NotFound(); + + return Ok(ItemDefinitionResponse.FromModel(definition)); + } + + [HttpPost("item-definitions")] + [Authorize(Roles = "SUPER")] + public async Task CreateItemDefinition([FromBody] CreateItemDefinitionRequest req) + { + if (string.IsNullOrWhiteSpace(req.ItemKey)) + return BadRequest("itemKey required"); + if (string.IsNullOrWhiteSpace(req.DisplayName)) + return BadRequest("displayName required"); + if (req.MaxStackSize <= 0) + return BadRequest("maxStackSize must be greater than 0"); + if (!req.Stackable && req.MaxStackSize != 1) + return BadRequest("Non-stackable items must have maxStackSize of 1"); + + var created = await _inventory.CreateItemDefinitionAsync(req); + if (created is null) + return Conflict("Item definition already exists"); + + return Ok(ItemDefinitionResponse.FromModel(created)); + } + + [HttpPut("item-definitions/{itemKey}")] + [Authorize(Roles = "SUPER")] + public async Task UpdateItemDefinition(string itemKey, [FromBody] UpdateItemDefinitionRequest req) + { + if (string.IsNullOrWhiteSpace(req.DisplayName)) + return BadRequest("displayName required"); + if (req.MaxStackSize <= 0) + return BadRequest("maxStackSize must be greater than 0"); + if (!req.Stackable && req.MaxStackSize != 1) + return BadRequest("Non-stackable items must have maxStackSize of 1"); + + var updated = await _inventory.UpdateItemDefinitionAsync(itemKey, req); + if (updated is null) + return NotFound(); + + return Ok(ItemDefinitionResponse.FromModel(updated)); + } + + [HttpGet("by-owner/{ownerType}/{ownerId}")] + [Authorize(Roles = "USER,SUPER")] + public async Task GetByOwner(string ownerType, string ownerId) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var access = await _inventory.ResolveOwnerAsync(ownerType, ownerId, userId, User.IsInRole("SUPER")); + if (!access.IsSupported) + return BadRequest("Unsupported ownerType"); + if (!access.Exists) + return NotFound(); + if (!access.IsAuthorized) + return Forbid(); + + var items = await _inventory.GetByOwnerAsync(access.OwnerType, access.OwnerId); + return Ok(new InventoryOwnerResponse + { + OwnerType = access.OwnerType, + OwnerId = access.OwnerId, + Items = items.Select(InventoryItemResponse.FromModel).ToList() + }); + } + + [HttpPost("by-owner/{ownerType}/{ownerId}/grant")] + [Authorize(Roles = "USER,SUPER")] + public async Task Grant(string ownerType, string ownerId, [FromBody] GrantInventoryItemRequest req) + { + if (string.IsNullOrWhiteSpace(req.ItemKey)) + return BadRequest("itemKey required"); + if (req.Quantity <= 0) + return BadRequest("quantity must be greater than 0"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var access = await _inventory.ResolveOwnerAsync(ownerType, ownerId, userId, User.IsInRole("SUPER")); + if (!access.IsSupported) + return BadRequest("Unsupported ownerType"); + if (!access.Exists) + return NotFound(); + if (!access.IsAuthorized) + return Forbid(); + + var definition = await _inventory.GetItemDefinitionAsync(req.ItemKey); + if (definition is null) + return BadRequest("Unknown itemKey"); + + var items = await _inventory.GrantAsync(access, req, definition); + return Ok(new InventoryOwnerResponse + { + OwnerType = access.OwnerType, + OwnerId = access.OwnerId, + Items = items.Select(InventoryItemResponse.FromModel).ToList() + }); + } + + [HttpPost("by-owner/{ownerType}/{ownerId}/move")] + [Authorize(Roles = "USER,SUPER")] + public async Task Move(string ownerType, string ownerId, [FromBody] MoveInventoryItemRequest req) + { + if (string.IsNullOrWhiteSpace(req.ItemId)) + return BadRequest("itemId required"); + if (req.ToSlot < 0) + return BadRequest("toSlot must be >= 0"); + if (req.Quantity is <= 0) + return BadRequest("quantity must be greater than 0"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var access = await _inventory.ResolveOwnerAsync(ownerType, ownerId, userId, User.IsInRole("SUPER")); + if (!access.IsSupported) + return BadRequest("Unsupported ownerType"); + if (!access.Exists) + return NotFound(); + if (!access.IsAuthorized) + return Forbid(); + + var result = await _inventory.MoveAsync(access, req); + return result.Status switch + { + InventoryMutationStatus.ItemNotFound => NotFound(), + InventoryMutationStatus.Invalid => BadRequest("Invalid move"), + InventoryMutationStatus.Conflict => Conflict("Target slot is not available"), + _ => Ok(new InventoryOwnerResponse + { + OwnerType = access.OwnerType, + OwnerId = access.OwnerId, + Items = result.Items.Select(InventoryItemResponse.FromModel).ToList() + }) + }; + } + + [HttpPost("transfer")] + [Authorize(Roles = "USER,SUPER")] + public async Task Transfer([FromBody] TransferInventoryItemRequest req) + { + if (string.IsNullOrWhiteSpace(req.ItemId)) + return BadRequest("itemId required"); + if (string.IsNullOrWhiteSpace(req.FromOwnerType) || string.IsNullOrWhiteSpace(req.FromOwnerId)) + return BadRequest("from owner required"); + if (string.IsNullOrWhiteSpace(req.ToOwnerType) || string.IsNullOrWhiteSpace(req.ToOwnerId)) + return BadRequest("to owner required"); + if (req.ToSlot is < 0) + return BadRequest("toSlot must be >= 0"); + if (req.Quantity is <= 0) + return BadRequest("quantity must be greater than 0"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var fromAccess = await _inventory.ResolveOwnerAsync(req.FromOwnerType, req.FromOwnerId, userId, User.IsInRole("SUPER")); + if (!fromAccess.IsSupported) + return BadRequest("Unsupported fromOwnerType"); + if (!fromAccess.Exists) + return NotFound("Source owner not found"); + if (!fromAccess.IsAuthorized) + return Forbid(); + + var toAccess = await _inventory.ResolveOwnerAsync(req.ToOwnerType, req.ToOwnerId, userId, User.IsInRole("SUPER")); + if (!toAccess.IsSupported) + return BadRequest("Unsupported toOwnerType"); + if (!toAccess.Exists) + return NotFound("Target owner not found"); + if (!toAccess.IsAuthorized) + return Forbid(); + + var result = await _inventory.TransferAsync(fromAccess, toAccess, req); + return result.Status switch + { + InventoryMutationStatus.ItemNotFound => NotFound(), + InventoryMutationStatus.Invalid => BadRequest("Invalid transfer"), + InventoryMutationStatus.Conflict => Conflict("Target slot is not available"), + _ => Ok(new TransferInventoryResponse + { + MovedItemId = req.ItemId, + FromOwnerType = fromAccess.OwnerType, + FromOwnerId = fromAccess.OwnerId, + ToOwnerType = toAccess.OwnerType, + ToOwnerId = toAccess.OwnerId, + FromItems = result.Items.Select(InventoryItemResponse.FromModel).Where(x => x.OwnerType == fromAccess.OwnerType && x.OwnerId == fromAccess.OwnerId).ToList(), + ToItems = result.Items.Select(InventoryItemResponse.FromModel).Where(x => x.OwnerType == toAccess.OwnerType && x.OwnerId == toAccess.OwnerId).ToList() + }) + }; + } + + [HttpPost("items/{itemId}/consume")] + [Authorize(Roles = "USER,SUPER")] + public async Task Consume(string itemId, [FromBody] ConsumeInventoryItemRequest req) + { + if (req.Quantity <= 0) + return BadRequest("quantity must be greater than 0"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var result = await _inventory.ConsumeAsync(itemId, req.Quantity, userId, User.IsInRole("SUPER")); + return result.Status switch + { + InventoryMutationStatus.ItemNotFound => NotFound(), + InventoryMutationStatus.Invalid => BadRequest("Invalid consume request"), + InventoryMutationStatus.Conflict => Conflict(), + _ => Ok(new InventoryOwnerResponse + { + OwnerType = result.OwnerType, + OwnerId = result.OwnerId, + Items = result.Items.Select(InventoryItemResponse.FromModel).ToList() + }) + }; + } + + [HttpPost("items/{itemId}/equip")] + [Authorize(Roles = "USER,SUPER")] + public async Task Equip(string itemId, [FromBody] EquipInventoryItemRequest req) + { + if (string.IsNullOrWhiteSpace(req.OwnerId)) + return BadRequest("ownerId required"); + if (string.IsNullOrWhiteSpace(req.EquipmentSlot)) + return BadRequest("equipmentSlot required"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var result = await _inventory.EquipAsync(itemId, req.OwnerId, req.EquipmentSlot, userId, User.IsInRole("SUPER")); + return result.Status switch + { + InventoryMutationStatus.ItemNotFound => NotFound(), + InventoryMutationStatus.Invalid => BadRequest("Invalid equip request"), + InventoryMutationStatus.Conflict => Conflict("Equipment slot is not available"), + _ => Ok(new InventoryOwnerResponse + { + OwnerType = result.OwnerType, + OwnerId = result.OwnerId, + Items = result.Items.Select(InventoryItemResponse.FromModel).ToList() + }) + }; + } + + [HttpPost("items/{itemId}/unequip")] + [Authorize(Roles = "USER,SUPER")] + public async Task Unequip(string itemId, [FromBody] UnequipInventoryItemRequest req) + { + if (string.IsNullOrWhiteSpace(req.OwnerId)) + return BadRequest("ownerId required"); + if (req.PreferredSlot is < 0) + return BadRequest("preferredSlot must be >= 0"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var result = await _inventory.UnequipAsync(itemId, req.OwnerId, req.PreferredSlot, userId, User.IsInRole("SUPER")); + return result.Status switch + { + InventoryMutationStatus.ItemNotFound => NotFound(), + InventoryMutationStatus.Invalid => BadRequest("Invalid unequip request"), + InventoryMutationStatus.Conflict => Conflict("Inventory slot is not available"), + _ => Ok(new InventoryOwnerResponse + { + OwnerType = result.OwnerType, + OwnerId = result.OwnerId, + Items = result.Items.Select(InventoryItemResponse.FromModel).ToList() + }) + }; + } +} diff --git a/microservices/InventoryApi/DOCUMENTS.md b/microservices/InventoryApi/DOCUMENTS.md new file mode 100644 index 0000000..05e00a5 --- /dev/null +++ b/microservices/InventoryApi/DOCUMENTS.md @@ -0,0 +1,239 @@ +# InventoryApi document shapes + +This service stores one MongoDB document per inventory item record. + +Inbound JSON documents +- CreateItemDefinitionRequest (`POST /api/inventory/item-definitions`) + ```json + { + "itemKey": "wood", + "displayName": "Wood", + "stackable": true, + "maxStackSize": 20, + "category": "resource", + "equipSlot": null + } + ``` + +- UpdateItemDefinitionRequest (`PUT /api/inventory/item-definitions/{itemKey}`) + ```json + { + "displayName": "Wood", + "stackable": true, + "maxStackSize": 20, + "category": "resource", + "equipSlot": null + } + ``` + +- GrantInventoryItemRequest (`POST /api/inventory/by-owner/{ownerType}/{ownerId}/grant`) + ```json + { + "itemKey": "string", + "quantity": 1, + "preferredSlot": 0 + } + ``` + `preferredSlot` is optional. If omitted, the service finds a valid destination slot. + +- MoveInventoryItemRequest (`POST /api/inventory/by-owner/{ownerType}/{ownerId}/move`) + ```json + { + "itemId": "uuid-string", + "toSlot": 1, + "quantity": 1 + } + ``` + `quantity` is optional. If omitted, move the full stack. + +- TransferInventoryItemRequest (`POST /api/inventory/transfer`) + ```json + { + "itemId": "uuid-string", + "fromOwnerType": "character", + "fromOwnerId": "string", + "toOwnerType": "location", + "toOwnerId": "string", + "toSlot": 0, + "quantity": 1 + } + ``` + `toSlot` and `quantity` are optional. If omitted, the service finds a valid destination and transfers the full stack. + +- ConsumeInventoryItemRequest (`POST /api/inventory/items/{itemId}/consume`) + ```json + { + "quantity": 1 + } + ``` + +- EquipInventoryItemRequest (`POST /api/inventory/items/{itemId}/equip`) + ```json + { + "ownerId": "string", + "equipmentSlot": "weapon" + } + ``` + Only valid for items currently owned by a character inventory. + +- UnequipInventoryItemRequest (`POST /api/inventory/items/{itemId}/unequip`) + ```json + { + "ownerId": "string", + "preferredSlot": 0 + } + ``` + Only valid for items currently equipped by a character. + +Stored documents (MongoDB) +- ItemDefinition + ```json + { + "itemKey": "wood", + "displayName": "Wood", + "stackable": true, + "maxStackSize": 20, + "category": "resource", + "equipSlot": null, + "createdUtc": "string (ISO-8601 datetime)", + "updatedUtc": "string (ISO-8601 datetime)" + } + ``` + +- InventoryItem + ```json + { + "id": "string (UUID)", + "itemKey": "wood", + "quantity": 12, + "ownerType": "character", + "ownerId": "string (ObjectId or stable external id)", + "ownerUserId": "string (ObjectId from auth token, null for public world owners)", + "slot": 0, + "equippedSlot": null, + "createdUtc": "string (ISO-8601 datetime)", + "updatedUtc": "string (ISO-8601 datetime)" + } + ``` + +Equipped character item example: +```json +{ + "id": "string (UUID)", + "itemKey": "pistol", + "quantity": 1, + "ownerType": "character", + "ownerId": "string (Character ObjectId)", + "ownerUserId": "string (ObjectId from auth token)", + "slot": null, + "equippedSlot": "weapon", + "createdUtc": "string (ISO-8601 datetime)", + "updatedUtc": "string (ISO-8601 datetime)" +} +``` + +Location stack example: +```json +{ + "id": "string (UUID)", + "itemKey": "wood", + "quantity": 12, + "ownerType": "location", + "ownerId": "string (Location ObjectId)", + "ownerUserId": null, + "slot": 3, + "equippedSlot": null, + "createdUtc": "string (ISO-8601 datetime)", + "updatedUtc": "string (ISO-8601 datetime)" +} +``` + +Outbound JSON documents +- ItemDefinitionResponse + ```json + { + "itemKey": "wood", + "displayName": "Wood", + "stackable": true, + "maxStackSize": 20, + "category": "resource", + "equipSlot": null, + "updatedUtc": "string (ISO-8601 datetime)" + } + ``` + +- InventoryItemResponse + ```json + { + "id": "string (UUID)", + "itemKey": "wood", + "quantity": 12, + "ownerType": "character", + "ownerId": "string", + "slot": 0, + "equippedSlot": null, + "updatedUtc": "string (ISO-8601 datetime)" + } + ``` + +- InventoryOwnerResponse (`GET /api/inventory/by-owner/{ownerType}/{ownerId}`) + ```json + { + "ownerType": "character", + "ownerId": "string", + "items": [ + { + "id": "string (UUID)", + "itemKey": "wood", + "quantity": 12, + "ownerType": "character", + "ownerId": "string", + "slot": 0, + "equippedSlot": null, + "updatedUtc": "string (ISO-8601 datetime)" + } + ] + } + ``` + +- TransferInventoryResponse (`POST /api/inventory/transfer`) + ```json + { + "movedItemId": "string (UUID)", + "fromOwnerType": "character", + "fromOwnerId": "string", + "toOwnerType": "location", + "toOwnerId": "string", + "fromItems": [], + "toItems": [] + } + ``` + +Validation rules +- `itemKey` must map to an existing item definition before item instances can be created +- `ownerType` must be a supported container type +- `ownerId` must map to an existing owning entity where applicable +- non-`SUPER` callers may only access owned character items unless explicit gameplay rules allow a world container read/write +- `quantity` must be greater than `0` +- non-stackable item definitions must have `maxStackSize = 1` +- non-stackable items must have `quantity = 1` +- equipped items must have `slot = null` +- unequipped bag items must have `equippedSlot = null` +- an item must not have both `slot` and `equippedSlot` populated +- slot occupancy must be unique for `(ownerType, ownerId, slot)` where `slot != null` +- equipment occupancy must be unique for `(ownerType, ownerId, equippedSlot)` where `equippedSlot != null` + +Recommended indexes +- unique on `itemKey` for item definitions +- index on `(ownerType, ownerId)` +- unique on `(ownerType, ownerId, slot)` for bag slots +- unique on `(ownerType, ownerId, equippedSlot)` for equipped slots +- index on `itemKey` for inventory items + +Behavior rules +- moving a full non-stackable item should update its owner and slot in place +- moving part of a stack should split the stack and create a new item record with a new UUID for the moved quantity +- moving into a compatible stack should merge quantities and delete or reduce the source record +- cross-owner transfer should be transactional when it mutates multiple records +- auctions should reference `itemId` values directly instead of copying item state into the auction document +- the service does not auto-seed item definitions; `ItemDefinitions` must be populated explicitly diff --git a/microservices/InventoryApi/Dockerfile b/microservices/InventoryApi/Dockerfile new file mode 100644 index 0000000..49087c6 --- /dev/null +++ b/microservices/InventoryApi/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +COPY ["InventoryApi.csproj", "./"] +RUN dotnet restore "InventoryApi.csproj" + +COPY . . +RUN dotnet publish "InventoryApi.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final +WORKDIR /app +COPY --from=build /app/publish . + +ENV ASPNETCORE_URLS=http://+:8080 \ + ASPNETCORE_ENVIRONMENT=Production + +EXPOSE 8080 + +ENTRYPOINT ["dotnet", "InventoryApi.dll"] diff --git a/microservices/InventoryApi/InventoryApi.csproj b/microservices/InventoryApi/InventoryApi.csproj new file mode 100644 index 0000000..0cb0950 --- /dev/null +++ b/microservices/InventoryApi/InventoryApi.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + + + + + + + + + + diff --git a/microservices/InventoryApi/Models/ConsumeInventoryItemRequest.cs b/microservices/InventoryApi/Models/ConsumeInventoryItemRequest.cs new file mode 100644 index 0000000..48d234a --- /dev/null +++ b/microservices/InventoryApi/Models/ConsumeInventoryItemRequest.cs @@ -0,0 +1,6 @@ +namespace InventoryApi.Models; + +public class ConsumeInventoryItemRequest +{ + public int Quantity { get; set; } = 1; +} diff --git a/microservices/InventoryApi/Models/CreateItemDefinitionRequest.cs b/microservices/InventoryApi/Models/CreateItemDefinitionRequest.cs new file mode 100644 index 0000000..1a141c6 --- /dev/null +++ b/microservices/InventoryApi/Models/CreateItemDefinitionRequest.cs @@ -0,0 +1,16 @@ +namespace InventoryApi.Models; + +public class CreateItemDefinitionRequest +{ + public string ItemKey { get; set; } = string.Empty; + + public string DisplayName { get; set; } = string.Empty; + + public bool Stackable { get; set; } + + public int MaxStackSize { get; set; } = 1; + + public string Category { get; set; } = "misc"; + + public string? EquipSlot { get; set; } +} diff --git a/microservices/InventoryApi/Models/EquipInventoryItemRequest.cs b/microservices/InventoryApi/Models/EquipInventoryItemRequest.cs new file mode 100644 index 0000000..303860f --- /dev/null +++ b/microservices/InventoryApi/Models/EquipInventoryItemRequest.cs @@ -0,0 +1,8 @@ +namespace InventoryApi.Models; + +public class EquipInventoryItemRequest +{ + public string OwnerId { get; set; } = string.Empty; + + public string EquipmentSlot { get; set; } = string.Empty; +} diff --git a/microservices/InventoryApi/Models/GrantInventoryItemRequest.cs b/microservices/InventoryApi/Models/GrantInventoryItemRequest.cs new file mode 100644 index 0000000..ad96551 --- /dev/null +++ b/microservices/InventoryApi/Models/GrantInventoryItemRequest.cs @@ -0,0 +1,10 @@ +namespace InventoryApi.Models; + +public class GrantInventoryItemRequest +{ + public string ItemKey { get; set; } = string.Empty; + + public int Quantity { get; set; } = 1; + + public int? PreferredSlot { get; set; } +} diff --git a/microservices/InventoryApi/Models/InventoryItem.cs b/microservices/InventoryApi/Models/InventoryItem.cs new file mode 100644 index 0000000..6bd1703 --- /dev/null +++ b/microservices/InventoryApi/Models/InventoryItem.cs @@ -0,0 +1,38 @@ +using MongoDB.Bson.Serialization.Attributes; + +namespace InventoryApi.Models; + +public class InventoryItem +{ + [BsonId] + public string Id { get; set; } = Guid.NewGuid().ToString(); + + [BsonElement("itemKey")] + public string ItemKey { get; set; } = string.Empty; + + [BsonElement("quantity")] + public int Quantity { get; set; } = 1; + + [BsonElement("ownerType")] + public string OwnerType { get; set; } = string.Empty; + + [BsonElement("ownerId")] + public string OwnerId { get; set; } = string.Empty; + + [BsonElement("ownerUserId")] + public string? OwnerUserId { get; set; } + + [BsonElement("slot")] + [BsonIgnoreIfNull] + public int? Slot { get; set; } + + [BsonElement("equippedSlot")] + [BsonIgnoreIfNull] + public string? EquippedSlot { get; set; } + + [BsonElement("createdUtc")] + public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; + + [BsonElement("updatedUtc")] + public DateTime UpdatedUtc { get; set; } = DateTime.UtcNow; +} diff --git a/microservices/InventoryApi/Models/InventoryItemResponse.cs b/microservices/InventoryApi/Models/InventoryItemResponse.cs new file mode 100644 index 0000000..d4b4d6f --- /dev/null +++ b/microservices/InventoryApi/Models/InventoryItemResponse.cs @@ -0,0 +1,32 @@ +namespace InventoryApi.Models; + +public class InventoryItemResponse +{ + public string Id { get; set; } = string.Empty; + + public string ItemKey { get; set; } = string.Empty; + + public int Quantity { get; set; } + + public string OwnerType { get; set; } = string.Empty; + + public string OwnerId { get; set; } = string.Empty; + + public int? Slot { get; set; } + + public string? EquippedSlot { get; set; } + + public DateTime UpdatedUtc { get; set; } + + public static InventoryItemResponse FromModel(InventoryItem item) => new() + { + Id = item.Id, + ItemKey = item.ItemKey, + Quantity = item.Quantity, + OwnerType = item.OwnerType, + OwnerId = item.OwnerId, + Slot = item.Slot, + EquippedSlot = item.EquippedSlot, + UpdatedUtc = item.UpdatedUtc + }; +} diff --git a/microservices/InventoryApi/Models/InventoryMutationResult.cs b/microservices/InventoryApi/Models/InventoryMutationResult.cs new file mode 100644 index 0000000..09946f4 --- /dev/null +++ b/microservices/InventoryApi/Models/InventoryMutationResult.cs @@ -0,0 +1,22 @@ +namespace InventoryApi.Models; + +public enum InventoryMutationStatus +{ + Ok, + ItemNotFound, + Invalid, + Conflict +} + +public class InventoryMutationResult +{ + public InventoryMutationStatus Status { get; init; } = InventoryMutationStatus.Ok; + + public string OwnerType { get; init; } = string.Empty; + + public string OwnerId { get; init; } = string.Empty; + + public List Items { get; init; } = []; + + public static implicit operator InventoryMutationStatus(InventoryMutationResult result) => result.Status; +} diff --git a/microservices/InventoryApi/Models/InventoryOwnerResponse.cs b/microservices/InventoryApi/Models/InventoryOwnerResponse.cs new file mode 100644 index 0000000..e3af042 --- /dev/null +++ b/microservices/InventoryApi/Models/InventoryOwnerResponse.cs @@ -0,0 +1,10 @@ +namespace InventoryApi.Models; + +public class InventoryOwnerResponse +{ + public string OwnerType { get; set; } = string.Empty; + + public string OwnerId { get; set; } = string.Empty; + + public List Items { get; set; } = []; +} diff --git a/microservices/InventoryApi/Models/ItemDefinition.cs b/microservices/InventoryApi/Models/ItemDefinition.cs new file mode 100644 index 0000000..9cb8b98 --- /dev/null +++ b/microservices/InventoryApi/Models/ItemDefinition.cs @@ -0,0 +1,33 @@ +using MongoDB.Bson.Serialization.Attributes; + +namespace InventoryApi.Models; + +[BsonIgnoreExtraElements] +public class ItemDefinition +{ + [BsonId] + [BsonElement("itemKey")] + public string ItemKey { get; set; } = string.Empty; + + [BsonElement("displayName")] + public string DisplayName { get; set; } = string.Empty; + + [BsonElement("stackable")] + public bool Stackable { get; set; } + + [BsonElement("maxStackSize")] + public int MaxStackSize { get; set; } = 1; + + [BsonElement("category")] + public string Category { get; set; } = "misc"; + + [BsonElement("equipSlot")] + [BsonIgnoreIfNull] + public string? EquipSlot { get; set; } + + [BsonElement("createdUtc")] + public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; + + [BsonElement("updatedUtc")] + public DateTime UpdatedUtc { get; set; } = DateTime.UtcNow; +} diff --git a/microservices/InventoryApi/Models/ItemDefinitionResponse.cs b/microservices/InventoryApi/Models/ItemDefinitionResponse.cs new file mode 100644 index 0000000..5f8e31f --- /dev/null +++ b/microservices/InventoryApi/Models/ItemDefinitionResponse.cs @@ -0,0 +1,29 @@ +namespace InventoryApi.Models; + +public class ItemDefinitionResponse +{ + public string ItemKey { get; set; } = string.Empty; + + public string DisplayName { get; set; } = string.Empty; + + public bool Stackable { get; set; } + + public int MaxStackSize { get; set; } + + public string Category { get; set; } = "misc"; + + public string? EquipSlot { get; set; } + + public DateTime UpdatedUtc { get; set; } + + public static ItemDefinitionResponse FromModel(ItemDefinition definition) => new() + { + ItemKey = definition.ItemKey, + DisplayName = definition.DisplayName, + Stackable = definition.Stackable, + MaxStackSize = definition.MaxStackSize, + Category = definition.Category, + EquipSlot = definition.EquipSlot, + UpdatedUtc = definition.UpdatedUtc + }; +} diff --git a/microservices/InventoryApi/Models/MoveInventoryItemRequest.cs b/microservices/InventoryApi/Models/MoveInventoryItemRequest.cs new file mode 100644 index 0000000..f7c8419 --- /dev/null +++ b/microservices/InventoryApi/Models/MoveInventoryItemRequest.cs @@ -0,0 +1,10 @@ +namespace InventoryApi.Models; + +public class MoveInventoryItemRequest +{ + public string ItemId { get; set; } = string.Empty; + + public int ToSlot { get; set; } + + public int? Quantity { get; set; } +} diff --git a/microservices/InventoryApi/Models/OwnerAccessResult.cs b/microservices/InventoryApi/Models/OwnerAccessResult.cs new file mode 100644 index 0000000..6453864 --- /dev/null +++ b/microservices/InventoryApi/Models/OwnerAccessResult.cs @@ -0,0 +1,16 @@ +namespace InventoryApi.Models; + +public class OwnerAccessResult +{ + public bool IsSupported { get; init; } + + public bool Exists { get; init; } + + public bool IsAuthorized { get; init; } + + public string OwnerType { get; init; } = string.Empty; + + public string OwnerId { get; init; } = string.Empty; + + public string? OwnerUserId { get; init; } +} diff --git a/microservices/InventoryApi/Models/TransferInventoryItemRequest.cs b/microservices/InventoryApi/Models/TransferInventoryItemRequest.cs new file mode 100644 index 0000000..ad89bfb --- /dev/null +++ b/microservices/InventoryApi/Models/TransferInventoryItemRequest.cs @@ -0,0 +1,18 @@ +namespace InventoryApi.Models; + +public class TransferInventoryItemRequest +{ + public string ItemId { get; set; } = string.Empty; + + public string FromOwnerType { get; set; } = string.Empty; + + public string FromOwnerId { get; set; } = string.Empty; + + public string ToOwnerType { get; set; } = string.Empty; + + public string ToOwnerId { get; set; } = string.Empty; + + public int? ToSlot { get; set; } + + public int? Quantity { get; set; } +} diff --git a/microservices/InventoryApi/Models/TransferInventoryResponse.cs b/microservices/InventoryApi/Models/TransferInventoryResponse.cs new file mode 100644 index 0000000..a211c13 --- /dev/null +++ b/microservices/InventoryApi/Models/TransferInventoryResponse.cs @@ -0,0 +1,18 @@ +namespace InventoryApi.Models; + +public class TransferInventoryResponse +{ + public string MovedItemId { get; set; } = string.Empty; + + public string FromOwnerType { get; set; } = string.Empty; + + public string FromOwnerId { get; set; } = string.Empty; + + public string ToOwnerType { get; set; } = string.Empty; + + public string ToOwnerId { get; set; } = string.Empty; + + public List FromItems { get; set; } = []; + + public List ToItems { get; set; } = []; +} diff --git a/microservices/InventoryApi/Models/UnequipInventoryItemRequest.cs b/microservices/InventoryApi/Models/UnequipInventoryItemRequest.cs new file mode 100644 index 0000000..d63122a --- /dev/null +++ b/microservices/InventoryApi/Models/UnequipInventoryItemRequest.cs @@ -0,0 +1,8 @@ +namespace InventoryApi.Models; + +public class UnequipInventoryItemRequest +{ + public string OwnerId { get; set; } = string.Empty; + + public int? PreferredSlot { get; set; } +} diff --git a/microservices/InventoryApi/Models/UpdateItemDefinitionRequest.cs b/microservices/InventoryApi/Models/UpdateItemDefinitionRequest.cs new file mode 100644 index 0000000..58165ac --- /dev/null +++ b/microservices/InventoryApi/Models/UpdateItemDefinitionRequest.cs @@ -0,0 +1,14 @@ +namespace InventoryApi.Models; + +public class UpdateItemDefinitionRequest +{ + public string DisplayName { get; set; } = string.Empty; + + public bool Stackable { get; set; } + + public int MaxStackSize { get; set; } = 1; + + public string Category { get; set; } = "misc"; + + public string? EquipSlot { get; set; } +} diff --git a/microservices/InventoryApi/Program.cs b/microservices/InventoryApi/Program.cs new file mode 100644 index 0000000..33e9252 --- /dev/null +++ b/microservices/InventoryApi/Program.cs @@ -0,0 +1,106 @@ +using InventoryApi.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); + +builder.Services.AddSingleton(); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Inventory API", Version = "v1" }); + c.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT", + Description = "Paste your access token here (no 'Bearer ' prefix needed)." + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { Type = ReferenceType.SecurityScheme, Id = "bearerAuth" } + }, + Array.Empty() + } + }); +}); + +var cfg = builder.Configuration; +var jwtKey = cfg["Jwt:Key"] ?? throw new Exception("Jwt:Key missing"); +var issuer = cfg["Jwt:Issuer"] ?? "promiscuity"; +var aud = cfg["Jwt:Audience"] ?? issuer; + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(o => + { + o.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = issuer, + ValidateAudience = true, + ValidAudience = aud, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)), + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(30) + }; + }); + +builder.Services.AddAuthorization(); + +var app = builder.Build(); + +app.UseExceptionHandler(errorApp => +{ + errorApp.Run(async context => + { + var feature = context.Features.Get(); + var exception = feature?.Error; + var logger = context.RequestServices.GetRequiredService().CreateLogger("GlobalException"); + var traceId = context.TraceIdentifier; + + if (exception is not null) + { + logger.LogError( + exception, + "Unhandled exception for {Method} {Path}. TraceId={TraceId}", + context.Request.Method, + context.Request.Path, + traceId + ); + } + + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + context.Response.ContentType = "application/problem+json"; + + await context.Response.WriteAsJsonAsync(new + { + type = "https://httpstatuses.com/500", + title = "Internal Server Error", + status = 500, + detail = exception?.Message ?? "An unexpected server error occurred.", + traceId + }); + }); +}); + +app.MapGet("/healthz", () => Results.Ok("ok")); +app.UseSwagger(); +app.UseSwaggerUI(o => +{ + o.SwaggerEndpoint("/swagger/v1/swagger.json", "Inventory API v1"); + o.RoutePrefix = "swagger"; +}); +app.UseAuthentication(); +app.UseAuthorization(); +app.MapControllers(); +app.Run(); diff --git a/microservices/InventoryApi/Properties/launchSettings.json b/microservices/InventoryApi/Properties/launchSettings.json new file mode 100644 index 0000000..b70cfb5 --- /dev/null +++ b/microservices/InventoryApi/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5184", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/microservices/InventoryApi/README.md b/microservices/InventoryApi/README.md new file mode 100644 index 0000000..b95d50c --- /dev/null +++ b/microservices/InventoryApi/README.md @@ -0,0 +1,124 @@ +# InventoryApi + +## Purpose +Owns item-instance state. + +This service should answer: +- what item records currently belong to a character, location, or other container owner +- where each item currently is +- whether an item move, split, merge, equip, or consume action is valid +- which specific item instance is being traded, auctioned, or transferred + +This service should not own: +- auth/token issuance +- character identity creation +- location generation +- auction bidding logic + +## Ownership model +- every inventory record has an `ownerType` and `ownerId` +- `USER` can read and mutate items owned by their own characters +- `USER` can read and mutate location/container items only when gameplay rules allow it +- `SUPER` can read and mutate any inventory item +- access should be resolved through the current owner, not from the item id alone + +## Initial design +Use one MongoDB document per inventory item record. + +Practical interpretation: +- non-stackable items: one document per item instance +- stackable items: one document per stack + +Reasons: +- every item or stack gets a stable UUID, which is useful for auctions, trade, mail, and auditing +- ownership transfer is explicit and cheap: update `ownerType`, `ownerId`, and slot fields +- future item metadata like durability, rarity rolls, or provenance can live on the item document +- auction listings can point to specific item ids instead of vague stack descriptions + +Tradeoffs: +- inventory reads are queries over many documents instead of one aggregate read +- stack merging and slot enforcement need careful indexes and mutation logic +- transfers should use transactions when they touch multiple item documents + +## Suggested endpoints +- `GET /api/inventory/by-owner/{ownerType}/{ownerId}` + Return all item records currently owned by that container owner. +- `POST /api/inventory/by-owner/{ownerType}/{ownerId}/grant` + Create a new item record or add quantity into an existing compatible stack. +- `POST /api/inventory/by-owner/{ownerType}/{ownerId}/move` + Move an item or stack within the same owner inventory. +- `POST /api/inventory/transfer` + Move quantity from one owner inventory to another. +- `POST /api/inventory/items/{itemId}/consume` + Consume quantity from a specific item record. +- `POST /api/inventory/items/{itemId}/equip` + Equip a specific item into a character equipment slot. +- `POST /api/inventory/items/{itemId}/unequip` + Return an equipped item to a character inventory slot. + +Notes: +- `equip` and `unequip` only make sense for character-owned items +- `transfer` is the core world interaction primitive for looting, dropping, trading, chest interaction, and auction handoff +- a future AuctionApi can reserve or re-own specific item ids without redesigning InventoryApi + +## Item identity +Every inventory record should have a stable UUID string such as: +- `a8d4218b-5e20-4e47-8b5f-0f0f0b9d7e10` + +Each record also carries an `itemKey` such as: +- `wood` +- `stone` +- `pistol` +- `small_health_potion` + +Recommended distinction: +- `id`: unique item-record identifier used for ownership changes, auctions, and references +- `itemKey`: item definition identifier used to decide stackability and gameplay behavior + +## Recommended stored shape +Each item document should include: +- `id` +- `itemKey` +- `quantity` +- `ownerType` +- `ownerId` +- `slot` +- `equippedSlot` +- `ownerUserId` when applicable +- `createdUtc` +- `updatedUtc` + +Optional future fields: +- `durability` +- `rarity` +- `instanceData` +- `listingId` +- `reservedUntilUtc` + +## MVP rules +- an item record must belong to exactly one owner at a time +- stackable items may share `itemKey` but should still be represented by one stack record per occupied slot +- non-stackable items must always have `quantity = 1` +- equipped items should set `equippedSlot` and clear `slot` +- unequipped bag items should set `slot` and clear `equippedSlot` +- slot occupancy must be unique per `(ownerType, ownerId, slot)` +- all mutating endpoints should be idempotent where practical + +## Definition source +Item behavior is fully data-driven through the `ItemDefinitions` collection inside `InventoryApi`. + +That means: +- no hardcoded stack-size rules +- no automatic item-definition seeding +- item instances can only be created for `itemKey` values that already exist in `ItemDefinitions` + +## Client shape +The Godot client should fetch all items for the currently relevant owner and group them into a bag view locally. + +Good pattern: +- login/select character +- `GET /api/inventory/by-owner/character/{characterId}` +- when opening a stash or world container: `GET /api/inventory/by-owner/location/{locationId}` +- cache the returned item records locally +- call transfer/equip/consume endpoints using specific `itemId` values +- replace local state with the server response after each mutation diff --git a/microservices/InventoryApi/Services/InventoryStore.cs b/microservices/InventoryApi/Services/InventoryStore.cs new file mode 100644 index 0000000..8f2cea6 --- /dev/null +++ b/microservices/InventoryApi/Services/InventoryStore.cs @@ -0,0 +1,601 @@ +using InventoryApi.Models; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace InventoryApi.Services; + +public class InventoryStore +{ + private const string CharacterOwnerType = "character"; + private const string LocationOwnerType = "location"; + private const string OwnerIndexName = "owner_type_1_owner_id_1"; + private const string SlotIndexName = "owner_type_1_owner_id_1_slot_1"; + private const string EquippedSlotIndexName = "owner_type_1_owner_id_1_equipped_slot_1"; + private const string DefinitionCategoryIndexName = "category_1"; + + private readonly IMongoCollection _items; + private readonly IMongoCollection _definitions; + private readonly IMongoCollection _characters; + private readonly IMongoCollection _locations; + private readonly IMongoClient _client; + private readonly string _dbName; + + public InventoryStore(IConfiguration cfg) + { + var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; + _dbName = cfg["MongoDB:DatabaseName"] ?? "promiscuity"; + _client = new MongoClient(cs); + var db = _client.GetDatabase(_dbName); + _items = db.GetCollection("InventoryItems"); + _definitions = db.GetCollection("ItemDefinitions"); + _characters = db.GetCollection("Characters"); + _locations = db.GetCollection("Locations"); + + EnsureIndexes(); + } + + public async Task ResolveOwnerAsync(string ownerType, string ownerId, string userId, bool allowAnyOwner) + { + var normalizedOwnerType = NormalizeOwnerType(ownerType); + if (normalizedOwnerType is null) + return new OwnerAccessResult { IsSupported = false }; + + if (normalizedOwnerType == CharacterOwnerType) + { + var character = await _characters.Find(c => c.Id == ownerId).FirstOrDefaultAsync(); + if (character is null) + { + return new OwnerAccessResult + { + IsSupported = true, + Exists = false, + OwnerType = normalizedOwnerType, + OwnerId = ownerId + }; + } + + var authorized = allowAnyOwner || character.OwnerUserId == userId; + return new OwnerAccessResult + { + IsSupported = true, + Exists = true, + IsAuthorized = authorized, + OwnerType = normalizedOwnerType, + OwnerId = ownerId, + OwnerUserId = character.OwnerUserId + }; + } + + var location = await _locations.Find(l => l.Id == ownerId).FirstOrDefaultAsync(); + return new OwnerAccessResult + { + IsSupported = true, + Exists = location is not null, + IsAuthorized = location is not null, + OwnerType = normalizedOwnerType, + OwnerId = ownerId, + OwnerUserId = null + }; + } + + public Task> GetByOwnerAsync(string ownerType, string ownerId) => + _items.Find(i => i.OwnerType == ownerType && i.OwnerId == ownerId) + .SortBy(i => i.EquippedSlot) + .ThenBy(i => i.Slot) + .ThenBy(i => i.ItemKey) + .ToListAsync(); + + public Task> ListItemDefinitionsAsync() => + _definitions.Find(Builders.Filter.Empty).SortBy(d => d.ItemKey).ToListAsync(); + + public async Task GetItemDefinitionAsync(string itemKey) => + await _definitions.Find(d => d.ItemKey == NormalizeItemKey(itemKey)).FirstOrDefaultAsync(); + + public async Task CreateItemDefinitionAsync(CreateItemDefinitionRequest req) + { + var itemKey = NormalizeItemKey(req.ItemKey); + var existing = await GetItemDefinitionAsync(itemKey); + if (existing is not null) + return null; + + var definition = new ItemDefinition + { + ItemKey = itemKey, + DisplayName = req.DisplayName.Trim(), + Stackable = req.Stackable, + MaxStackSize = req.Stackable ? req.MaxStackSize : 1, + Category = string.IsNullOrWhiteSpace(req.Category) ? "misc" : req.Category.Trim().ToLowerInvariant(), + EquipSlot = string.IsNullOrWhiteSpace(req.EquipSlot) ? null : req.EquipSlot.Trim().ToLowerInvariant(), + CreatedUtc = DateTime.UtcNow, + UpdatedUtc = DateTime.UtcNow + }; + + await _definitions.InsertOneAsync(definition); + return definition; + } + + public async Task UpdateItemDefinitionAsync(string itemKey, UpdateItemDefinitionRequest req) + { + var existing = await GetItemDefinitionAsync(itemKey); + if (existing is null) + return null; + + existing.DisplayName = req.DisplayName.Trim(); + existing.Stackable = req.Stackable; + existing.MaxStackSize = req.Stackable ? req.MaxStackSize : 1; + existing.Category = string.IsNullOrWhiteSpace(req.Category) ? "misc" : req.Category.Trim().ToLowerInvariant(); + existing.EquipSlot = string.IsNullOrWhiteSpace(req.EquipSlot) ? null : req.EquipSlot.Trim().ToLowerInvariant(); + existing.UpdatedUtc = DateTime.UtcNow; + + await _definitions.ReplaceOneAsync(d => d.ItemKey == existing.ItemKey, existing); + return existing; + } + + public async Task> GrantAsync(OwnerAccessResult owner, GrantInventoryItemRequest req, ItemDefinition definition) + { + var normalizedKey = NormalizeItemKey(req.ItemKey); + if (definition.Stackable) + { + var remaining = req.Quantity; + var targetSlot = req.PreferredSlot; + while (remaining > 0) + { + var slot = targetSlot ?? await FindFirstOpenSlotAsync(owner.OwnerType, owner.OwnerId); + var existing = await FindStackAsync(owner.OwnerType, owner.OwnerId, normalizedKey, slot); + if (existing is not null) + { + var availableSpace = definition.MaxStackSize - existing.Quantity; + if (availableSpace > 0) + { + var added = Math.Min(remaining, availableSpace); + existing.Quantity += added; + existing.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(existing); + remaining -= added; + } + } + else + { + var stackQuantity = Math.Min(remaining, definition.MaxStackSize); + await InsertItemAsync(new InventoryItem + { + ItemKey = normalizedKey, + Quantity = stackQuantity, + OwnerType = owner.OwnerType, + OwnerId = owner.OwnerId, + OwnerUserId = owner.OwnerUserId, + Slot = slot + }); + remaining -= stackQuantity; + } + + targetSlot = null; + } + return await GetByOwnerAsync(owner.OwnerType, owner.OwnerId); + } + + var nextPreferredSlot = req.PreferredSlot; + for (var index = 0; index < req.Quantity; index += 1) + { + var slot = nextPreferredSlot ?? await FindFirstOpenSlotAsync(owner.OwnerType, owner.OwnerId); + await InsertItemAsync(new InventoryItem + { + ItemKey = normalizedKey, + Quantity = 1, + OwnerType = owner.OwnerType, + OwnerId = owner.OwnerId, + OwnerUserId = owner.OwnerUserId, + Slot = slot + }); + nextPreferredSlot = null; + } + return await GetByOwnerAsync(owner.OwnerType, owner.OwnerId); + } + + public async Task MoveAsync(OwnerAccessResult owner, MoveInventoryItemRequest req) + { + var item = await _items.Find(i => i.Id == req.ItemId).FirstOrDefaultAsync(); + if (item is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.ItemNotFound }; + if (item.OwnerType != owner.OwnerType || item.OwnerId != owner.OwnerId || item.EquippedSlot is not null) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var quantity = req.Quantity ?? item.Quantity; + if (quantity <= 0 || quantity > item.Quantity) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var definition = await GetItemDefinitionAsync(item.ItemKey); + if (definition is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var existing = await FindItemBySlotAsync(owner.OwnerType, owner.OwnerId, req.ToSlot); + if (existing is not null && existing.Id != item.Id) + { + if (!CanMerge(item, existing, definition)) + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + + var transferable = Math.Min(quantity, definition.MaxStackSize - existing.Quantity); + if (transferable <= 0) + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + + existing.Quantity += transferable; + existing.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(existing); + + if (transferable == item.Quantity) + await DeleteItemAsync(item.Id); + else + { + item.Quantity -= transferable; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item); + } + } + else if (quantity == item.Quantity) + { + item.Slot = req.ToSlot; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item); + } + else + { + item.Quantity -= quantity; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item); + + await InsertItemAsync(new InventoryItem + { + ItemKey = item.ItemKey, + Quantity = quantity, + OwnerType = item.OwnerType, + OwnerId = item.OwnerId, + OwnerUserId = item.OwnerUserId, + Slot = req.ToSlot + }); + } + + return new InventoryMutationResult + { + Status = InventoryMutationStatus.Ok, + OwnerType = owner.OwnerType, + OwnerId = owner.OwnerId, + Items = await GetByOwnerAsync(owner.OwnerType, owner.OwnerId) + }; + } + + public async Task TransferAsync(OwnerAccessResult fromOwner, OwnerAccessResult toOwner, TransferInventoryItemRequest req) + { + using var session = await _client.StartSessionAsync(); + session.StartTransaction(); + + try + { + var item = await _items.Find(session, i => i.Id == req.ItemId).FirstOrDefaultAsync(); + if (item is null) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.ItemNotFound }; + } + + if (item.OwnerType != fromOwner.OwnerType || item.OwnerId != fromOwner.OwnerId || item.EquippedSlot is not null) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + } + + var quantity = req.Quantity ?? item.Quantity; + if (quantity <= 0 || quantity > item.Quantity) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + } + + var definition = await GetItemDefinitionAsync(item.ItemKey); + if (definition is null) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + } + + var toSlot = req.ToSlot ?? await FindFirstOpenSlotAsync(toOwner.OwnerType, toOwner.OwnerId, session); + var target = await FindItemBySlotAsync(toOwner.OwnerType, toOwner.OwnerId, toSlot, session); + if (target is not null && !CanMerge(item, target, definition)) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + } + + if (target is not null) + { + var transferable = Math.Min(quantity, definition.MaxStackSize - target.Quantity); + if (transferable <= 0) + { + await session.AbortTransactionAsync(); + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + } + + target.Quantity += transferable; + target.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(target, session); + + if (transferable == item.Quantity) + await DeleteItemAsync(item.Id, session); + else + { + item.Quantity -= transferable; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item, session); + } + } + else if (quantity == item.Quantity) + { + item.OwnerType = toOwner.OwnerType; + item.OwnerId = toOwner.OwnerId; + item.OwnerUserId = toOwner.OwnerUserId; + item.Slot = toSlot; + item.EquippedSlot = null; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item, session); + } + else + { + item.Quantity -= quantity; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item, session); + + await InsertItemAsync(new InventoryItem + { + ItemKey = item.ItemKey, + Quantity = quantity, + OwnerType = toOwner.OwnerType, + OwnerId = toOwner.OwnerId, + OwnerUserId = toOwner.OwnerUserId, + Slot = toSlot + }, session); + } + + await session.CommitTransactionAsync(); + var fromItems = await GetByOwnerAsync(fromOwner.OwnerType, fromOwner.OwnerId); + var toItems = await GetByOwnerAsync(toOwner.OwnerType, toOwner.OwnerId); + return new InventoryMutationResult + { + Status = InventoryMutationStatus.Ok, + Items = fromItems.Concat(toItems).ToList() + }; + } + catch + { + await session.AbortTransactionAsync(); + throw; + } + } + + public async Task ConsumeAsync(string itemId, int quantity, string userId, bool allowAnyOwner) + { + var item = await _items.Find(i => i.Id == itemId).FirstOrDefaultAsync(); + if (item is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.ItemNotFound }; + + var access = await ResolveOwnerAsync(item.OwnerType, item.OwnerId, userId, allowAnyOwner); + if (!access.Exists || !access.IsAuthorized) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + if (quantity > item.Quantity) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + if (quantity == item.Quantity) + await DeleteItemAsync(item.Id); + else + { + item.Quantity -= quantity; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item); + } + + return new InventoryMutationResult + { + Status = InventoryMutationStatus.Ok, + OwnerType = item.OwnerType, + OwnerId = item.OwnerId, + Items = await GetByOwnerAsync(item.OwnerType, item.OwnerId) + }; + } + + public async Task EquipAsync(string itemId, string ownerId, string equipmentSlot, string userId, bool allowAnyOwner) + { + var owner = await ResolveOwnerAsync(CharacterOwnerType, ownerId, userId, allowAnyOwner); + if (!owner.Exists || !owner.IsAuthorized) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var item = await _items.Find(i => i.Id == itemId).FirstOrDefaultAsync(); + if (item is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.ItemNotFound }; + if (item.OwnerType != CharacterOwnerType || item.OwnerId != ownerId || item.Slot is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + var definition = await GetItemDefinitionAsync(item.ItemKey); + if (definition is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + if (!string.Equals(definition.EquipSlot, equipmentSlot.Trim(), StringComparison.OrdinalIgnoreCase)) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var equipped = await _items.Find(i => + i.OwnerType == CharacterOwnerType && + i.OwnerId == ownerId && + i.EquippedSlot == equipmentSlot).FirstOrDefaultAsync(); + if (equipped is not null && equipped.Id != item.Id) + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + + item.Slot = null; + item.EquippedSlot = equipmentSlot.Trim(); + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item); + + return new InventoryMutationResult + { + Status = InventoryMutationStatus.Ok, + OwnerType = item.OwnerType, + OwnerId = item.OwnerId, + Items = await GetByOwnerAsync(item.OwnerType, item.OwnerId) + }; + } + + public async Task UnequipAsync(string itemId, string ownerId, int? preferredSlot, string userId, bool allowAnyOwner) + { + var owner = await ResolveOwnerAsync(CharacterOwnerType, ownerId, userId, allowAnyOwner); + if (!owner.Exists || !owner.IsAuthorized) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var item = await _items.Find(i => i.Id == itemId).FirstOrDefaultAsync(); + if (item is null) + return new InventoryMutationResult { Status = InventoryMutationStatus.ItemNotFound }; + if (item.OwnerType != CharacterOwnerType || item.OwnerId != ownerId || string.IsNullOrWhiteSpace(item.EquippedSlot)) + return new InventoryMutationResult { Status = InventoryMutationStatus.Invalid }; + + var slot = preferredSlot ?? await FindFirstOpenSlotAsync(item.OwnerType, item.OwnerId); + var existing = await FindItemBySlotAsync(item.OwnerType, item.OwnerId, slot); + if (existing is not null && existing.Id != item.Id) + return new InventoryMutationResult { Status = InventoryMutationStatus.Conflict }; + + item.EquippedSlot = null; + item.Slot = slot; + item.UpdatedUtc = DateTime.UtcNow; + await ReplaceItemAsync(item); + + return new InventoryMutationResult + { + Status = InventoryMutationStatus.Ok, + OwnerType = item.OwnerType, + OwnerId = item.OwnerId, + Items = await GetByOwnerAsync(item.OwnerType, item.OwnerId) + }; + } + + private static string? NormalizeOwnerType(string ownerType) + { + var normalized = ownerType.Trim().ToLowerInvariant(); + return normalized switch + { + CharacterOwnerType => CharacterOwnerType, + LocationOwnerType => LocationOwnerType, + _ => null + }; + } + + private async Task FindFirstOpenSlotAsync(string ownerType, string ownerId, IClientSessionHandle? session = null) + { + var items = session is null + ? await GetByOwnerAsync(ownerType, ownerId) + : await _items.Find(session, i => i.OwnerType == ownerType && i.OwnerId == ownerId).ToListAsync(); + + var usedSlots = items.Where(i => i.Slot.HasValue).Select(i => i.Slot!.Value).ToHashSet(); + var slot = 0; + while (usedSlots.Contains(slot)) + slot += 1; + return slot; + } + + private Task FindItemBySlotAsync(string ownerType, string ownerId, int slot, IClientSessionHandle? session = null) + => session is null + ? FindItemBySlotNoSessionAsync(ownerType, ownerId, slot) + : FindItemBySlotWithSessionAsync(ownerType, ownerId, slot, session); + + private Task FindStackAsync(string ownerType, string ownerId, string itemKey, int slot, IClientSessionHandle? session = null) + => session is null + ? FindStackNoSessionAsync(ownerType, ownerId, itemKey, slot) + : FindStackWithSessionAsync(ownerType, ownerId, itemKey, slot, session); + + private async Task InsertItemAsync(InventoryItem item, IClientSessionHandle? session = null) + { + item.ItemKey = NormalizeItemKey(item.ItemKey); + item.OwnerType = item.OwnerType.Trim().ToLowerInvariant(); + item.CreatedUtc = DateTime.UtcNow; + item.UpdatedUtc = item.CreatedUtc; + + if (session is null) + await _items.InsertOneAsync(item); + else + await _items.InsertOneAsync(session, item); + } + + private async Task ReplaceItemAsync(InventoryItem item, IClientSessionHandle? session = null) + { + item.UpdatedUtc = DateTime.UtcNow; + if (session is null) + await _items.ReplaceOneAsync(i => i.Id == item.Id, item); + else + await _items.ReplaceOneAsync(session, i => i.Id == item.Id, item); + } + + private Task DeleteItemAsync(string itemId, IClientSessionHandle? session = null) + { + return session is null + ? _items.DeleteOneAsync(i => i.Id == itemId) + : _items.DeleteOneAsync(session, i => i.Id == itemId); + } + + private static string NormalizeItemKey(string itemKey) => itemKey.Trim().ToLowerInvariant(); + + private static bool CanMerge(InventoryItem source, InventoryItem target, ItemDefinition definition) => + source.ItemKey == target.ItemKey && + source.EquippedSlot is null && + target.EquippedSlot is null && + definition.Stackable; + + private void EnsureIndexes() + { + _items.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(i => i.OwnerType).Ascending(i => i.OwnerId), + new CreateIndexOptions { Name = OwnerIndexName })); + + _items.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(i => i.OwnerType).Ascending(i => i.OwnerId).Ascending(i => i.Slot), + new CreateIndexOptions + { + Unique = true, + Name = SlotIndexName, + PartialFilterExpression = new BsonDocument("slot", new BsonDocument("$exists", true)) + })); + + _items.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(i => i.OwnerType).Ascending(i => i.OwnerId).Ascending(i => i.EquippedSlot), + new CreateIndexOptions + { + Unique = true, + Name = EquippedSlotIndexName, + PartialFilterExpression = new BsonDocument("equippedSlot", new BsonDocument("$exists", true)) + })); + + _definitions.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(d => d.Category), + new CreateIndexOptions { Name = DefinitionCategoryIndexName })); + } + + private async Task FindItemBySlotNoSessionAsync(string ownerType, string ownerId, int slot) => + await _items.Find(i => i.OwnerType == ownerType && i.OwnerId == ownerId && i.Slot == slot).FirstOrDefaultAsync(); + + private async Task FindItemBySlotWithSessionAsync(string ownerType, string ownerId, int slot, IClientSessionHandle session) => + await _items.Find(session, i => i.OwnerType == ownerType && i.OwnerId == ownerId && i.Slot == slot).FirstOrDefaultAsync(); + + private async Task FindStackNoSessionAsync(string ownerType, string ownerId, string itemKey, int slot) => + await _items.Find(i => i.OwnerType == ownerType && i.OwnerId == ownerId && i.Slot == slot && i.ItemKey == itemKey && i.EquippedSlot == null).FirstOrDefaultAsync(); + + private async Task FindStackWithSessionAsync(string ownerType, string ownerId, string itemKey, int slot, IClientSessionHandle session) => + await _items.Find(session, i => i.OwnerType == ownerType && i.OwnerId == ownerId && i.Slot == slot && i.ItemKey == itemKey && i.EquippedSlot == null).FirstOrDefaultAsync(); + + [MongoDB.Bson.Serialization.Attributes.BsonIgnoreExtraElements] + private class CharacterOwnerDocument + { + [MongoDB.Bson.Serialization.Attributes.BsonId] + [MongoDB.Bson.Serialization.Attributes.BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)] + public string? Id { get; set; } + + public string OwnerUserId { get; set; } = string.Empty; + } + + [MongoDB.Bson.Serialization.Attributes.BsonIgnoreExtraElements] + private class LocationOwnerDocument + { + [MongoDB.Bson.Serialization.Attributes.BsonId] + [MongoDB.Bson.Serialization.Attributes.BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)] + public string? Id { get; set; } + } +} diff --git a/microservices/InventoryApi/appsettings.Development.json b/microservices/InventoryApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/microservices/InventoryApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/microservices/InventoryApi/appsettings.json b/microservices/InventoryApi/appsettings.json new file mode 100644 index 0000000..b461ae5 --- /dev/null +++ b/microservices/InventoryApi/appsettings.json @@ -0,0 +1,7 @@ +{ + "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5003" } } }, + "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, + "Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, + "Logging": { "LogLevel": { "Default": "Information" } }, + "AllowedHosts": "*" +} diff --git a/microservices/InventoryApi/k8s/deployment.yaml b/microservices/InventoryApi/k8s/deployment.yaml new file mode 100644 index 0000000..a54590b --- /dev/null +++ b/microservices/InventoryApi/k8s/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: promiscuity-inventory + labels: + app: promiscuity-inventory +spec: + replicas: 2 + selector: + matchLabels: + app: promiscuity-inventory + template: + metadata: + labels: + app: promiscuity-inventory + spec: + containers: + - name: promiscuity-inventory + image: promiscuity-inventory:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 5003 + readinessProbe: + httpGet: + path: /healthz + port: 5003 + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/microservices/InventoryApi/k8s/service.yaml b/microservices/InventoryApi/k8s/service.yaml new file mode 100644 index 0000000..8417b75 --- /dev/null +++ b/microservices/InventoryApi/k8s/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: promiscuity-inventory + labels: + app: promiscuity-inventory +spec: + selector: + app: promiscuity-inventory + type: NodePort + ports: + - name: http + port: 80 + targetPort: 5003 + nodePort: 30083 diff --git a/microservices/LocationsApi/Controllers/LocationsController.cs b/microservices/LocationsApi/Controllers/LocationsController.cs index 6c9886f..aa33f88 100644 --- a/microservices/LocationsApi/Controllers/LocationsController.cs +++ b/microservices/LocationsApi/Controllers/LocationsController.cs @@ -3,6 +3,10 @@ using LocationsApi.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MongoDB.Driver; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Text.Json; namespace LocationsApi.Controllers; @@ -11,10 +15,14 @@ namespace LocationsApi.Controllers; public class LocationsController : ControllerBase { private readonly LocationStore _locations; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IConfiguration _configuration; - public LocationsController(LocationStore locations) + public LocationsController(LocationStore locations, IHttpClientFactory httpClientFactory, IConfiguration configuration) { _locations = locations; + _httpClientFactory = httpClientFactory; + _configuration = configuration; } [HttpPost] @@ -85,4 +93,67 @@ public class LocationsController : ControllerBase return Ok("Updated"); } + + [HttpPost("{id}/gather")] + [Authorize(Roles = "USER,SUPER")] + public async Task Gather(string id, [FromBody] GatherResourceRequest req) + { + if (string.IsNullOrWhiteSpace(req.CharacterId)) + return BadRequest("characterId required"); + if (string.IsNullOrWhiteSpace(req.ResourceKey)) + return BadRequest("resourceKey required"); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrWhiteSpace(userId)) + return Unauthorized(); + + var allowAnyOwner = User.IsInRole("SUPER"); + var gather = await _locations.GatherResourceAsync(id, req.CharacterId, req.ResourceKey, userId, allowAnyOwner); + if (gather.Status == GatherStatus.LocationNotFound) + return NotFound("Location not found"); + if (gather.Status == GatherStatus.CharacterNotFound) + return NotFound("Character not found"); + if (gather.Status == GatherStatus.Forbidden) + return Forbid(); + if (gather.Status == GatherStatus.Invalid) + return BadRequest("Character is not at the target location"); + if (gather.Status == GatherStatus.ResourceNotFound) + return NotFound("Resource not found at location"); + if (gather.Status == GatherStatus.ResourceDepleted) + return Conflict("Resource is depleted"); + + var inventoryBaseUrl = (_configuration["Services:InventoryApiBaseUrl"] ?? "http://localhost:5003").TrimEnd('/'); + var token = Request.Headers.Authorization.ToString(); + var grantBody = JsonSerializer.Serialize(new + { + itemKey = gather.ResourceKey, + quantity = gather.QuantityGranted + }); + + var client = _httpClientFactory.CreateClient(); + using var request = new HttpRequestMessage( + HttpMethod.Post, + $"{inventoryBaseUrl}/api/inventory/by-owner/character/{req.CharacterId}/grant"); + request.Content = new StringContent(grantBody, Encoding.UTF8, "application/json"); + if (!string.IsNullOrWhiteSpace(token)) + request.Headers.Authorization = AuthenticationHeaderValue.Parse(token); + + using var response = await client.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + await _locations.RestoreGatheredResourceAsync(id, gather.ResourceKey, gather.QuantityGranted); + return StatusCode((int)response.StatusCode, responseBody); + } + + return Ok(new GatherResourceResponse + { + LocationId = id, + CharacterId = req.CharacterId, + ResourceKey = gather.ResourceKey, + QuantityGranted = gather.QuantityGranted, + RemainingQuantity = gather.RemainingQuantity, + InventoryResponseJson = responseBody + }); + } } diff --git a/microservices/LocationsApi/DOCUMENTS.md b/microservices/LocationsApi/DOCUMENTS.md index 0df5336..9b85cb9 100644 --- a/microservices/LocationsApi/DOCUMENTS.md +++ b/microservices/LocationsApi/DOCUMENTS.md @@ -21,6 +21,13 @@ Inbound JSON documents } ``` `coord` cannot be updated. +- GatherResourceRequest (`POST /api/locations/{id}/gather`) + ```json + { + "characterId": "string (Character ObjectId)", + "resourceKey": "wood" + } + ``` Stored documents (MongoDB) - Location @@ -32,6 +39,26 @@ Stored documents (MongoDB) "x": 0, "y": 0 }, + "resources": [ + { + "itemKey": "wood", + "remainingQuantity": 100, + "gatherQuantity": 3 + } + ], "createdUtc": "string (ISO-8601 datetime)" } ``` + +Outbound JSON documents +- GatherResourceResponse (`POST /api/locations/{id}/gather`) + ```json + { + "locationId": "string (ObjectId)", + "characterId": "string (ObjectId)", + "resourceKey": "wood", + "quantityGranted": 3, + "remainingQuantity": 97, + "inventoryResponseJson": "string (raw InventoryApi response JSON)" + } + ``` diff --git a/microservices/LocationsApi/Models/GatherResourceRequest.cs b/microservices/LocationsApi/Models/GatherResourceRequest.cs new file mode 100644 index 0000000..ec7a07f --- /dev/null +++ b/microservices/LocationsApi/Models/GatherResourceRequest.cs @@ -0,0 +1,8 @@ +namespace LocationsApi.Models; + +public class GatherResourceRequest +{ + public string CharacterId { get; set; } = string.Empty; + + public string ResourceKey { get; set; } = string.Empty; +} diff --git a/microservices/LocationsApi/Models/GatherResourceResponse.cs b/microservices/LocationsApi/Models/GatherResourceResponse.cs new file mode 100644 index 0000000..092d5c2 --- /dev/null +++ b/microservices/LocationsApi/Models/GatherResourceResponse.cs @@ -0,0 +1,16 @@ +namespace LocationsApi.Models; + +public class GatherResourceResponse +{ + public string LocationId { get; set; } = string.Empty; + + public string CharacterId { get; set; } = string.Empty; + + public string ResourceKey { get; set; } = string.Empty; + + public int QuantityGranted { get; set; } + + public int RemainingQuantity { get; set; } + + public string InventoryResponseJson { get; set; } = string.Empty; +} diff --git a/microservices/LocationsApi/Models/Location.cs b/microservices/LocationsApi/Models/Location.cs index 462e763..f477932 100644 --- a/microservices/LocationsApi/Models/Location.cs +++ b/microservices/LocationsApi/Models/Location.cs @@ -15,6 +15,9 @@ public class Location [BsonElement("coord")] public required Coord Coord { get; set; } + [BsonElement("resources")] + public List Resources { get; set; } = []; + [BsonElement("createdUtc")] public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; } diff --git a/microservices/LocationsApi/Models/LocationResource.cs b/microservices/LocationsApi/Models/LocationResource.cs new file mode 100644 index 0000000..8d955ca --- /dev/null +++ b/microservices/LocationsApi/Models/LocationResource.cs @@ -0,0 +1,15 @@ +using MongoDB.Bson.Serialization.Attributes; + +namespace LocationsApi.Models; + +public class LocationResource +{ + [BsonElement("itemKey")] + public string ItemKey { get; set; } = string.Empty; + + [BsonElement("remainingQuantity")] + public int RemainingQuantity { get; set; } + + [BsonElement("gatherQuantity")] + public int GatherQuantity { get; set; } = 1; +} diff --git a/microservices/LocationsApi/Program.cs b/microservices/LocationsApi/Program.cs index c4da181..fdfbe7c 100644 --- a/microservices/LocationsApi/Program.cs +++ b/microservices/LocationsApi/Program.cs @@ -6,6 +6,7 @@ using System.Text; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); +builder.Services.AddHttpClient(); // DI builder.Services.AddSingleton(); diff --git a/microservices/LocationsApi/Services/LocationStore.cs b/microservices/LocationsApi/Services/LocationStore.cs index 27e753f..570dbda 100644 --- a/microservices/LocationsApi/Services/LocationStore.cs +++ b/microservices/LocationsApi/Services/LocationStore.cs @@ -4,28 +4,29 @@ using MongoDB.Driver; namespace LocationsApi.Services; -public class LocationStore -{ - private readonly IMongoCollection _col; - - public LocationStore(IConfiguration cfg) - { - var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; - var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb"; +public class LocationStore +{ + private readonly IMongoCollection _col; + private readonly IMongoCollection _rawCol; + private readonly IMongoCollection _characters; + private const string CoordIndexName = "coord_x_1_coord_y_1"; + + public LocationStore(IConfiguration cfg) + { + var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; + var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb"; var client = new MongoClient(cs); - var db = client.GetDatabase(dbName); - var collectionName = "Locations"; - EnsureLocationSchema(db, collectionName); - _col = db.GetCollection(collectionName); - - var coordIndex = Builders.IndexKeys - .Ascending(l => l.Coord.X) - .Ascending(l => l.Coord.Y); - var coordIndexOptions = new CreateIndexOptions { Unique = true }; - _col.Indexes.CreateOne(new CreateIndexModel(coordIndex, coordIndexOptions)); - - EnsureOriginLocation(); - } + var db = client.GetDatabase(dbName); + var collectionName = "Locations"; + EnsureLocationSchema(db, collectionName); + _col = db.GetCollection(collectionName); + _rawCol = db.GetCollection(collectionName); + _characters = db.GetCollection("Characters"); + + EnsureCoordIndexes(); + + EnsureOriginLocation(); + } private static void EnsureLocationSchema(IMongoDatabase db, string collectionName) { @@ -35,11 +36,11 @@ public class LocationStore "$jsonSchema", new BsonDocument { { "bsonType", "object" }, - { "required", new BsonArray { "name", "coord", "createdUtc" } }, - { - "properties", new BsonDocument - { - { "name", new BsonDocument { { "bsonType", "string" } } }, + { "required", new BsonArray { "name", "coord", "createdUtc" } }, + { + "properties", new BsonDocument + { + { "name", new BsonDocument { { "bsonType", "string" } } }, { "coord", new BsonDocument { @@ -53,10 +54,31 @@ public class LocationStore } } } - }, - { "createdUtc", new BsonDocument { { "bsonType", "date" } } } - } - } + }, + { + "resources", new BsonDocument + { + { "bsonType", new BsonArray { "array", "null" } }, + { + "items", new BsonDocument + { + { "bsonType", "object" }, + { "required", new BsonArray { "itemKey", "remainingQuantity", "gatherQuantity" } }, + { + "properties", new BsonDocument + { + { "itemKey", new BsonDocument { { "bsonType", "string" } } }, + { "remainingQuantity", new BsonDocument { { "bsonType", "int" }, { "minimum", 0 } } }, + { "gatherQuantity", new BsonDocument { { "bsonType", "int" }, { "minimum", 1 } } } + } + } + } + } + } + }, + { "createdUtc", new BsonDocument { { "bsonType", "date" } } } + } + } } } }; @@ -95,30 +117,123 @@ public class LocationStore return result.DeletedCount > 0; } - public async Task UpdateNameAsync(string id, string name) - { - var filter = Builders.Filter.Eq(l => l.Id, id); - var update = Builders.Update.Set(l => l.Name, name); - var result = await _col.UpdateOneAsync(filter, update); - return result.ModifiedCount > 0; - } - - private void EnsureOriginLocation() - { - var filter = Builders.Filter.And( - Builders.Filter.Eq(l => l.Coord.X, 0), + public async Task UpdateNameAsync(string id, string name) + { + var filter = Builders.Filter.Eq(l => l.Id, id); + var update = Builders.Update.Set(l => l.Name, name); + var result = await _col.UpdateOneAsync(filter, update); + return result.ModifiedCount > 0; + } + + public sealed record GatherResult(GatherStatus Status, string ResourceKey = "", int QuantityGranted = 0, int RemainingQuantity = 0); + + public async Task GatherResourceAsync(string locationId, string characterId, string resourceKey, string userId, bool allowAnyOwner) + { + var normalizedKey = resourceKey.Trim().ToLowerInvariant(); + var location = await _col.Find(l => l.Id == locationId).FirstOrDefaultAsync(); + if (location is null) + return new GatherResult(GatherStatus.LocationNotFound); + + var character = await _characters.Find(c => c.Id == characterId).FirstOrDefaultAsync(); + if (character is null) + return new GatherResult(GatherStatus.CharacterNotFound); + if (!allowAnyOwner && character.OwnerUserId != userId) + return new GatherResult(GatherStatus.Forbidden); + if (character.Coord.X != location.Coord.X || character.Coord.Y != location.Coord.Y) + return new GatherResult(GatherStatus.Invalid); + + var resource = location.Resources.FirstOrDefault(r => NormalizeItemKey(r.ItemKey) == normalizedKey); + if (resource is null) + return new GatherResult(GatherStatus.ResourceNotFound); + if (resource.RemainingQuantity <= 0) + return new GatherResult(GatherStatus.ResourceDepleted); + + var quantityGranted = Math.Min(resource.GatherQuantity, resource.RemainingQuantity); + var filter = Builders.Filter.And( + Builders.Filter.Eq(l => l.Id, locationId), + Builders.Filter.ElemMatch(l => l.Resources, r => r.ItemKey == resource.ItemKey && r.RemainingQuantity >= quantityGranted) + ); + var update = Builders.Update.Inc("resources.$.remainingQuantity", -quantityGranted); + var result = await _col.UpdateOneAsync(filter, update); + if (result.ModifiedCount == 0) + return new GatherResult(GatherStatus.ResourceDepleted); + + return new GatherResult( + GatherStatus.Ok, + resource.ItemKey, + quantityGranted, + resource.RemainingQuantity - quantityGranted); + } + + public async Task RestoreGatheredResourceAsync(string locationId, string resourceKey, int quantity) + { + var normalizedKey = NormalizeItemKey(resourceKey); + var filter = Builders.Filter.And( + Builders.Filter.Eq(l => l.Id, locationId), + Builders.Filter.ElemMatch(l => l.Resources, r => r.ItemKey == normalizedKey) + ); + var update = Builders.Update.Inc("resources.$.remainingQuantity", quantity); + await _col.UpdateOneAsync(filter, update); + } + + private void EnsureCoordIndexes() + { + var indexes = _rawCol.Indexes.List().ToList(); + foreach (var index in indexes) + { + var name = index.GetValue("name", "").AsString; + if (name == "_id_") + continue; + + var keyDoc = index.GetValue("key", new BsonDocument()).AsBsonDocument; + if (IsLegacyCoordIndex(keyDoc) || IsUnexpectedCoordIndex(keyDoc)) + _rawCol.Indexes.DropOne(name); + } + + var coordIndex = new BsonDocument + { + { "coord.x", 1 }, + { "coord.y", 1 } + }; + var coordIndexOptions = new CreateIndexOptions { Unique = true, Name = CoordIndexName }; + _rawCol.Indexes.CreateOne(new CreateIndexModel(coordIndex, coordIndexOptions)); + } + + private static bool IsLegacyCoordIndex(BsonDocument keyDoc) => + keyDoc.ElementCount == 2 && + keyDoc.TryGetValue("Coord.X", out var xValue) && + xValue.IsInt32 && xValue.AsInt32 == 1 && + keyDoc.TryGetValue("Coord.Y", out var yValue) && + yValue.IsInt32 && yValue.AsInt32 == 1; + + private static bool IsUnexpectedCoordIndex(BsonDocument keyDoc) + { + var hasLower = keyDoc.Contains("coord.x") || keyDoc.Contains("coord.y"); + var hasUpper = keyDoc.Contains("Coord.X") || keyDoc.Contains("Coord.Y"); + return hasUpper || (hasLower && !(keyDoc.Contains("coord.x") && keyDoc.Contains("coord.y"))); + } + + private void EnsureOriginLocation() + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(l => l.Coord.X, 0), Builders.Filter.Eq(l => l.Coord.Y, 0) ); var existing = _col.Find(filter).FirstOrDefault(); if (existing is not null) return; - var origin = new Location - { - Name = "Origin", - Coord = new Coord { X = 0, Y = 0 }, - CreatedUtc = DateTime.UtcNow - }; + var origin = new Location + { + Name = "Origin", + Coord = new Coord { X = 0, Y = 0 }, + Resources = + [ + new LocationResource { ItemKey = "wood", RemainingQuantity = 100, GatherQuantity = 3 }, + new LocationResource { ItemKey = "grass", RemainingQuantity = 500, GatherQuantity = 10 } + ], + CreatedUtc = DateTime.UtcNow + }; try { @@ -128,5 +243,30 @@ public class LocationStore { // Another instance seeded it first. } - } -} + } + + private static string NormalizeItemKey(string itemKey) => itemKey.Trim().ToLowerInvariant(); + + [MongoDB.Bson.Serialization.Attributes.BsonIgnoreExtraElements] + private class CharacterDocument + { + [MongoDB.Bson.Serialization.Attributes.BsonId] + [MongoDB.Bson.Serialization.Attributes.BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)] + public string? Id { get; set; } + + public string OwnerUserId { get; set; } = string.Empty; + + public Coord Coord { get; set; } = new(); + } +} + +public enum GatherStatus +{ + Ok, + LocationNotFound, + CharacterNotFound, + Forbidden, + Invalid, + ResourceNotFound, + ResourceDepleted +} diff --git a/microservices/LocationsApi/appsettings.Development.json b/microservices/LocationsApi/appsettings.Development.json index 07f3f94..c478bd3 100644 --- a/microservices/LocationsApi/appsettings.Development.json +++ b/microservices/LocationsApi/appsettings.Development.json @@ -1,6 +1,11 @@ { - "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5002" } } }, - "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, - "Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, - "Logging": { "LogLevel": { "Default": "Information" } } + "Services": { + "InventoryApiBaseUrl": "http://localhost:5003" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } } diff --git a/microservices/LocationsApi/appsettings.json b/microservices/LocationsApi/appsettings.json index d67c59f..45d1ba6 100644 --- a/microservices/LocationsApi/appsettings.json +++ b/microservices/LocationsApi/appsettings.json @@ -1,6 +1,7 @@ { "Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5002" } } }, "MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" }, + "Services": { "InventoryApiBaseUrl": "https://pinv.ranaze.com" }, "Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" }, "Logging": { "LogLevel": { "Default": "Information" } }, "AllowedHosts": "*" diff --git a/microservices/README.md b/microservices/README.md index f849d14..bb28261 100644 --- a/microservices/README.md +++ b/microservices/README.md @@ -3,10 +3,12 @@ ## Document shapes - AuthApi: `AuthApi/DOCUMENTS.md` (auth request payloads and user document shape) - CharacterApi: `CharacterApi/DOCUMENTS.md` (character create payload and stored document) +- InventoryApi: `InventoryApi/DOCUMENTS.md` (inventory mutation payloads and stored document) - LocationsApi: `LocationsApi/DOCUMENTS.md` (location create/update payloads and stored document) ## Service READMEs - AuthApi: `AuthApi/README.md` - CharacterApi: `CharacterApi/README.md` +- InventoryApi: `InventoryApi/README.md` - LocationsApi: `LocationsApi/README.md` diff --git a/microservices/micro-services.sln b/microservices/micro-services.sln index 4d1a08d..685a13a 100644 --- a/microservices/micro-services.sln +++ b/microservices/micro-services.sln @@ -8,11 +8,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CharacterApi", "CharacterAp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocationsApi", "LocationsApi\LocationsApi.csproj", "{C343AFFB-9AB0-4B70-834C-3D2A21E2B506}" EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InventoryApi", "InventoryApi\InventoryApi.csproj", "{72AA73C5-6A42-4F79-ADCC-19F10A66B9E0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {334F3B23-EFE8-6F1A-5E5F-9A2275D56E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {334F3B23-EFE8-6F1A-5E5F-9A2275D56E28}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -26,7 +28,11 @@ Global {C343AFFB-9AB0-4B70-834C-3D2A21E2B506}.Debug|Any CPU.Build.0 = Debug|Any CPU {C343AFFB-9AB0-4B70-834C-3D2A21E2B506}.Release|Any CPU.ActiveCfg = Release|Any CPU {C343AFFB-9AB0-4B70-834C-3D2A21E2B506}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection + {72AA73C5-6A42-4F79-ADCC-19F10A66B9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72AA73C5-6A42-4F79-ADCC-19F10A66B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72AA73C5-6A42-4F79-ADCC-19F10A66B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72AA73C5-6A42-4F79-ADCC-19F10A66B9E0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection