Compare commits
31
Commits
LAWDev
..
5fbc4a88ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fbc4a88ec | ||
|
|
581734d740 | ||
|
|
1f7cf9f26c | ||
|
|
b0573be749 | ||
|
|
6e66dccf78 | ||
|
|
07b57dc1f7 | ||
|
|
3271059a97 | ||
|
|
d9eededbfa | ||
|
|
9a56091e8e | ||
|
|
acfcd09fe1 | ||
|
|
3bc4668cbe | ||
|
|
85711e42cd | ||
|
|
59e33760ad | ||
|
|
f3154deba6 | ||
|
|
b1ca87c681 | ||
|
|
f3f90ec900 | ||
|
|
cc3cab5451 | ||
|
|
c3f740548d | ||
|
|
1d1bb1bf4f | ||
|
|
337e26c4ed | ||
|
|
cbdea22143 | ||
|
|
0d3686ed37 | ||
|
|
ca898b0d09 | ||
|
|
e013ef7875 | ||
|
|
4256b2001f | ||
|
|
695c8de124 | ||
|
|
0e03ced3c6 | ||
|
|
8feb7bbc47 | ||
|
|
d751b677c3 | ||
|
|
f87278323d | ||
|
|
5893123adb |
@@ -0,0 +1,104 @@
|
|||||||
|
name: Deploy Promiscuity Auth API
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: promiscuity-auth:latest
|
||||||
|
IMAGE_TAR: /tmp/promiscuity-auth.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/AuthApi
|
||||||
|
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-auth.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-auth.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-auth.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
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Apply Kubernetes manifests
|
||||||
|
# (You create these files in your repo)
|
||||||
|
# -----------------------------
|
||||||
|
- name: Apply Auth deployment & service
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kube/config
|
||||||
|
run: |
|
||||||
|
kubectl apply -f microservices/AuthApi/k8s/deployment.yaml -n promiscuity-auth
|
||||||
|
kubectl apply -f microservices/AuthApi/k8s/service.yaml -n promiscuity-auth
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Rollout restart & wait
|
||||||
|
# -----------------------------
|
||||||
|
- name: Restart Auth deployment
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kube/config
|
||||||
|
run: |
|
||||||
|
kubectl rollout restart deployment/promiscuity-auth -n promiscuity-auth
|
||||||
|
kubectl rollout status deployment/promiscuity-auth -n promiscuity-auth
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
name: Deploy Promiscuity Character API
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: promiscuity-character:latest
|
||||||
|
IMAGE_TAR: /tmp/promiscuity-character.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/CharacterApi
|
||||||
|
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-character.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-character.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-character.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
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Apply Kubernetes manifests
|
||||||
|
# -----------------------------
|
||||||
|
- name: Apply Character deployment & service
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kube/config
|
||||||
|
run: |
|
||||||
|
kubectl apply -f microservices/CharacterApi/k8s/deployment.yaml -n promiscuity-character
|
||||||
|
kubectl apply -f microservices/CharacterApi/k8s/service.yaml -n promiscuity-character
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Rollout restart & wait
|
||||||
|
# -----------------------------
|
||||||
|
- name: Restart Character deployment
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kube/config
|
||||||
|
run: |
|
||||||
|
kubectl rollout restart deployment/promiscuity-character -n promiscuity-character
|
||||||
|
kubectl rollout status deployment/promiscuity-character -n promiscuity-character
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: k8s smoke test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Write kubeconfig from secret
|
||||||
|
env:
|
||||||
|
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/kube
|
||||||
|
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||||
|
|
||||||
|
- name: Test kubectl connectivity
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kube/config
|
||||||
|
run: |
|
||||||
|
kubectl get nodes --kubeconfig "${KUBECONFIG}"
|
||||||
+6
-8
@@ -1,9 +1,7 @@
|
|||||||
Perforce Connection String: ssl:perforce.ranaze.com:443
|
Auth Microservice swagger is accessible at https://pauth.ranaze.com/swagger/index.html
|
||||||
|
|
||||||
Git ↔ Perforce quick map
|
Test Users:
|
||||||
repo → depot (//depot/...)
|
SUPER/SUPER - Super User
|
||||||
branch → stream (e.g., //depot/main, //depot/alice)
|
test1/test1 - Super User
|
||||||
clone → workspace/client (local view + metadata)
|
test3/test3 - User
|
||||||
commit → changelist (pending → submitted)
|
|
||||||
PR/MR → shelve + review (with Swarm) or merge up from task stream
|
|
||||||
.gitignore → P4IGNORE file (e.g., .p4ignore)
|
|
||||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="oggvorbisstr"
|
||||||
|
type="AudioStreamOggVorbis"
|
||||||
|
uid="uid://de2e8sy4x724m"
|
||||||
|
path="res://.godot/imported/jump.ogg-09aff86a6f79a8fce2febb69902962cf.oggvorbisstr"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/audio/jump.ogg"
|
||||||
|
dest_files=["res://.godot/imported/jump.ogg-09aff86a6f79a8fce2febb69902962cf.oggvorbisstr"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
loop=false
|
||||||
|
loop_offset=0
|
||||||
|
bpm=0
|
||||||
|
beat_count=0
|
||||||
|
bar_beats=4
|
||||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="oggvorbisstr"
|
||||||
|
type="AudioStreamOggVorbis"
|
||||||
|
uid="uid://64dplcgx2icb"
|
||||||
|
path="res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/audio/silly-menu-hover-test.ogg"
|
||||||
|
dest_files=["res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
loop=false
|
||||||
|
loop_offset=0
|
||||||
|
bpm=0
|
||||||
|
beat_count=0
|
||||||
|
bar_beats=4
|
||||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="oggvorbisstr"
|
||||||
|
type="AudioStreamOggVorbis"
|
||||||
|
uid="uid://txgki0ijeuud"
|
||||||
|
path="res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/audio/silly-test.ogg"
|
||||||
|
dest_files=["res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
loop=false
|
||||||
|
loop_offset=0
|
||||||
|
bpm=0
|
||||||
|
beat_count=0
|
||||||
|
bar_beats=4
|
||||||
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
@@ -0,0 +1,30 @@
|
|||||||
|
# weapon.gd
|
||||||
|
extends Resource
|
||||||
|
class_name iWeapon
|
||||||
|
# This acts as an interface base class.
|
||||||
|
|
||||||
|
# --- Common Stats ---
|
||||||
|
@export var weapon_name: String = "Unnamed Weapon"
|
||||||
|
@export var damage: float = 10.0
|
||||||
|
@export var attack_speed: float = 1.0 # attacks per second
|
||||||
|
@export var range: float = 1.5 # meters; melee = short, ranged = long
|
||||||
|
@export var knockback: float = 0.0
|
||||||
|
@export var stamina_cost: float = 5.0
|
||||||
|
|
||||||
|
# --- Ranged‑specific Stats ---
|
||||||
|
@export var projectile_scene: PackedScene # null for melee
|
||||||
|
@export var projectile_speed: float = 20.0
|
||||||
|
@export var ammo_type: String = "" # e.g. "arrows", "bullets"
|
||||||
|
@export var ammo_per_shot: int = 1
|
||||||
|
|
||||||
|
# --- Melee‑specific Stats ---
|
||||||
|
@export var swing_arc: float = 90.0 # degrees
|
||||||
|
@export var hitbox_size: float = 1.0
|
||||||
|
|
||||||
|
# --- Interface Methods ---
|
||||||
|
func attack(user):
|
||||||
|
push_error("Weapon.attack() not implemented in subclass")
|
||||||
|
return null
|
||||||
|
|
||||||
|
func can_attack(user) -> bool:
|
||||||
|
return true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bttaq8w3plgqh
|
||||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bb6hj6l23043x"
|
||||||
|
path="res://.godot/imported/human.blend-738fbf7b85a13f54d00c9db65cf59296.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/human.blend"
|
||||||
|
dest_files=["res://.godot/imported/human.blend-738fbf7b85a13f54d00c9db65cf59296.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
blender/nodes/visible=0
|
||||||
|
blender/nodes/active_collection_only=false
|
||||||
|
blender/nodes/punctual_lights=true
|
||||||
|
blender/nodes/cameras=true
|
||||||
|
blender/nodes/custom_properties=true
|
||||||
|
blender/nodes/modifiers=1
|
||||||
|
blender/meshes/colors=false
|
||||||
|
blender/meshes/uvs=true
|
||||||
|
blender/meshes/normals=true
|
||||||
|
blender/meshes/export_geometry_nodes_instances=false
|
||||||
|
blender/meshes/tangents=true
|
||||||
|
blender/meshes/skins=2
|
||||||
|
blender/meshes/export_bones_deforming_mesh_only=false
|
||||||
|
blender/materials/unpack_enabled=true
|
||||||
|
blender/materials/export_materials=1
|
||||||
|
blender/animation/limit_playback=true
|
||||||
|
blender/animation/always_sample=true
|
||||||
|
blender/animation/group_tracks=true
|
||||||
|
gltf/naming_version=2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[gd_scene format=3 uid="uid://cuyws13lbkmxb"]
|
||||||
|
|
||||||
|
[node name="Test" type="Node2D"]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_resource type="AudioBusLayout" format=3]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
bus/0/name = "Master"
|
||||||
|
bus/0/volume_db = 0.0
|
||||||
|
bus/0/send = ""
|
||||||
|
bus/1/name = "Music"
|
||||||
|
bus/1/volume_db = 0.0
|
||||||
|
bus/1/send = "Master"
|
||||||
|
bus/2/name = "SFX"
|
||||||
|
bus/2/volume_db = 0.0
|
||||||
|
bus/2/send = "Master"
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
[preset.0]
|
||||||
|
|
||||||
|
name="Windows Desktop"
|
||||||
|
platform="Windows Desktop"
|
||||||
|
runnable=true
|
||||||
|
advanced_options=false
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter=""
|
||||||
|
export_path="bin/test.exe"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.0.options]
|
||||||
|
|
||||||
|
custom_template/debug=""
|
||||||
|
custom_template/release=""
|
||||||
|
debug/export_console_wrapper=1
|
||||||
|
binary_format/embed_pck=false
|
||||||
|
texture_format/s3tc_bptc=true
|
||||||
|
texture_format/etc2_astc=false
|
||||||
|
shader_baker/enabled=false
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
codesign/enable=false
|
||||||
|
codesign/timestamp=true
|
||||||
|
codesign/timestamp_server_url=""
|
||||||
|
codesign/digest_algorithm=1
|
||||||
|
codesign/description=""
|
||||||
|
codesign/custom_options=PackedStringArray()
|
||||||
|
application/modify_resources=true
|
||||||
|
application/icon=""
|
||||||
|
application/console_wrapper_icon=""
|
||||||
|
application/icon_interpolation=4
|
||||||
|
application/file_version=""
|
||||||
|
application/product_version=""
|
||||||
|
application/company_name=""
|
||||||
|
application/product_name=""
|
||||||
|
application/file_description=""
|
||||||
|
application/copyright=""
|
||||||
|
application/trademarks=""
|
||||||
|
application/export_angle=0
|
||||||
|
application/export_d3d12=0
|
||||||
|
application/d3d12_agility_sdk_multiarch=true
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
ssh_remote_deploy/host="user@host_ip"
|
||||||
|
ssh_remote_deploy/port="22"
|
||||||
|
ssh_remote_deploy/extra_args_ssh=""
|
||||||
|
ssh_remote_deploy/extra_args_scp=""
|
||||||
|
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||||
|
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||||
|
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||||
|
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||||
|
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||||
|
Start-ScheduledTask -TaskName godot_remote_debug
|
||||||
|
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||||
|
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||||
|
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||||
|
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||||
|
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||||
|
dotnet/include_scripts_content=false
|
||||||
|
dotnet/include_debug_symbols=true
|
||||||
|
dotnet/embed_build_outputs=false
|
||||||
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 995 B |
@@ -15,6 +15,17 @@ run/main_scene="uid://b4k81taauef4q"
|
|||||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[audio]
|
||||||
|
|
||||||
|
bus_layout="res://audio/bus_layout.tres"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
MenuMusic="*res://scenes/UI/menu_music.tscn"
|
||||||
|
MenuSfx="*res://scenes/UI/menu_sfx.tscn"
|
||||||
|
AuthState="*res://scenes/UI/auth_state.gd"
|
||||||
|
CharacterService="*res://scenes/UI/character_service.gd"
|
||||||
|
|
||||||
[dotnet]
|
[dotnet]
|
||||||
|
|
||||||
project/assembly_name="Promiscuity"
|
project/assembly_name="Promiscuity"
|
||||||
@@ -53,3 +64,8 @@ ui_down={
|
|||||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
player_light={
|
||||||
|
"deadzone": 0.2,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
@export var left_pupil_path: NodePath = NodePath("Body/HeadPivot/EyeLeft/Pupil")
|
||||||
|
@export var right_pupil_path: NodePath = NodePath("Body/HeadPivot/EyeRight/Pupil")
|
||||||
|
@export var camera_path: NodePath
|
||||||
|
@export var look_origin_path: NodePath = NodePath("Body/HeadPivot")
|
||||||
|
@export var look_reference_path: NodePath = NodePath("Body")
|
||||||
|
@export var lock_vertical: bool = true
|
||||||
|
@export var vertical_unlock_height: float = 0.6
|
||||||
|
@export var vertical_lock_smooth_speed: float = 6.0
|
||||||
|
@export var vertical_lock_hold_time: float = 0.3
|
||||||
|
@export var max_look_angle_deg: float = 90.0
|
||||||
|
@export var eye_return_speed: float = 0.2
|
||||||
|
@export var max_offset: float = 0.08
|
||||||
|
@export var side_eye_boost: float = 1.4
|
||||||
|
@export var head_path: NodePath = NodePath("Body/HeadPivot")
|
||||||
|
@export var head_turn_speed: float = 16.0
|
||||||
|
@export var head_max_yaw_deg: float = 55.0
|
||||||
|
@export var head_max_pitch_deg: float = 22.0
|
||||||
|
|
||||||
|
var _left_pupil: Node3D
|
||||||
|
var _right_pupil: Node3D
|
||||||
|
var _left_base: Vector3
|
||||||
|
var _right_base: Vector3
|
||||||
|
var _camera: Camera3D
|
||||||
|
var _look_origin: Node3D
|
||||||
|
var _head: Node3D
|
||||||
|
var _head_base_rot: Vector3
|
||||||
|
var _vertical_lock_factor: float = 1.0
|
||||||
|
var _vertical_hold_timer: float = 0.0
|
||||||
|
var _look_reference: Node3D
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_left_pupil = get_node_or_null(left_pupil_path) as Node3D
|
||||||
|
_right_pupil = get_node_or_null(right_pupil_path) as Node3D
|
||||||
|
if _left_pupil:
|
||||||
|
_left_base = _left_pupil.position
|
||||||
|
if _right_pupil:
|
||||||
|
_right_base = _right_pupil.position
|
||||||
|
_camera = _resolve_camera()
|
||||||
|
_look_origin = get_node_or_null(look_origin_path) as Node3D
|
||||||
|
_look_reference = get_node_or_null(look_reference_path) as Node3D
|
||||||
|
_head = get_node_or_null(head_path) as Node3D
|
||||||
|
if _head:
|
||||||
|
_head_base_rot = _head.rotation
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(_delta: float) -> void:
|
||||||
|
_update_pupils()
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
_update_pupils()
|
||||||
|
|
||||||
|
|
||||||
|
func _update_pupils() -> void:
|
||||||
|
if _camera == null or not _camera.is_inside_tree():
|
||||||
|
_camera = _resolve_camera()
|
||||||
|
if _camera == null:
|
||||||
|
return
|
||||||
|
var origin := _look_origin
|
||||||
|
if origin == null:
|
||||||
|
origin = self
|
||||||
|
var target := _camera.global_position
|
||||||
|
var dir_world := target - origin.global_position
|
||||||
|
if dir_world.length_squared() <= 0.0001:
|
||||||
|
return
|
||||||
|
dir_world = dir_world.normalized()
|
||||||
|
var reference := _look_reference if _look_reference != null else origin
|
||||||
|
var forward := -reference.global_basis.z
|
||||||
|
var min_dot := cos(deg_to_rad(max_look_angle_deg))
|
||||||
|
var can_look := dir_world.dot(forward) >= min_dot
|
||||||
|
if not can_look:
|
||||||
|
var delta := get_process_delta_time()
|
||||||
|
if _left_pupil:
|
||||||
|
var target_left := Vector3(_left_base.x, _left_base.y, _left_base.z)
|
||||||
|
var pos_left := _left_pupil.position
|
||||||
|
pos_left.x = move_toward(pos_left.x, target_left.x, eye_return_speed * delta)
|
||||||
|
pos_left.y = target_left.y
|
||||||
|
pos_left.z = move_toward(pos_left.z, target_left.z, eye_return_speed * delta)
|
||||||
|
_left_pupil.position = pos_left
|
||||||
|
if _right_pupil:
|
||||||
|
var target_right := Vector3(_right_base.x, _right_base.y, _right_base.z)
|
||||||
|
var pos_right := _right_pupil.position
|
||||||
|
pos_right.x = move_toward(pos_right.x, target_right.x, eye_return_speed * delta)
|
||||||
|
pos_right.y = target_right.y
|
||||||
|
pos_right.z = move_toward(pos_right.z, target_right.z, eye_return_speed * delta)
|
||||||
|
_right_pupil.position = pos_right
|
||||||
|
if _head:
|
||||||
|
_head.rotation.x = _head_base_rot.x
|
||||||
|
_head.rotation.y = lerp_angle(_head.rotation.y, _head_base_rot.y, head_turn_speed * delta)
|
||||||
|
return
|
||||||
|
if lock_vertical:
|
||||||
|
var origin_y := origin.global_position.y
|
||||||
|
var target_offset := target.y - origin_y
|
||||||
|
var is_above := target_offset > vertical_unlock_height
|
||||||
|
if is_above:
|
||||||
|
_vertical_hold_timer = vertical_lock_hold_time
|
||||||
|
else:
|
||||||
|
_vertical_hold_timer = max(0.0, _vertical_hold_timer - get_process_delta_time())
|
||||||
|
if is_above:
|
||||||
|
_vertical_lock_factor = 1.0
|
||||||
|
else:
|
||||||
|
var unlock := 1.0 if _vertical_hold_timer > 0.0 else 0.0
|
||||||
|
_vertical_lock_factor = move_toward(_vertical_lock_factor, unlock, vertical_lock_smooth_speed * get_process_delta_time())
|
||||||
|
target.y = lerp(origin_y, target.y, _vertical_lock_factor)
|
||||||
|
dir_world = target - origin.global_position
|
||||||
|
if dir_world.length_squared() <= 0.0001:
|
||||||
|
return
|
||||||
|
dir_world = dir_world.normalized()
|
||||||
|
if _left_pupil:
|
||||||
|
_update_eye(_left_pupil, _left_base, dir_world)
|
||||||
|
if _right_pupil:
|
||||||
|
_update_eye(_right_pupil, _right_base, dir_world)
|
||||||
|
if _head:
|
||||||
|
_update_head(dir_world)
|
||||||
|
|
||||||
|
|
||||||
|
func _resolve_camera() -> Camera3D:
|
||||||
|
if camera_path != NodePath(""):
|
||||||
|
var from_path := get_node_or_null(camera_path) as Camera3D
|
||||||
|
if from_path:
|
||||||
|
return from_path
|
||||||
|
var viewport_cam := get_viewport().get_camera_3d()
|
||||||
|
if viewport_cam:
|
||||||
|
return viewport_cam
|
||||||
|
var by_name := get_tree().get_root().find_child("Camera3D", true, false) as Camera3D
|
||||||
|
return by_name
|
||||||
|
|
||||||
|
|
||||||
|
func _update_eye(eye: Node3D, base_pos: Vector3, dir_world: Vector3) -> void:
|
||||||
|
var parent := eye.get_parent() as Node3D
|
||||||
|
if parent == null:
|
||||||
|
return
|
||||||
|
var dir_local := parent.global_basis.inverse() * dir_world
|
||||||
|
var flat := Vector2(dir_local.x, dir_local.y)
|
||||||
|
flat.x *= side_eye_boost
|
||||||
|
if flat.length() > 1.0:
|
||||||
|
flat = flat.normalized()
|
||||||
|
var offset := Vector3(flat.x, flat.y, 0.0) * max_offset
|
||||||
|
eye.position = base_pos + offset
|
||||||
|
|
||||||
|
|
||||||
|
func _update_head(dir_world: Vector3) -> void:
|
||||||
|
var parent := _head.get_parent() as Node3D
|
||||||
|
if parent == null:
|
||||||
|
return
|
||||||
|
var dir_local := parent.global_basis.inverse() * dir_world
|
||||||
|
var yaw := atan2(-dir_local.x, -dir_local.z)
|
||||||
|
var pitch := atan2(dir_local.y, -dir_local.z)
|
||||||
|
yaw = clamp(yaw, deg_to_rad(-head_max_yaw_deg), deg_to_rad(head_max_yaw_deg))
|
||||||
|
pitch = clamp(pitch, deg_to_rad(-head_max_pitch_deg), deg_to_rad(head_max_pitch_deg))
|
||||||
|
var target := Vector3(_head_base_rot.x + pitch, _head_base_rot.y + yaw, _head_base_rot.z)
|
||||||
|
_head.rotation.x = lerp_angle(_head.rotation.x, target.x, head_turn_speed * get_process_delta_time())
|
||||||
|
_head.rotation.y = lerp_angle(_head.rotation.y, target.y, head_turn_speed * get_process_delta_time())
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bs3eqqujhetsm
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
[gd_scene load_steps=14 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/Characters/repo_bot.gd" id="1_repo_bot"]
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_body"]
|
||||||
|
albedo_color = Color(0.78, 0.8, 0.82, 1)
|
||||||
|
metallic = 0.2
|
||||||
|
roughness = 0.35
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_accent"]
|
||||||
|
albedo_color = Color(0.25, 0.82, 0.55, 1)
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.25, 0.82, 0.55, 1)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_eye_white"]
|
||||||
|
albedo_color = Color(0.95, 0.95, 0.95, 1)
|
||||||
|
roughness = 0.2
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_pupil"]
|
||||||
|
albedo_color = Color(0.02, 0.02, 0.02, 1)
|
||||||
|
roughness = 0.8
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleMesh" id="CapsuleMesh_body"]
|
||||||
|
radius = 0.25
|
||||||
|
height = 0.6
|
||||||
|
material = SubResource("StandardMaterial3D_body")
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="SphereMesh_head"]
|
||||||
|
radius = 0.22
|
||||||
|
height = 0.44
|
||||||
|
material = SubResource("StandardMaterial3D_body")
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="SphereMesh_eye_white"]
|
||||||
|
radius = 0.075
|
||||||
|
height = 0.15
|
||||||
|
material = SubResource("StandardMaterial3D_eye_white")
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="SphereMesh_pupil"]
|
||||||
|
radius = 0.028
|
||||||
|
height = 0.056
|
||||||
|
material = SubResource("StandardMaterial3D_pupil")
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_limb"]
|
||||||
|
top_radius = 0.06
|
||||||
|
bottom_radius = 0.06
|
||||||
|
height = 0.35
|
||||||
|
material = SubResource("StandardMaterial3D_body")
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_pack"]
|
||||||
|
size = Vector3(0.26, 0.3, 0.12)
|
||||||
|
material = SubResource("StandardMaterial3D_accent")
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
|
||||||
|
radius = 0.3
|
||||||
|
height = 1.1
|
||||||
|
|
||||||
|
[node name="RepoBot" type="Node3D"]
|
||||||
|
script = ExtResource("1_repo_bot")
|
||||||
|
|
||||||
|
[node name="Body" type="StaticBody3D" parent="."]
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.55, 0)
|
||||||
|
shape = SubResource("CapsuleShape3D_body")
|
||||||
|
|
||||||
|
[node name="Torso" type="MeshInstance3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
||||||
|
mesh = SubResource("CapsuleMesh_body")
|
||||||
|
|
||||||
|
[node name="HeadPivot" type="Node3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.95, 0)
|
||||||
|
|
||||||
|
[node name="Head" type="MeshInstance3D" parent="Body/HeadPivot"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
|
||||||
|
mesh = SubResource("SphereMesh_head")
|
||||||
|
|
||||||
|
[node name="EyeLeft" type="MeshInstance3D" parent="Body/HeadPivot"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.09, 0, -0.205)
|
||||||
|
mesh = SubResource("SphereMesh_eye_white")
|
||||||
|
|
||||||
|
[node name="Pupil" type="MeshInstance3D" parent="Body/HeadPivot/EyeLeft"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.06)
|
||||||
|
mesh = SubResource("SphereMesh_pupil")
|
||||||
|
|
||||||
|
[node name="EyeRight" type="MeshInstance3D" parent="Body/HeadPivot"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.09, 0, -0.205)
|
||||||
|
mesh = SubResource("SphereMesh_eye_white")
|
||||||
|
|
||||||
|
[node name="Pupil" type="MeshInstance3D" parent="Body/HeadPivot/EyeRight"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.06)
|
||||||
|
mesh = SubResource("SphereMesh_pupil")
|
||||||
|
|
||||||
|
[node name="ArmLeft" type="MeshInstance3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.32, 0.55, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_limb")
|
||||||
|
|
||||||
|
[node name="ArmRight" type="MeshInstance3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.32, 0.55, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_limb")
|
||||||
|
|
||||||
|
[node name="LegLeft" type="MeshInstance3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.12, 0.15, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_limb")
|
||||||
|
|
||||||
|
[node name="LegRight" type="MeshInstance3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.12, 0.15, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_limb")
|
||||||
|
|
||||||
|
[node name="Backpack" type="MeshInstance3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0.22)
|
||||||
|
mesh = SubResource("BoxMesh_pack")
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
@export var day_length := 120.0 # seconds for full rotation
|
||||||
|
@export var start_light_angle := -90.0
|
||||||
|
var end_light_angle = start_light_angle + 360.0
|
||||||
|
var start_radians = start_light_angle * PI / 180
|
||||||
|
var time := 0.0
|
||||||
|
|
||||||
|
@onready var sun := $DirectionalLight3D
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
time = fmod((time + delta), day_length)
|
||||||
|
var t = time / day_length
|
||||||
|
|
||||||
|
# Rotate sun around X axis
|
||||||
|
var angle = lerp(start_light_angle, end_light_angle, t) # sunrise → sunset → night → sunrise
|
||||||
|
sun.rotation_degrees.x = angle
|
||||||
|
|
||||||
|
# Adjust intensity
|
||||||
|
var curSin = -sin((t * TAU) + start_radians)
|
||||||
|
var energy = clamp((curSin * 1.0) + 0.2, 0.0, 1.2)
|
||||||
|
sun.light_energy = energy
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://brgmxhhhtakja
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
[gd_scene load_steps=17 format=3 uid="uid://dchj6g2i8ebph"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://brgmxhhhtakja" path="res://scenes/Levels/level.gd" id="1_a4mo8"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bb6hj6l23043x" path="res://assets/models/human.blend" id="1_eg4yq"]
|
||||||
|
[ext_resource type="Script" uid="uid://bpxggc8nr6tf6" path="res://scenes/player.gd" id="1_muv8p"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c5of6aaxop1hl" path="res://scenes/block.tscn" id="2_tc7dm"]
|
||||||
|
[ext_resource type="Script" uid="uid://b7fopt7sx74g8" path="res://scenes/Levels/menu.gd" id="3_tc7dm"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/Characters/repo_bot.tscn" id="4_repo"]
|
||||||
|
|
||||||
|
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_2q6dc"]
|
||||||
|
bounce = 0.5
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_2q6dc"]
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="SphereMesh_w7c3h"]
|
||||||
|
|
||||||
|
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_w8frs"]
|
||||||
|
bounce = 0.5
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_mx8sn"]
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_2q6dc"]
|
||||||
|
size = Vector3(1080, 2, 1080)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_w7c3h"]
|
||||||
|
size = Vector3(1080, 2, 1080)
|
||||||
|
|
||||||
|
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_fi66n"]
|
||||||
|
|
||||||
|
[sub_resource type="Sky" id="Sky_a4mo8"]
|
||||||
|
sky_material = SubResource("ProceduralSkyMaterial_fi66n")
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_a4mo8"]
|
||||||
|
background_mode = 2
|
||||||
|
sky = SubResource("Sky_a4mo8")
|
||||||
|
ambient_light_source = 3
|
||||||
|
|
||||||
|
[node name="Node3D" type="Node3D"]
|
||||||
|
script = ExtResource("1_a4mo8")
|
||||||
|
|
||||||
|
[node name="human" parent="." instance=ExtResource("1_eg4yq")]
|
||||||
|
|
||||||
|
[node name="RepoBot" parent="." instance=ExtResource("4_repo")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.9426608, 0, -4.4451966)
|
||||||
|
|
||||||
|
[node name="Thing" type="RigidBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3.7986288)
|
||||||
|
physics_material_override = SubResource("PhysicsMaterial_2q6dc")
|
||||||
|
gravity_scale = 0.0
|
||||||
|
contact_monitor = true
|
||||||
|
max_contacts_reported = 5
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Thing"]
|
||||||
|
shape = SubResource("SphereShape3D_2q6dc")
|
||||||
|
debug_color = Color(0.29772994, 0.6216631, 0.28140613, 0.41960785)
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="Thing"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
mesh = SubResource("SphereMesh_w7c3h")
|
||||||
|
|
||||||
|
[node name="Player" type="RigidBody3D" parent="."]
|
||||||
|
physics_material_override = SubResource("PhysicsMaterial_w8frs")
|
||||||
|
script = ExtResource("1_muv8p")
|
||||||
|
camera_path = NodePath("Camera3D")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
|
||||||
|
shape = SubResource("SphereShape3D_mx8sn")
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="Player"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.31670225, 0)
|
||||||
|
current = true
|
||||||
|
|
||||||
|
[node name="SpotLight3D" type="SpotLight3D" parent="Player"]
|
||||||
|
|
||||||
|
[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_2q6dc")
|
||||||
|
|
||||||
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
|
||||||
|
mesh = SubResource("BoxMesh_w7c3h")
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.5, 0.8660253, 0, -0.8660253, 0.5, 0, 34, 0)
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="Starter Blocks" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="Block" parent="Starter Blocks" instance=ExtResource("2_tc7dm")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.298158, -7.0724635)
|
||||||
|
|
||||||
|
[node name="Block2" parent="Starter Blocks" instance=ExtResource("2_tc7dm")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.63255787, 2.596316, -6.980046)
|
||||||
|
|
||||||
|
[node name="Menu" type="CanvasLayer" parent="."]
|
||||||
|
process_mode = 3
|
||||||
|
visible = false
|
||||||
|
script = ExtResource("3_tc7dm")
|
||||||
|
|
||||||
|
[node name="Control" type="Control" parent="Menu"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="Menu/Control"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 8
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_top = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
anchor_bottom = 0.5
|
||||||
|
offset_left = -39.5
|
||||||
|
offset_top = -33.0
|
||||||
|
offset_right = 39.5
|
||||||
|
offset_bottom = 33.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="ContinueButton" type="Button" parent="Menu/Control/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Continue"
|
||||||
|
|
||||||
|
[node name="MainMenuButton" type="Button" parent="Menu/Control/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Main Menu"
|
||||||
|
|
||||||
|
[node name="QuitButton" type="Button" parent="Menu/Control/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Quit"
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||||
|
environment = SubResource("Environment_a4mo8")
|
||||||
|
|
||||||
|
[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"]
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
extends CanvasLayer
|
||||||
|
|
||||||
|
const START_SCREEN_SCENE := "res://scenes/UI/start_screen.tscn"
|
||||||
|
|
||||||
|
@onready var pause_menu = $Control
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_register_focus_sounds()
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
if get_tree().paused:
|
||||||
|
resume_game()
|
||||||
|
else:
|
||||||
|
pause_game()
|
||||||
|
|
||||||
|
func pause_game():
|
||||||
|
get_tree().paused = true
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
func resume_game():
|
||||||
|
get_tree().paused = false
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func _on_quit_button_pressed():
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _on_continue_button_pressed():
|
||||||
|
resume_game()
|
||||||
|
|
||||||
|
func _on_main_menu_button_pressed():
|
||||||
|
resume_game()
|
||||||
|
get_tree().change_scene_to_file(START_SCREEN_SCENE)
|
||||||
|
|
||||||
|
func _register_focus_sounds() -> void:
|
||||||
|
if pause_menu == null:
|
||||||
|
return
|
||||||
|
var vbox := pause_menu.get_node_or_null("VBoxContainer")
|
||||||
|
if vbox == null:
|
||||||
|
return
|
||||||
|
for child in vbox.get_children():
|
||||||
|
if child is BaseButton:
|
||||||
|
var button: BaseButton = child
|
||||||
|
if not button.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")):
|
||||||
|
button.focus_entered.connect(_on_menu_item_focus)
|
||||||
|
if not button.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")):
|
||||||
|
button.mouse_entered.connect(_on_menu_item_focus)
|
||||||
|
|
||||||
|
func _on_menu_item_focus() -> void:
|
||||||
|
if MenuSfx:
|
||||||
|
MenuSfx.play_hover()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b7fopt7sx74g8
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
@onready var _tab_bar: TabBar = $MarginContainer/VBoxContainer/TabBar
|
||||||
|
@onready var _tab_container: TabContainer = $MarginContainer/VBoxContainer/TabContainer
|
||||||
|
@onready var _music_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeSlider
|
||||||
|
@onready var _music_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeValue
|
||||||
|
@onready var _music_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicMuteCheckBox
|
||||||
|
@onready var _sfx_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeSlider
|
||||||
|
@onready var _sfx_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeValue
|
||||||
|
@onready var _sfx_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxMuteCheckBox
|
||||||
|
@onready var _menu_music: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuMusic")
|
||||||
|
@onready var _menu_sfx: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuSfx")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_tab_bar.tab_changed.connect(_on_tab_bar_tab_changed)
|
||||||
|
_tab_container.tab_changed.connect(_on_tab_container_tab_changed)
|
||||||
|
_tab_container.current_tab = _tab_bar.current_tab
|
||||||
|
_music_volume_slider.value_changed.connect(_on_music_volume_changed)
|
||||||
|
_music_mute_checkbox.toggled.connect(_on_music_mute_toggled)
|
||||||
|
_sfx_volume_slider.value_changed.connect(_on_sfx_volume_changed)
|
||||||
|
_sfx_mute_checkbox.toggled.connect(_on_sfx_mute_toggled)
|
||||||
|
_sync_audio_controls()
|
||||||
|
_register_focus_sounds()
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
if event.is_action_pressed("ui_cancel"):
|
||||||
|
get_tree().change_scene_to_file("uid://b4k81taauef4q")
|
||||||
|
|
||||||
|
func _on_tab_bar_tab_changed(tab_index: int) -> void:
|
||||||
|
if _tab_container.current_tab != tab_index:
|
||||||
|
_tab_container.current_tab = tab_index
|
||||||
|
|
||||||
|
func _on_tab_container_tab_changed(tab_index: int) -> void:
|
||||||
|
if _tab_bar.current_tab != tab_index:
|
||||||
|
_tab_bar.current_tab = tab_index
|
||||||
|
|
||||||
|
func _sync_audio_controls() -> void:
|
||||||
|
var value: float = 0.7
|
||||||
|
var muted: bool = false
|
||||||
|
if _menu_music:
|
||||||
|
if _menu_music.has_method("get_user_volume"):
|
||||||
|
value = _menu_music.get_user_volume()
|
||||||
|
if _menu_music.has_method("is_user_muted"):
|
||||||
|
muted = _menu_music.is_user_muted()
|
||||||
|
_apply_audio_control_state(_music_volume_slider, _music_mute_checkbox, _music_volume_value, value, muted)
|
||||||
|
|
||||||
|
var sfx_value: float = 0.7
|
||||||
|
var sfx_muted: bool = false
|
||||||
|
if _menu_sfx:
|
||||||
|
if _menu_sfx.has_method("get_user_volume"):
|
||||||
|
sfx_value = _menu_sfx.get_user_volume()
|
||||||
|
if _menu_sfx.has_method("is_user_muted"):
|
||||||
|
sfx_muted = _menu_sfx.is_user_muted()
|
||||||
|
_apply_audio_control_state(_sfx_volume_slider, _sfx_mute_checkbox, _sfx_volume_value, sfx_value, sfx_muted)
|
||||||
|
|
||||||
|
func _on_music_volume_changed(value: float) -> void:
|
||||||
|
if _menu_music and _menu_music.has_method("set_user_volume"):
|
||||||
|
_menu_music.set_user_volume(value)
|
||||||
|
_update_volume_label(_music_volume_value, value, _music_mute_checkbox.button_pressed)
|
||||||
|
|
||||||
|
func _on_music_mute_toggled(pressed: bool) -> void:
|
||||||
|
if _menu_music and _menu_music.has_method("set_user_muted"):
|
||||||
|
_menu_music.set_user_muted(pressed)
|
||||||
|
_music_volume_slider.editable = not pressed
|
||||||
|
_update_volume_label(_music_volume_value, _music_volume_slider.value, pressed)
|
||||||
|
|
||||||
|
func _on_sfx_volume_changed(value: float) -> void:
|
||||||
|
if _menu_sfx and _menu_sfx.has_method("set_user_volume"):
|
||||||
|
_menu_sfx.set_user_volume(value)
|
||||||
|
_update_volume_label(_sfx_volume_value, value, _sfx_mute_checkbox.button_pressed)
|
||||||
|
|
||||||
|
func _on_sfx_mute_toggled(pressed: bool) -> void:
|
||||||
|
if _menu_sfx and _menu_sfx.has_method("set_user_muted"):
|
||||||
|
_menu_sfx.set_user_muted(pressed)
|
||||||
|
_sfx_volume_slider.editable = not pressed
|
||||||
|
_update_volume_label(_sfx_volume_value, _sfx_volume_slider.value, pressed)
|
||||||
|
|
||||||
|
func _apply_audio_control_state(slider: HSlider, checkbox: CheckBox, value_label: Label, value: float, muted: bool) -> void:
|
||||||
|
slider.set_block_signals(true)
|
||||||
|
slider.value = value
|
||||||
|
slider.set_block_signals(false)
|
||||||
|
slider.editable = not muted
|
||||||
|
checkbox.set_block_signals(true)
|
||||||
|
checkbox.button_pressed = muted
|
||||||
|
checkbox.set_block_signals(false)
|
||||||
|
_update_volume_label(value_label, value, muted)
|
||||||
|
|
||||||
|
func _update_volume_label(value_label: Label, value: float, muted: bool) -> void:
|
||||||
|
if muted:
|
||||||
|
value_label.text = "Muted"
|
||||||
|
else:
|
||||||
|
var percent: int = int(round(value * 100.0))
|
||||||
|
value_label.text = str(percent) + "%"
|
||||||
|
|
||||||
|
func _register_focus_sounds() -> void:
|
||||||
|
_connect_focus_recursive(self)
|
||||||
|
|
||||||
|
func _connect_focus_recursive(node: Node) -> void:
|
||||||
|
if node is Control:
|
||||||
|
var control: Control = node
|
||||||
|
if control.focus_mode != Control.FOCUS_NONE:
|
||||||
|
if not control.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")):
|
||||||
|
control.focus_entered.connect(_on_menu_item_focus)
|
||||||
|
if control.has_signal("mouse_entered") and (control is BaseButton or control.focus_mode != Control.FOCUS_NONE):
|
||||||
|
if not control.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")):
|
||||||
|
control.mouse_entered.connect(_on_menu_item_focus)
|
||||||
|
for child in node.get_children():
|
||||||
|
_connect_focus_recursive(child)
|
||||||
|
|
||||||
|
func _on_menu_item_focus() -> void:
|
||||||
|
if MenuSfx:
|
||||||
|
MenuSfx.play_hover()
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://d3tqrm4ry4l88"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://h1slqbemgwvr" path="res://scenes/UI/Settings.gd" id="1_1dggd"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="1_i47rn"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="2_46duy"]
|
||||||
|
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="3_46duy"]
|
||||||
|
|
||||||
|
|
||||||
|
[node name="settings_screen" type="Node2D"]
|
||||||
|
script = ExtResource("1_1dggd")
|
||||||
|
metadata/_edit_vertical_guides_ = [1152.0]
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="."]
|
||||||
|
offset_left = -192.0
|
||||||
|
offset_top = -188.0
|
||||||
|
offset_right = 1344.0
|
||||||
|
offset_bottom = 836.0
|
||||||
|
texture = ExtResource("1_i47rn")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
offset_right = 1152.0
|
||||||
|
offset_bottom = 648.0
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("2_46duy")
|
||||||
|
theme_override_font_sizes/font_size = 42
|
||||||
|
text = "Settings"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="TabBar" type="TabBar" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme = ExtResource("3_46duy")
|
||||||
|
current_tab = 0
|
||||||
|
tab_count = 4
|
||||||
|
tab_0/title = "Gameplay"
|
||||||
|
tab_1/title = "Video"
|
||||||
|
tab_2/title = "Audio"
|
||||||
|
tab_3/title = "Dev"
|
||||||
|
|
||||||
|
[node name="TabContainer" type="TabContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
tabs_visible = false
|
||||||
|
|
||||||
|
[node name="Gameplay" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Video" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="Audio" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="AudioVBox" type="VBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
offset_left = 120.0
|
||||||
|
offset_top = 240.0
|
||||||
|
offset_right = -120.0
|
||||||
|
offset_bottom = -40.0
|
||||||
|
|
||||||
|
[node name="MusicVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Music Volume"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
|
||||||
|
[node name="MusicVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
alignment = 1
|
||||||
|
custom_minimum_size = Vector2(0, 40)
|
||||||
|
|
||||||
|
[node name="MusicVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 0.0
|
||||||
|
max_value = 1.0
|
||||||
|
step = 0.01
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(320, 0)
|
||||||
|
|
||||||
|
[node name="MusicVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "70%"
|
||||||
|
size_flags_horizontal = 1
|
||||||
|
horizontal_alignment = 1
|
||||||
|
custom_minimum_size = Vector2(60, 0)
|
||||||
|
|
||||||
|
[node name="MusicMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Mute"
|
||||||
|
size_flags_horizontal = 1
|
||||||
|
|
||||||
|
[node name="SfxVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Menu SFX Volume"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
|
||||||
|
[node name="SfxVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
alignment = 1
|
||||||
|
custom_minimum_size = Vector2(0, 40)
|
||||||
|
|
||||||
|
[node name="SfxVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
|
||||||
|
layout_mode = 2
|
||||||
|
min_value = 0.0
|
||||||
|
max_value = 1.0
|
||||||
|
step = 0.01
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(320, 0)
|
||||||
|
|
||||||
|
[node name="SfxVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "70%"
|
||||||
|
size_flags_horizontal = 1
|
||||||
|
horizontal_alignment = 1
|
||||||
|
custom_minimum_size = Vector2(60, 0)
|
||||||
|
|
||||||
|
[node name="SfxMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Mute"
|
||||||
|
size_flags_horizontal = 1
|
||||||
|
|
||||||
|
[node name="Dev" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var is_logged_in: bool = false
|
||||||
|
var access_token: String = ""
|
||||||
|
var username: String = ""
|
||||||
|
|
||||||
|
func set_session(new_username: String, token: String) -> void:
|
||||||
|
is_logged_in = true
|
||||||
|
username = new_username
|
||||||
|
access_token = token
|
||||||
|
|
||||||
|
func clear_session() -> void:
|
||||||
|
is_logged_in = false
|
||||||
|
username = ""
|
||||||
|
access_token = ""
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ccloj2rh4dche
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
const AUTH_LOGOUT_URL := "https://pauth.ranaze.com/api/Auth/logout"
|
||||||
|
|
||||||
|
@onready var _status_label: Label = %StatusLabel
|
||||||
|
@onready var _character_list: ItemList = %CharacterList
|
||||||
|
@onready var _name_input: LineEdit = %NameInput
|
||||||
|
@onready var _logout_request: HTTPRequest = %LogoutRequest
|
||||||
|
|
||||||
|
var _characters: Array = []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not AuthState.is_logged_in:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
|
||||||
|
return
|
||||||
|
_load_characters()
|
||||||
|
|
||||||
|
func _log_failure(action: String, response: Dictionary) -> void:
|
||||||
|
push_warning("%s failed: status=%s error=%s body=%s" % [
|
||||||
|
action,
|
||||||
|
response.get("status", "n/a"),
|
||||||
|
response.get("error", ""),
|
||||||
|
response.get("body", "")
|
||||||
|
])
|
||||||
|
|
||||||
|
func _load_characters() -> void:
|
||||||
|
_status_label.text = "Loading characters..."
|
||||||
|
var response := await CharacterService.list_characters()
|
||||||
|
if not response.get("ok", false):
|
||||||
|
_log_failure("List characters", response)
|
||||||
|
_status_label.text = "Failed to load characters."
|
||||||
|
return
|
||||||
|
|
||||||
|
var parsed: Variant = JSON.parse_string(String(response.get("body", "")))
|
||||||
|
if typeof(parsed) != TYPE_ARRAY:
|
||||||
|
_status_label.text = "Unexpected character response."
|
||||||
|
return
|
||||||
|
|
||||||
|
_characters = parsed
|
||||||
|
_character_list.clear()
|
||||||
|
for character in _characters:
|
||||||
|
var character_name := String(character.get("name", "Unnamed"))
|
||||||
|
_character_list.add_item(character_name)
|
||||||
|
|
||||||
|
if _characters.is_empty():
|
||||||
|
_status_label.text = "No characters yet. Add one below."
|
||||||
|
else:
|
||||||
|
_status_label.text = ""
|
||||||
|
|
||||||
|
func _on_add_button_pressed() -> void:
|
||||||
|
var character_name := _name_input.text.strip_edges()
|
||||||
|
if character_name.is_empty():
|
||||||
|
_status_label.text = "Enter a character name first."
|
||||||
|
return
|
||||||
|
|
||||||
|
_status_label.text = "Creating character..."
|
||||||
|
var response := await CharacterService.create_character(character_name)
|
||||||
|
if not response.get("ok", false):
|
||||||
|
_log_failure("Create character", response)
|
||||||
|
_status_label.text = "Failed to create character."
|
||||||
|
return
|
||||||
|
|
||||||
|
var parsed: Variant = JSON.parse_string(String(response.get("body", "")))
|
||||||
|
if typeof(parsed) == TYPE_DICTIONARY:
|
||||||
|
_characters.append(parsed)
|
||||||
|
_character_list.add_item(String(parsed.get("name", character_name)))
|
||||||
|
_name_input.text = ""
|
||||||
|
_status_label.text = "Character added."
|
||||||
|
else:
|
||||||
|
_status_label.text = "Character created, but response was unexpected."
|
||||||
|
|
||||||
|
func _on_delete_button_pressed() -> void:
|
||||||
|
var selected := _character_list.get_selected_items()
|
||||||
|
if selected.is_empty():
|
||||||
|
_status_label.text = "Select a character to delete."
|
||||||
|
return
|
||||||
|
|
||||||
|
var index := selected[0]
|
||||||
|
if index < 0 or index >= _characters.size():
|
||||||
|
_status_label.text = "Invalid selection."
|
||||||
|
return
|
||||||
|
|
||||||
|
var character: Dictionary = _characters[index]
|
||||||
|
var character_id := String(character.get("id", character.get("Id", "")))
|
||||||
|
if character_id.is_empty():
|
||||||
|
_status_label.text = "Missing character id."
|
||||||
|
return
|
||||||
|
|
||||||
|
_status_label.text = "Deleting character..."
|
||||||
|
var response := await CharacterService.delete_character(character_id)
|
||||||
|
if not response.get("ok", false):
|
||||||
|
_log_failure("Delete character", response)
|
||||||
|
_status_label.text = "Failed to delete character."
|
||||||
|
return
|
||||||
|
|
||||||
|
_characters.remove_at(index)
|
||||||
|
_character_list.remove_item(index)
|
||||||
|
_status_label.text = "Character deleted."
|
||||||
|
|
||||||
|
func _on_refresh_button_pressed() -> void:
|
||||||
|
_load_characters()
|
||||||
|
|
||||||
|
func _on_back_button_pressed() -> void:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
|
||||||
|
|
||||||
|
func _on_logout_button_pressed() -> void:
|
||||||
|
_request_logout()
|
||||||
|
|
||||||
|
func _request_logout() -> void:
|
||||||
|
if AuthState.access_token.is_empty():
|
||||||
|
AuthState.clear_session()
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
|
||||||
|
return
|
||||||
|
|
||||||
|
var headers := PackedStringArray([
|
||||||
|
"Authorization: Bearer %s" % AuthState.access_token,
|
||||||
|
])
|
||||||
|
var err := _logout_request.request(AUTH_LOGOUT_URL, headers, HTTPClient.METHOD_POST)
|
||||||
|
if err != OK:
|
||||||
|
push_warning("Failed to send logout request: %s" % err)
|
||||||
|
AuthState.clear_session()
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
|
||||||
|
|
||||||
|
func _on_logout_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
|
var body_text := body.get_string_from_utf8()
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||||
|
push_warning("Logout failed (%s/%s): %s" % [result, response_code, body_text])
|
||||||
|
AuthState.clear_session()
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/start_screen.tscn")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c2y7ftq2k3v4x
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://cw3b7n7k2c4h6"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/UI/character_screen.gd" id="1_0p3qk"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_5g2t1"]
|
||||||
|
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="3_k2j6k"]
|
||||||
|
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="4_5b3b7"]
|
||||||
|
|
||||||
|
[node name="CharacterScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_0p3qk")
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = ExtResource("2_5g2t1")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 80
|
||||||
|
theme_override_constants/margin_top = 40
|
||||||
|
theme_override_constants/margin_right = 80
|
||||||
|
theme_override_constants/margin_bottom = 40
|
||||||
|
|
||||||
|
[node name="ContentCenter" type="CenterContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_constants/separation = 18
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("3_k2j6k")
|
||||||
|
text = "YOUR CHARACTERS"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="StatusLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="CharacterList" type="ItemList" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
custom_minimum_size = Vector2(520, 240)
|
||||||
|
|
||||||
|
[node name="AddHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="NameInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/AddHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
placeholder_text = "character name"
|
||||||
|
|
||||||
|
[node name="AddButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/AddHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 0
|
||||||
|
theme = ExtResource("4_5b3b7")
|
||||||
|
text = "ADD"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="ActionHBox" type="HBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="RefreshButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("4_5b3b7")
|
||||||
|
text = "REFRESH"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="DeleteButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("4_5b3b7")
|
||||||
|
text = "DELETE"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("4_5b3b7")
|
||||||
|
text = "BACK"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="LogoutButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("4_5b3b7")
|
||||||
|
text = "LOG OUT"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="LogoutRequest" type="HTTPRequest" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/AddHBox/AddButton" to="." method="_on_add_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/RefreshButton" to="." method="_on_refresh_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/DeleteButton" to="." method="_on_delete_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/BackButton" to="." method="_on_back_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ActionHBox/LogoutButton" to="." method="_on_logout_button_pressed"]
|
||||||
|
[connection signal="request_completed" from="LogoutRequest" to="." method="_on_logout_request_completed"]
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const CHARACTER_API_URL := "https://pcharacter.ranaze.com/api/Characters"
|
||||||
|
|
||||||
|
func list_characters() -> Dictionary:
|
||||||
|
return await _request(HTTPClient.METHOD_GET, CHARACTER_API_URL)
|
||||||
|
|
||||||
|
func create_character(character_name: String) -> Dictionary:
|
||||||
|
var payload := JSON.stringify({
|
||||||
|
"name": character_name
|
||||||
|
})
|
||||||
|
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 _request(method: int, url: String, body: String = "") -> Dictionary:
|
||||||
|
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)
|
||||||
|
if method == HTTPClient.METHOD_POST or method == HTTPClient.METHOD_PUT:
|
||||||
|
headers.append("Content-Type: application/json")
|
||||||
|
|
||||||
|
var err := request.request(url, headers, method, body)
|
||||||
|
if err != OK:
|
||||||
|
request.queue_free()
|
||||||
|
return {
|
||||||
|
"ok": false,
|
||||||
|
"status": 0,
|
||||||
|
"error": "Failed to send request (%s)." % err,
|
||||||
|
"body": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
return {
|
||||||
|
"ok": false,
|
||||||
|
"status": response_code,
|
||||||
|
"error": "Network error (%s)." % result_code,
|
||||||
|
"body": response_body
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ok": response_code >= 200 and response_code < 300,
|
||||||
|
"status": response_code,
|
||||||
|
"error": "",
|
||||||
|
"body": response_body
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c8kchv0e77yw4
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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.")
|
||||||
|
return
|
||||||
|
|
||||||
|
var payload := {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
var headers := PackedStringArray(["Content-Type: application/json"])
|
||||||
|
var err := _login_request.request(AUTH_LOGIN_URL, headers, HTTPClient.METHOD_POST, JSON.stringify(payload))
|
||||||
|
if err != OK:
|
||||||
|
_show_error("Failed to send login request.")
|
||||||
|
|
||||||
|
func _on_login_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
|
var body_text := body.get_string_from_utf8()
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
_show_error("Network error. Please try again.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if response_code >= 200 and response_code < 300:
|
||||||
|
var response: Variant = JSON.parse_string(body_text)
|
||||||
|
if typeof(response) == TYPE_DICTIONARY:
|
||||||
|
#print("Login success for %s" % response.get("username", "unknown"))
|
||||||
|
#print("Access Token: %s" % response.get("accessToken", ""))
|
||||||
|
var token := String(response.get("accessToken", ""))
|
||||||
|
var username := String(response.get("username", ""))
|
||||||
|
AuthState.set_session(username, token)
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/character_screen.tscn")
|
||||||
|
else:
|
||||||
|
_show_error("Login failed. Check your credentials.")
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bnrhapdcfvp04
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
[gd_scene load_steps=5 format=3 uid="uid://fmp1tah03kew"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/UI/login_screen.gd" id="1_jqkpi"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_2n6di"]
|
||||||
|
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="3_c4k70"]
|
||||||
|
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="4_gx673"]
|
||||||
|
|
||||||
|
[node name="LoginScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_jqkpi")
|
||||||
|
|
||||||
|
[node name="TextureRect" type="TextureRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
texture = ExtResource("2_2n6di")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 80
|
||||||
|
theme_override_constants/margin_top = 40
|
||||||
|
theme_override_constants/margin_right = 80
|
||||||
|
theme_override_constants/margin_bottom = 40
|
||||||
|
|
||||||
|
[node name="ContentCenter" type="CenterContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme_override_constants/separation = 24
|
||||||
|
|
||||||
|
[node name="TitleLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("3_c4k70")
|
||||||
|
text = "ACCOUNT LOGIN"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="FormVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
custom_minimum_size = Vector2(480, 0)
|
||||||
|
|
||||||
|
[node name="UsernameLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Username"
|
||||||
|
|
||||||
|
[node name="UsernameInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
placeholder_text = "enter username"
|
||||||
|
caret_blink = true
|
||||||
|
|
||||||
|
[node name="PasswordLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Password"
|
||||||
|
|
||||||
|
[node name="PasswordInput" type="LineEdit" parent="MarginContainer/ContentCenter/ContentVBox/FormVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
placeholder_text = "enter password"
|
||||||
|
secret = true
|
||||||
|
|
||||||
|
[node name="ButtonVBox" type="VBoxContainer" parent="MarginContainer/ContentCenter/ContentVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="ErrorLabel" type="Label" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme = ExtResource("3_c4k70")
|
||||||
|
horizontal_alignment = 1
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
theme_override_colors/font_color = Color(1, 0.2, 0.2, 1)
|
||||||
|
|
||||||
|
[node name="LogInButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("4_gx673")
|
||||||
|
text = "LOG IN"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ButtonVBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme = ExtResource("4_gx673")
|
||||||
|
text = "BACK"
|
||||||
|
text_alignment = 1
|
||||||
|
|
||||||
|
[node name="LoginRequest" type="HTTPRequest" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ButtonVBox/LogInButton" to="." method="_on_log_in_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/ContentCenter/ContentVBox/ButtonVBox/BackButton" to="." method="_on_back_button_pressed"]
|
||||||
|
[connection signal="request_completed" from="LoginRequest" to="." method="_on_login_request_completed"]
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
extends AudioStreamPlayer
|
||||||
|
|
||||||
|
const MENU_SCENES: Dictionary = {
|
||||||
|
"res://scenes/UI/start_screen.tscn": true,
|
||||||
|
"res://scenes/UI/Settings.tscn": true,
|
||||||
|
"res://scenes/UI/login_screen.tscn": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIG_PATH := "user://settings.cfg"
|
||||||
|
const CONFIG_SECTION := "audio"
|
||||||
|
const CONFIG_KEY_MUSIC_VOLUME := "menu_music_volume"
|
||||||
|
const CONFIG_KEY_MUSIC_MUTED := "menu_music_muted"
|
||||||
|
const DEFAULT_VOLUME := 0.7
|
||||||
|
|
||||||
|
var _last_scene: Node = null
|
||||||
|
var _user_volume_linear: float = DEFAULT_VOLUME
|
||||||
|
var _is_muted: bool = false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
set_process(true)
|
||||||
|
_load_settings()
|
||||||
|
_apply_volume()
|
||||||
|
_update_playback(get_tree().current_scene)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var current := get_tree().current_scene
|
||||||
|
if current != _last_scene:
|
||||||
|
_update_playback(current)
|
||||||
|
elif _should_play_scene(current) and not playing and not _is_muted and _user_volume_linear > 0.0:
|
||||||
|
play()
|
||||||
|
|
||||||
|
func _update_playback(scene: Node) -> void:
|
||||||
|
if scene == null:
|
||||||
|
_last_scene = null
|
||||||
|
return
|
||||||
|
_last_scene = scene
|
||||||
|
if _should_play_scene(scene):
|
||||||
|
if not playing:
|
||||||
|
play()
|
||||||
|
elif playing:
|
||||||
|
stop()
|
||||||
|
|
||||||
|
func _should_play_scene(scene: Node) -> bool:
|
||||||
|
if scene == null:
|
||||||
|
return false
|
||||||
|
var scene_path: String = scene.get_scene_file_path()
|
||||||
|
if scene_path.is_empty():
|
||||||
|
return false
|
||||||
|
return MENU_SCENES.has(scene_path)
|
||||||
|
|
||||||
|
func set_user_volume(value: float) -> void:
|
||||||
|
var clamped_value: float = clamp(value, 0.0, 1.0)
|
||||||
|
if is_equal_approx(_user_volume_linear, clamped_value):
|
||||||
|
return
|
||||||
|
_user_volume_linear = clamped_value
|
||||||
|
_apply_volume()
|
||||||
|
_save_settings()
|
||||||
|
|
||||||
|
func get_user_volume() -> float:
|
||||||
|
return _user_volume_linear
|
||||||
|
|
||||||
|
func set_user_muted(muted: bool) -> void:
|
||||||
|
var new_muted: bool = muted
|
||||||
|
if _is_muted == new_muted:
|
||||||
|
return
|
||||||
|
_is_muted = new_muted
|
||||||
|
_apply_volume()
|
||||||
|
_save_settings()
|
||||||
|
|
||||||
|
func is_user_muted() -> bool:
|
||||||
|
return _is_muted
|
||||||
|
|
||||||
|
func _apply_volume() -> void:
|
||||||
|
if _is_muted or _user_volume_linear <= 0.0:
|
||||||
|
volume_db = -80.0
|
||||||
|
else:
|
||||||
|
volume_db = linear_to_db(_user_volume_linear)
|
||||||
|
|
||||||
|
func _load_settings() -> void:
|
||||||
|
var config: ConfigFile = ConfigFile.new()
|
||||||
|
var err: int = config.load(CONFIG_PATH)
|
||||||
|
if err == OK:
|
||||||
|
var stored_volume: float = float(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_VOLUME, DEFAULT_VOLUME))
|
||||||
|
_user_volume_linear = clamp(stored_volume, 0.0, 1.0)
|
||||||
|
var stored_muted: bool = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_MUTED, false))
|
||||||
|
_is_muted = stored_muted
|
||||||
|
elif err == ERR_DOES_NOT_EXIST:
|
||||||
|
_user_volume_linear = DEFAULT_VOLUME
|
||||||
|
_is_muted = false
|
||||||
|
else:
|
||||||
|
push_warning("Failed to load settings.cfg: %s" % err)
|
||||||
|
_user_volume_linear = DEFAULT_VOLUME
|
||||||
|
_is_muted = false
|
||||||
|
|
||||||
|
func _save_settings() -> void:
|
||||||
|
var config: ConfigFile = ConfigFile.new()
|
||||||
|
var err: int = config.load(CONFIG_PATH)
|
||||||
|
if err != OK and err != ERR_DOES_NOT_EXIST:
|
||||||
|
push_warning("Failed to load settings.cfg before saving: %s" % err)
|
||||||
|
config = ConfigFile.new()
|
||||||
|
elif err != OK:
|
||||||
|
config = ConfigFile.new()
|
||||||
|
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_VOLUME, _user_volume_linear)
|
||||||
|
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUSIC_MUTED, _is_muted)
|
||||||
|
var save_err: int = config.save(CONFIG_PATH)
|
||||||
|
if save_err != OK:
|
||||||
|
push_warning("Failed to save settings.cfg: %s" % save_err)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://l0cqi7dvoou3
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://dfmq1n507y7d3"]
|
||||||
|
|
||||||
|
[ext_resource type="AudioStream" uid="uid://txgki0ijeuud" path="res://assets/audio/silly-test.ogg" id="1_ek0t3"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/UI/menu_music.gd" id="2_21d4q"]
|
||||||
|
|
||||||
|
[node name="MenuMusic" type="AudioStreamPlayer"]
|
||||||
|
bus = &"Music"
|
||||||
|
stream = ExtResource("1_ek0t3")
|
||||||
|
autoplay = true
|
||||||
|
volume_db = -3.0
|
||||||
|
priority = 10.0
|
||||||
|
script = ExtResource("2_21d4q")
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
extends AudioStreamPlayer
|
||||||
|
|
||||||
|
const CONFIG_PATH := "user://settings.cfg"
|
||||||
|
const CONFIG_SECTION := "audio"
|
||||||
|
const CONFIG_KEY_VOLUME := "menu_sfx_volume"
|
||||||
|
const CONFIG_KEY_MUTED := "menu_sfx_muted"
|
||||||
|
const DEFAULT_VOLUME := 0.7
|
||||||
|
|
||||||
|
var _user_volume_linear: float = DEFAULT_VOLUME
|
||||||
|
var _is_muted: bool = false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_load_settings()
|
||||||
|
_apply_volume()
|
||||||
|
|
||||||
|
func play_hover() -> void:
|
||||||
|
if stream == null or _is_muted or _user_volume_linear <= 0.0:
|
||||||
|
return
|
||||||
|
play(0.0)
|
||||||
|
|
||||||
|
func set_user_volume(value: float) -> void:
|
||||||
|
var clamped_value: float = clamp(value, 0.0, 1.0)
|
||||||
|
if is_equal_approx(_user_volume_linear, clamped_value):
|
||||||
|
return
|
||||||
|
_user_volume_linear = clamped_value
|
||||||
|
_apply_volume()
|
||||||
|
_save_settings()
|
||||||
|
|
||||||
|
func get_user_volume() -> float:
|
||||||
|
return _user_volume_linear
|
||||||
|
|
||||||
|
func set_user_muted(muted: bool) -> void:
|
||||||
|
if _is_muted == muted:
|
||||||
|
return
|
||||||
|
_is_muted = muted
|
||||||
|
_apply_volume()
|
||||||
|
_save_settings()
|
||||||
|
if _is_muted:
|
||||||
|
stop()
|
||||||
|
|
||||||
|
func is_user_muted() -> bool:
|
||||||
|
return _is_muted
|
||||||
|
|
||||||
|
func _apply_volume() -> void:
|
||||||
|
if _is_muted or _user_volume_linear <= 0.0:
|
||||||
|
volume_db = -80.0
|
||||||
|
else:
|
||||||
|
volume_db = linear_to_db(_user_volume_linear)
|
||||||
|
|
||||||
|
func _load_settings() -> void:
|
||||||
|
var config: ConfigFile = ConfigFile.new()
|
||||||
|
var err: int = config.load(CONFIG_PATH)
|
||||||
|
if err == OK:
|
||||||
|
_user_volume_linear = clamp(float(config.get_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, DEFAULT_VOLUME)), 0.0, 1.0)
|
||||||
|
_is_muted = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUTED, false))
|
||||||
|
elif err == ERR_DOES_NOT_EXIST:
|
||||||
|
_user_volume_linear = DEFAULT_VOLUME
|
||||||
|
_is_muted = false
|
||||||
|
else:
|
||||||
|
push_warning("Failed to load settings.cfg: %s" % err)
|
||||||
|
_user_volume_linear = DEFAULT_VOLUME
|
||||||
|
_is_muted = false
|
||||||
|
|
||||||
|
func _save_settings() -> void:
|
||||||
|
var config: ConfigFile = ConfigFile.new()
|
||||||
|
var err: int = config.load(CONFIG_PATH)
|
||||||
|
if err != OK and err != ERR_DOES_NOT_EXIST:
|
||||||
|
push_warning("Failed to load settings.cfg before saving: %s" % err)
|
||||||
|
config = ConfigFile.new()
|
||||||
|
elif err != OK:
|
||||||
|
config = ConfigFile.new()
|
||||||
|
config.set_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, _user_volume_linear)
|
||||||
|
config.set_value(CONFIG_SECTION, CONFIG_KEY_MUTED, _is_muted)
|
||||||
|
var save_err: int = config.save(CONFIG_PATH)
|
||||||
|
if save_err != OK:
|
||||||
|
push_warning("Failed to save settings.cfg: %s" % save_err)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c7ixr4hbh5ad6
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[gd_scene load_steps=3 format=3 uid="uid://dt785sv7ie7uj"]
|
||||||
|
|
||||||
|
[ext_resource type="AudioStream" uid="uid://64dplcgx2icb" path="res://assets/audio/silly-menu-hover-test.ogg" id="1_a5j5k"]
|
||||||
|
[ext_resource type="Script" path="res://scenes/UI/menu_sfx.gd" id="1_ijvfa"]
|
||||||
|
|
||||||
|
[node name="MenuSfx" type="AudioStreamPlayer"]
|
||||||
|
bus = &"SFX"
|
||||||
|
stream = ExtResource("1_a5j5k")
|
||||||
|
volume_db = -6.0
|
||||||
|
autoplay = false
|
||||||
|
priority = 0.5
|
||||||
|
max_polyphony = 4
|
||||||
|
script = ExtResource("1_ijvfa")
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
const AUTH_LOGOUT_URL := "https://pauth.ranaze.com/api/Auth/logout"
|
||||||
|
|
||||||
|
@onready var _login_button: Button = $MarginContainer/CenterContainer/ContentVBox/VBoxContainer/LogInButton
|
||||||
|
@onready var _logout_request: HTTPRequest = %LogoutRequest
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
_register_focus_sounds()
|
||||||
|
_update_login_button()
|
||||||
|
|
||||||
|
func _register_focus_sounds() -> void:
|
||||||
|
var button_container := $MarginContainer/CenterContainer/ContentVBox/VBoxContainer
|
||||||
|
for child in button_container.get_children():
|
||||||
|
if child is BaseButton:
|
||||||
|
var button: BaseButton = child
|
||||||
|
if not button.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")):
|
||||||
|
button.focus_entered.connect(_on_menu_item_focus)
|
||||||
|
if not button.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")):
|
||||||
|
button.mouse_entered.connect(_on_menu_item_focus)
|
||||||
|
|
||||||
|
func _on_start_button_pressed():
|
||||||
|
get_tree().change_scene_to_file("uid://dchj6g2i8ebph")
|
||||||
|
|
||||||
|
func _on_settings_button_pressed():
|
||||||
|
get_tree().change_scene_to_file("uid://d3tqrm4ry4l88")
|
||||||
|
|
||||||
|
func _on_quit_button_pressed():
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
func _on_log_in_button_pressed():
|
||||||
|
if AuthState.is_logged_in:
|
||||||
|
_request_logout()
|
||||||
|
else:
|
||||||
|
get_tree().change_scene_to_file("res://scenes/UI/login_screen.tscn")
|
||||||
|
|
||||||
|
func _on_menu_item_focus() -> void:
|
||||||
|
if MenuSfx:
|
||||||
|
MenuSfx.play_hover()
|
||||||
|
|
||||||
|
func _request_logout() -> void:
|
||||||
|
if AuthState.access_token.is_empty():
|
||||||
|
AuthState.clear_session()
|
||||||
|
_update_login_button()
|
||||||
|
return
|
||||||
|
var headers := PackedStringArray([
|
||||||
|
"Authorization: Bearer %s" % AuthState.access_token,
|
||||||
|
])
|
||||||
|
var err := _logout_request.request(AUTH_LOGOUT_URL, headers, HTTPClient.METHOD_POST)
|
||||||
|
if err != OK:
|
||||||
|
push_warning("Failed to send logout request: %s" % err)
|
||||||
|
AuthState.clear_session()
|
||||||
|
_update_login_button()
|
||||||
|
|
||||||
|
func _on_logout_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||||
|
var body_text := body.get_string_from_utf8()
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||||
|
push_warning("Logout failed (%s/%s): %s" % [result, response_code, body_text])
|
||||||
|
AuthState.clear_session()
|
||||||
|
_update_login_button()
|
||||||
|
|
||||||
|
func _update_login_button() -> void:
|
||||||
|
if AuthState.is_logged_in:
|
||||||
|
_login_button.text = "LOG OUT"
|
||||||
|
else:
|
||||||
|
_login_button.text = "LOG IN"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[gd_scene load_steps=5 format=3 uid="uid://b4k81taauef4q"]
|
[gd_scene load_steps=5 format=3 uid="uid://b4k81taauef4q"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://cc8lskf7y74kh" path="res://scenes/start_screen.gd" id="1_o7i0r"]
|
[ext_resource type="Script" uid="uid://cc8lskf7y74kh" path="res://scenes/UI/start_screen.gd" id="1_o7i0r"]
|
||||||
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="1_tx5wa"]
|
[ext_resource type="Theme" uid="uid://wpxmub0n2dr3" path="res://themes/button_theme.tres" id="1_tx5wa"]
|
||||||
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_r2jwc"]
|
[ext_resource type="Texture2D" uid="uid://dhuosr0p605gj" path="res://assets/images/pp_start_bg.png" id="2_r2jwc"]
|
||||||
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="4_hm208"]
|
[ext_resource type="Theme" uid="uid://tn8qndst18d6" path="res://themes/title_font_theme.tres" id="4_hm208"]
|
||||||
@@ -35,7 +35,16 @@ theme_override_constants/margin_top = 10
|
|||||||
theme_override_constants/margin_right = 10
|
theme_override_constants/margin_right = 10
|
||||||
theme_override_constants/margin_bottom = 10
|
theme_override_constants/margin_bottom = 10
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="MarginContainer"]
|
[node name="CenterContainer" type="CenterContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ContentVBox" type="VBoxContainer" parent="MarginContainer/CenterContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 8
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
theme = ExtResource("4_hm208")
|
theme = ExtResource("4_hm208")
|
||||||
@@ -43,40 +52,49 @@ text = "PROJECT
|
|||||||
PROMISCUOUS"
|
PROMISCUOUS"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer"]
|
[node name="TitleSpacer" type="Control" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||||
|
custom_minimum_size = Vector2(0, 40)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
|
||||||
size_flags_vertical = 8
|
|
||||||
theme_override_constants/margin_top = 10
|
|
||||||
theme_override_constants/margin_bottom = 60
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/MarginContainer"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
size_flags_vertical = 8
|
size_flags_vertical = 8
|
||||||
theme_override_constants/separation = 6
|
theme_override_constants/separation = 6
|
||||||
|
|
||||||
[node name="StartButton" type="Button" parent="MarginContainer/MarginContainer/VBoxContainer"]
|
[node name="LogInButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 8
|
size_flags_horizontal = 4
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme = ExtResource("1_tx5wa")
|
theme = ExtResource("1_tx5wa")
|
||||||
text = "START "
|
text = "LOG IN"
|
||||||
|
|
||||||
[node name="SettingsButton" type="Button" parent="MarginContainer/MarginContainer/VBoxContainer"]
|
[node name="StartButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 4
|
||||||
|
theme = ExtResource("1_tx5wa")
|
||||||
|
text = "START"
|
||||||
|
|
||||||
|
[node name="SettingsButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme = ExtResource("1_tx5wa")
|
theme = ExtResource("1_tx5wa")
|
||||||
text = "SETTINGS"
|
text = "SETTINGS"
|
||||||
|
|
||||||
[node name="QuitButton" type="Button" parent="MarginContainer/MarginContainer/VBoxContainer"]
|
[node name="QuitButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
size_flags_vertical = 4
|
size_flags_vertical = 4
|
||||||
theme = ExtResource("1_tx5wa")
|
theme = ExtResource("1_tx5wa")
|
||||||
text = "QUIT"
|
text = "QUIT"
|
||||||
|
|
||||||
[connection signal="pressed" from="MarginContainer/MarginContainer/VBoxContainer/StartButton" to="." method="_on_start_button_pressed"]
|
[node name="LogoutRequest" type="HTTPRequest" parent="."]
|
||||||
[connection signal="pressed" from="MarginContainer/MarginContainer/VBoxContainer/SettingsButton" to="." method="_on_settings_button_pressed"]
|
unique_name_in_owner = true
|
||||||
[connection signal="pressed" from="MarginContainer/MarginContainer/VBoxContainer/QuitButton" to="." method="_on_quit_button_pressed"]
|
|
||||||
|
[connection signal="pressed" from="MarginContainer/CenterContainer/ContentVBox/VBoxContainer/LogInButton" to="." method="_on_log_in_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/CenterContainer/ContentVBox/VBoxContainer/StartButton" to="." method="_on_start_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/CenterContainer/ContentVBox/VBoxContainer/SettingsButton" to="." method="_on_settings_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/CenterContainer/ContentVBox/VBoxContainer/QuitButton" to="." method="_on_quit_button_pressed"]
|
||||||
|
[connection signal="request_completed" from="LogoutRequest" to="." method="_on_logout_request_completed"]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[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")
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
const MOVE_SPEED := 8.0
|
||||||
|
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 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
|
||||||
|
|
||||||
|
@export var camera_follow_speed := 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
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
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()
|
||||||
|
|
||||||
|
func _integrate_forces(state):
|
||||||
|
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)) # Prevent flipping
|
||||||
|
_camera_pitch = rotation_x
|
||||||
|
rotation.y = rotation_y
|
||||||
|
_pending_mouse_delta = Vector2.ZERO
|
||||||
|
|
||||||
|
# Input as 2D vector
|
||||||
|
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||||
|
|
||||||
|
# Camera based movement
|
||||||
|
var forward := Vector3.FORWARD * -1.0
|
||||||
|
var right := Vector3.RIGHT
|
||||||
|
if cam:
|
||||||
|
forward = cam.global_transform.basis.z
|
||||||
|
right = cam.global_transform.basis.x
|
||||||
|
# Project onto ground plane so looking up/down doesn't kill movement.
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Jump Logic
|
||||||
|
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()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
func _input(event):
|
||||||
|
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) # Zoom in
|
||||||
|
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||||
|
zoom_camera(ZOOM_FACTOR) # Zoom out
|
||||||
|
|
||||||
|
if event.is_action_pressed("player_light"):
|
||||||
|
$SpotLight3D.visible = !$SpotLight3D.visible
|
||||||
|
|
||||||
|
func zoom_camera(factor):
|
||||||
|
var new_fov = cam.fov * factor
|
||||||
|
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ---> VisualStudioCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"Version": 1,
|
||||||
|
"WorkspaceRootPath": "Z:\\Godot\\Godot_v4.5-stable_mono_win64\\repos\\promiscuity\\microservices\\",
|
||||||
|
"Documents": [],
|
||||||
|
"DocumentGroupContainers": [
|
||||||
|
{
|
||||||
|
"Orientation": 0,
|
||||||
|
"VerticalTabListWidth": 256,
|
||||||
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": -1,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"Version": 1,
|
||||||
|
"WorkspaceRootPath": "Z:\\Godot\\Godot_v4.5-stable_mono_win64\\repos\\promiscuity\\microservices\\",
|
||||||
|
"Documents": [],
|
||||||
|
"DocumentGroupContainers": [
|
||||||
|
{
|
||||||
|
"Orientation": 0,
|
||||||
|
"VerticalTabListWidth": 256,
|
||||||
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": -1,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bcrypt.Net-Next" Version="4.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="3.4.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ActiveDebugProfile>https</ActiveDebugProfile>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@AuthApi_HostAddress = http://localhost:5279
|
||||||
|
|
||||||
|
GET {{AuthApi_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using AuthApi.Models;
|
||||||
|
using AuthApi.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AuthApi.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UserService _users;
|
||||||
|
private readonly IConfiguration _cfg;
|
||||||
|
private readonly BlacklistService _blacklist;
|
||||||
|
|
||||||
|
public AuthController(UserService users, IConfiguration cfg, BlacklistService blacklist)
|
||||||
|
{
|
||||||
|
_users = users; _cfg = cfg; _blacklist = blacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("register")]
|
||||||
|
public async Task<IActionResult> Register([FromBody] RegisterRequest req)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password))
|
||||||
|
return BadRequest("Username and password required");
|
||||||
|
|
||||||
|
if (await _users.GetByUsernameAsync(req.Username) != null)
|
||||||
|
return BadRequest("User already exists");
|
||||||
|
|
||||||
|
var hash = BCrypt.Net.BCrypt.HashPassword(req.Password);
|
||||||
|
var user = new User { Username = req.Username, PasswordHash = hash, Role = "USER", Email = req.Email };
|
||||||
|
await _users.CreateAsync(user);
|
||||||
|
return Ok("User created");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<IActionResult> Login([FromBody] LoginRequest req)
|
||||||
|
{
|
||||||
|
var user = await _users.GetByUsernameAsync(req.Username);
|
||||||
|
if (user == null || !BCrypt.Net.BCrypt.Verify(req.Password, user.PasswordHash))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var (accessToken, jti, expUtc) = GenerateJwtToken(user);
|
||||||
|
user.RefreshToken = Guid.NewGuid().ToString("N");
|
||||||
|
user.RefreshTokenExpiry = DateTime.UtcNow.AddDays(7);
|
||||||
|
await _users.UpdateAsync(user);
|
||||||
|
|
||||||
|
return Ok(new { accessToken, refreshToken = user.RefreshToken, user.Username, user.Role, jti, exp = expUtc });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("refresh")]
|
||||||
|
public async Task<IActionResult> Refresh([FromBody] RefreshRequest req)
|
||||||
|
{
|
||||||
|
var user = await _users.GetByUsernameAsync(req.Username);
|
||||||
|
if (user == null || user.RefreshToken != req.RefreshToken || user.RefreshTokenExpiry < DateTime.UtcNow)
|
||||||
|
return Unauthorized("Invalid or expired refresh token");
|
||||||
|
|
||||||
|
var (accessToken, _, expUtc) = GenerateJwtToken(user);
|
||||||
|
return Ok(new { accessToken, exp = expUtc });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("logout")]
|
||||||
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
|
public async Task<IActionResult> Logout()
|
||||||
|
{
|
||||||
|
var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
|
||||||
|
if (string.IsNullOrWhiteSpace(token)) return BadRequest("Token missing");
|
||||||
|
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
|
||||||
|
await _blacklist.AddToBlacklistAsync(jwt.Id, jwt.ValidTo);
|
||||||
|
return Ok("Logged out.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("role")]
|
||||||
|
[Authorize(Roles = "SUPER")]
|
||||||
|
public async Task<IActionResult> ChangeUserRole([FromBody] ChangeRoleRequest req)
|
||||||
|
{
|
||||||
|
if (req.NewRole is not ("USER" or "SUPER")) return BadRequest("Role must be 'USER' or 'SUPER'");
|
||||||
|
var user = await _users.GetByUsernameAsync(req.Username);
|
||||||
|
if (user is null) return NotFound("User not found");
|
||||||
|
user.Role = req.NewRole;
|
||||||
|
await _users.UpdateAsync(user);
|
||||||
|
return Ok($"{req.Username}'s role updated to {req.NewRole}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("users")]
|
||||||
|
[Authorize(Roles = "SUPER")]
|
||||||
|
public async Task<IActionResult> GetAllUsers() => Ok(await _users.GetAllAsync());
|
||||||
|
|
||||||
|
private (string token, string jti, DateTime expUtc) GenerateJwtToken(User user)
|
||||||
|
{
|
||||||
|
var key = Encoding.UTF8.GetBytes(_cfg["Jwt:Key"]!);
|
||||||
|
var issuer = _cfg["Jwt:Issuer"] ?? "GameAuthApi";
|
||||||
|
var audience = _cfg["Jwt:Audience"] ?? issuer;
|
||||||
|
|
||||||
|
var creds = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
|
||||||
|
var jti = Guid.NewGuid().ToString("N");
|
||||||
|
var claims = new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.Name, user.Username),
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, user.Id),
|
||||||
|
new Claim(ClaimTypes.Role, user.Role),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, jti)
|
||||||
|
};
|
||||||
|
|
||||||
|
var exp = DateTime.UtcNow.AddMinutes(15);
|
||||||
|
var token = new JwtSecurityToken(issuer, audience, claims, expires: exp, signingCredentials: creds);
|
||||||
|
return (new JwtSecurityTokenHandler().WriteToken(token), jti, exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy project file first to take advantage of Docker layer caching
|
||||||
|
COPY ["AuthApi.csproj", "./"]
|
||||||
|
RUN dotnet restore "AuthApi.csproj"
|
||||||
|
|
||||||
|
# Copy the remaining source and publish
|
||||||
|
COPY . .
|
||||||
|
RUN dotnet publish "AuthApi.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", "AuthApi.dll"]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace AuthApi.Models;
|
||||||
|
|
||||||
|
public class RegisterRequest { public string Username { get; set; } = ""; public string Password { get; set; } = ""; public string? Email { get; set; } }
|
||||||
|
public class LoginRequest { public string Username { get; set; } = ""; public string Password { get; set; } = ""; }
|
||||||
|
public class ChangeRoleRequest { public string Username { get; set; } = ""; public string NewRole { get; set; } = ""; }
|
||||||
|
public class RefreshRequest { public string Username { get; set; } = ""; public string RefreshToken { get; set; } = ""; }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
||||||
|
namespace AuthApi.Models;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
[BsonId] [BsonRepresentation(BsonType.ObjectId)]
|
||||||
|
public string Id { get; set; } = default!;
|
||||||
|
public string Username { get; set; } = default!;
|
||||||
|
public string PasswordHash { get; set; } = default!;
|
||||||
|
public string Role { get; set; } = "USER";
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? RefreshToken { get; set; }
|
||||||
|
public DateTime? RefreshTokenExpiry { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using AuthApi.Services;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
// DI
|
||||||
|
builder.Services.AddSingleton<UserService>();
|
||||||
|
builder.Services.AddSingleton<BlacklistService>();
|
||||||
|
|
||||||
|
// Swagger + JWT auth in Swagger
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen(c =>
|
||||||
|
{
|
||||||
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Auth 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<string>()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// AuthN/JWT
|
||||||
|
var cfg = builder.Configuration;
|
||||||
|
var jwtKey = cfg["Jwt:Key"] ?? throw new Exception("Jwt:Key missing");
|
||||||
|
var issuer = cfg["Jwt:Issuer"] ?? "GameAuthApi";
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
o.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnTokenValidated = async ctx =>
|
||||||
|
{
|
||||||
|
var jti = ctx.Principal?.FindFirstValue(System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames.Jti);
|
||||||
|
if (!string.IsNullOrEmpty(jti))
|
||||||
|
{
|
||||||
|
var bl = ctx.HttpContext.RequestServices.GetRequiredService<BlacklistService>();
|
||||||
|
if (await bl.IsBlacklistedAsync(jti)) ctx.Fail("Token revoked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.MapGet("/healthz", () => Results.Ok("ok"));
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(o =>
|
||||||
|
{
|
||||||
|
o.SwaggerEndpoint("/swagger/v1/swagger.json", "Auth API v1");
|
||||||
|
o.RoutePrefix = "swagger";
|
||||||
|
});
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
app.MapControllers();
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5279",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "https://localhost:7295;http://localhost:5279",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
|
||||||
|
namespace AuthApi.Services;
|
||||||
|
|
||||||
|
public class BlacklistedToken
|
||||||
|
{
|
||||||
|
[BsonId] public string Jti { get; set; } = default!;
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlacklistService
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<BlacklistedToken> _col;
|
||||||
|
|
||||||
|
public BlacklistService(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<BlacklistedToken>("BlacklistedTokens");
|
||||||
|
|
||||||
|
// TTL index so revocations expire automatically
|
||||||
|
var keys = Builders<BlacklistedToken>.IndexKeys.Ascending(x => x.ExpiresAt);
|
||||||
|
_col.Indexes.CreateOne(new CreateIndexModel<BlacklistedToken>(keys, new CreateIndexOptions { ExpireAfter = TimeSpan.Zero }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddToBlacklistAsync(string jti, DateTime expiresAt) =>
|
||||||
|
_col.ReplaceOneAsync(x => x.Jti == jti,
|
||||||
|
new BlacklistedToken { Jti = jti, ExpiresAt = expiresAt },
|
||||||
|
new ReplaceOptions { IsUpsert = true });
|
||||||
|
|
||||||
|
public Task<bool> IsBlacklistedAsync(string jti) =>
|
||||||
|
_col.Find(x => x.Jti == jti).AnyAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using AuthApi.Models;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
|
||||||
|
namespace AuthApi.Services;
|
||||||
|
|
||||||
|
public class UserService
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<User> _col;
|
||||||
|
|
||||||
|
public UserService(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<User>("Users");
|
||||||
|
|
||||||
|
var keys = Builders<User>.IndexKeys.Ascending(u => u.Username);
|
||||||
|
_col.Indexes.CreateOne(new CreateIndexModel<User>(keys, new CreateIndexOptions { Unique = true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<User?> GetByUsernameAsync(string username) =>
|
||||||
|
_col.Find(u => u.Username == username).FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
public Task CreateAsync(User user) => _col.InsertOneAsync(user);
|
||||||
|
|
||||||
|
public Task UpdateAsync(User user) =>
|
||||||
|
_col.ReplaceOneAsync(u => u.Id == user.Id, user);
|
||||||
|
|
||||||
|
public Task<List<User>> GetAllAsync() =>
|
||||||
|
_col.Find(FilterDefinition<User>.Empty).ToListAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5000" } } },
|
||||||
|
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
|
||||||
|
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
|
||||||
|
"Logging": { "LogLevel": { "Default": "Information" } },
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
@@ -0,0 +1,476 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v8.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v8.0": {
|
||||||
|
"AuthApi/1.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"BCrypt.Net-Next": "4.0.3",
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.8",
|
||||||
|
"Microsoft.AspNetCore.OpenApi": "8.0.8",
|
||||||
|
"MongoDB.Driver": "3.4.3",
|
||||||
|
"Swashbuckle.AspNetCore": "6.5.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"AuthApi.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BCrypt.Net-Next/4.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/BCrypt.Net-Next.dll": {
|
||||||
|
"assemblyVersion": "4.0.3.0",
|
||||||
|
"fileVersion": "4.0.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DnsClient/1.6.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Win32.Registry": "5.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net5.0/DnsClient.dll": {
|
||||||
|
"assemblyVersion": "1.6.1.0",
|
||||||
|
"fileVersion": "1.6.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.8": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
|
||||||
|
"assemblyVersion": "8.0.8.0",
|
||||||
|
"fileVersion": "8.0.824.36908"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.OpenApi/8.0.8": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.OpenApi": "1.4.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.AspNetCore.OpenApi.dll": {
|
||||||
|
"assemblyVersion": "8.0.8.0",
|
||||||
|
"fileVersion": "8.0.824.36908"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.ApiDescription.Server/6.0.5": {},
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions/2.0.0": {},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/7.1.2": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Abstractions.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Tokens": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Abstractions": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Logging.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Logging": "7.1.2",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Protocols.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols": "7.1.2",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Logging": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Tokens.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.NETCore.Platforms/5.0.0": {},
|
||||||
|
"Microsoft.OpenApi/1.4.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
||||||
|
"assemblyVersion": "1.4.3.0",
|
||||||
|
"fileVersion": "1.4.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.Win32.Registry/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Security.AccessControl": "5.0.0",
|
||||||
|
"System.Security.Principal.Windows": "5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MongoDB.Bson/3.4.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Memory": "4.5.5",
|
||||||
|
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/MongoDB.Bson.dll": {
|
||||||
|
"assemblyVersion": "3.4.3.0",
|
||||||
|
"fileVersion": "3.4.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MongoDB.Driver/3.4.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"DnsClient": "1.6.1",
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "2.0.0",
|
||||||
|
"MongoDB.Bson": "3.4.3",
|
||||||
|
"SharpCompress": "0.30.1",
|
||||||
|
"Snappier": "1.0.0",
|
||||||
|
"System.Buffers": "4.5.1",
|
||||||
|
"ZstdSharp.Port": "0.7.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/MongoDB.Driver.dll": {
|
||||||
|
"assemblyVersion": "3.4.3.0",
|
||||||
|
"fileVersion": "3.4.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SharpCompress/0.30.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net5.0/SharpCompress.dll": {
|
||||||
|
"assemblyVersion": "0.30.1.0",
|
||||||
|
"fileVersion": "0.30.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Snappier/1.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net5.0/Snappier.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore/6.5.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.ApiDescription.Server": "6.0.5",
|
||||||
|
"Swashbuckle.AspNetCore.Swagger": "6.5.0",
|
||||||
|
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0",
|
||||||
|
"Swashbuckle.AspNetCore.SwaggerUI": "6.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore.Swagger/6.5.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.OpenApi": "1.4.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net7.0/Swashbuckle.AspNetCore.Swagger.dll": {
|
||||||
|
"assemblyVersion": "6.5.0.0",
|
||||||
|
"fileVersion": "6.5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore.SwaggerGen/6.5.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Swashbuckle.AspNetCore.Swagger": "6.5.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
|
||||||
|
"assemblyVersion": "6.5.0.0",
|
||||||
|
"fileVersion": "6.5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore.SwaggerUI/6.5.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
|
||||||
|
"assemblyVersion": "6.5.0.0",
|
||||||
|
"fileVersion": "6.5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Buffers/4.5.1": {},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens": "7.1.2",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.IdentityModel.Tokens.Jwt.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Memory/4.5.5": {},
|
||||||
|
"System.Runtime.CompilerServices.Unsafe/5.0.0": {},
|
||||||
|
"System.Security.AccessControl/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.Platforms": "5.0.0",
|
||||||
|
"System.Security.Principal.Windows": "5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Security.Principal.Windows/5.0.0": {},
|
||||||
|
"ZstdSharp.Port/0.7.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net7.0/ZstdSharp.dll": {
|
||||||
|
"assemblyVersion": "0.7.3.0",
|
||||||
|
"fileVersion": "0.7.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"AuthApi/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
},
|
||||||
|
"BCrypt.Net-Next/4.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==",
|
||||||
|
"path": "bcrypt.net-next/4.0.3",
|
||||||
|
"hashPath": "bcrypt.net-next.4.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"DnsClient/1.6.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==",
|
||||||
|
"path": "dnsclient/1.6.1",
|
||||||
|
"hashPath": "dnsclient.1.6.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.8": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-J145j2LgD4kkkNkrf5DW/pKzithZRKN5EFY+KAO3SqweMyDfv4cgKgtOIsv2bhrOLGqPJixuZkZte7LfK1seYQ==",
|
||||||
|
"path": "microsoft.aspnetcore.authentication.jwtbearer/8.0.8",
|
||||||
|
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.8.0.8.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.OpenApi/8.0.8": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-wNHhohqP8rmsQ4UhKbd6jZMD6l+2Q/+DvRBT0Cgqeuglr13aF6sSJWicZKCIhZAUXzuhkdwtHVc95MlPlFk0dA==",
|
||||||
|
"path": "microsoft.aspnetcore.openapi/8.0.8",
|
||||||
|
"hashPath": "microsoft.aspnetcore.openapi.8.0.8.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.ApiDescription.Server/6.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==",
|
||||||
|
"path": "microsoft.extensions.apidescription.server/6.0.5",
|
||||||
|
"hashPath": "microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions/2.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==",
|
||||||
|
"path": "microsoft.extensions.logging.abstractions/2.0.0",
|
||||||
|
"hashPath": "microsoft.extensions.logging.abstractions.2.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-33eTIA2uO/L9utJjZWbKsMSVsQf7F8vtd6q5mQX7ZJzNvCpci5fleD6AeANGlbbb7WX7XKxq9+Dkb5e3GNDrmQ==",
|
||||||
|
"path": "microsoft.identitymodel.abstractions/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.abstractions.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-cloLGeZolXbCJhJBc5OC05uhrdhdPL6MWHuVUnkkUvPDeK7HkwThBaLZ1XjBQVk9YhxXE2OvHXnKi0PLleXxDg==",
|
||||||
|
"path": "microsoft.identitymodel.jsonwebtokens/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.jsonwebtokens.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-YCxBt2EeJP8fcXk9desChkWI+0vFqFLvBwrz5hBMsoh0KJE6BC66DnzkdzkJNqMltLromc52dkdT206jJ38cTw==",
|
||||||
|
"path": "microsoft.identitymodel.logging/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.logging.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-SydLwMRFx6EHPWJ+N6+MVaoArN1Htt92b935O3RUWPY1yUF63zEjvd3lBu79eWdZUwedP8TN2I5V9T3nackvIQ==",
|
||||||
|
"path": "microsoft.identitymodel.protocols/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-6lHQoLXhnMQ42mGrfDkzbIOR3rzKM1W1tgTeMPLgLCqwwGw0d96xFi/UiX/fYsu7d6cD5MJiL3+4HuI8VU+sVQ==",
|
||||||
|
"path": "microsoft.identitymodel.protocols.openidconnect/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.openidconnect.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-oICJMqr3aNEDZOwnH5SK49bR6Z4aX0zEAnOLuhloumOSuqnNq+GWBdQyrgILnlcT5xj09xKCP/7Y7gJYB+ls/g==",
|
||||||
|
"path": "microsoft.identitymodel.tokens/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.tokens.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.NETCore.Platforms/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
|
||||||
|
"path": "microsoft.netcore.platforms/5.0.0",
|
||||||
|
"hashPath": "microsoft.netcore.platforms.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.OpenApi/1.4.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-rURwggB+QZYcSVbDr7HSdhw/FELvMlriW10OeOzjPT7pstefMo7IThhtNtDudxbXhW+lj0NfX72Ka5EDsG8x6w==",
|
||||||
|
"path": "microsoft.openapi/1.4.3",
|
||||||
|
"hashPath": "microsoft.openapi.1.4.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.Win32.Registry/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
|
||||||
|
"path": "microsoft.win32.registry/5.0.0",
|
||||||
|
"hashPath": "microsoft.win32.registry.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"MongoDB.Bson/3.4.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ZB2nCdlWtmDGItkDFh2E2kfYlXaItG414t9Np7CZhpftLypemYnxtdI52H+0b8RPqoUJD7bUvrf598sDTJd5iA==",
|
||||||
|
"path": "mongodb.bson/3.4.3",
|
||||||
|
"hashPath": "mongodb.bson.3.4.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"MongoDB.Driver/3.4.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-yE6XQiDoFwTH4Xq/STJCbzsz+74RuzCXU45g9gaWFlLyy95xG8utuj+e64uXSbONtzabbp1O/8vfA3/HJXL6Pg==",
|
||||||
|
"path": "mongodb.driver/3.4.3",
|
||||||
|
"hashPath": "mongodb.driver.3.4.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SharpCompress/0.30.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==",
|
||||||
|
"path": "sharpcompress/0.30.1",
|
||||||
|
"hashPath": "sharpcompress.0.30.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Snappier/1.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==",
|
||||||
|
"path": "snappier/1.0.0",
|
||||||
|
"hashPath": "snappier.1.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore/6.5.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-FK05XokgjgwlCI6wCT+D4/abtQkL1X1/B9Oas6uIwHFmYrIO9WUD5aLC9IzMs9GnHfUXOtXZ2S43gN1mhs5+aA==",
|
||||||
|
"path": "swashbuckle.aspnetcore/6.5.0",
|
||||||
|
"hashPath": "swashbuckle.aspnetcore.6.5.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore.Swagger/6.5.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-XWmCmqyFmoItXKFsQSwQbEAsjDKcxlNf1l+/Ki42hcb6LjKL8m5Db69OTvz5vLonMSRntYO1XLqz0OP+n3vKnA==",
|
||||||
|
"path": "swashbuckle.aspnetcore.swagger/6.5.0",
|
||||||
|
"hashPath": "swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore.SwaggerGen/6.5.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Y/qW8Qdg9OEs7V013tt+94OdPxbRdbhcEbw4NiwGvf4YBcfhL/y7qp/Mjv/cENsQ2L3NqJ2AOu94weBy/h4KvA==",
|
||||||
|
"path": "swashbuckle.aspnetcore.swaggergen/6.5.0",
|
||||||
|
"hashPath": "swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Swashbuckle.AspNetCore.SwaggerUI/6.5.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-OvbvxX+wL8skxTBttcBsVxdh73Fag4xwqEU2edh4JMn7Ws/xJHnY/JB1e9RoCb6XpDxUF3hD9A0Z1lEUx40Pfw==",
|
||||||
|
"path": "swashbuckle.aspnetcore.swaggerui/6.5.0",
|
||||||
|
"hashPath": "swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Buffers/4.5.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
|
||||||
|
"path": "system.buffers/4.5.1",
|
||||||
|
"hashPath": "system.buffers.4.5.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Thhbe1peAmtSBFaV/ohtykXiZSOkx59Da44hvtWfIMFofDA3M3LaVyjstACf2rKGn4dEDR2cUpRAZ0Xs/zB+7Q==",
|
||||||
|
"path": "system.identitymodel.tokens.jwt/7.1.2",
|
||||||
|
"hashPath": "system.identitymodel.tokens.jwt.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Memory/4.5.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
|
||||||
|
"path": "system.memory/4.5.5",
|
||||||
|
"hashPath": "system.memory.4.5.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Runtime.CompilerServices.Unsafe/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA==",
|
||||||
|
"path": "system.runtime.compilerservices.unsafe/5.0.0",
|
||||||
|
"hashPath": "system.runtime.compilerservices.unsafe.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Security.AccessControl/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
|
||||||
|
"path": "system.security.accesscontrol/5.0.0",
|
||||||
|
"hashPath": "system.security.accesscontrol.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Security.Principal.Windows/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==",
|
||||||
|
"path": "system.security.principal.windows/5.0.0",
|
||||||
|
"hashPath": "system.security.principal.windows.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"ZstdSharp.Port/0.7.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==",
|
||||||
|
"path": "zstdsharp.port/0.7.3",
|
||||||
|
"hashPath": "zstdsharp.port.0.7.3.nupkg.sha512"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net8.0",
|
||||||
|
"frameworks": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "8.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.AspNetCore.App",
|
||||||
|
"version": "8.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configProperties": {
|
||||||
|
"System.GC.Server": true,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user