Compare commits
34
Commits
5fbc4a88ec
...
dialog
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a66c39e9f8 | ||
|
|
2d27e4254e | ||
|
|
b3287ccf6f | ||
|
|
d1fade919c | ||
|
|
b2eb85fdf1 | ||
|
|
d8138fbe69 | ||
|
|
e54dd191f1 | ||
|
|
db632e2522 | ||
|
|
52e1dfe99f | ||
|
|
3a99c2154c | ||
|
|
241312b0be | ||
|
|
aef117bcf9 | ||
|
|
582b2c43cb | ||
|
|
900951b1a7 | ||
|
|
fc647890a6 | ||
|
|
86fa27c78d | ||
|
|
268eca39c0 | ||
|
|
88e3767971 | ||
|
|
cdeea60e52 | ||
|
|
f1ae24f627 | ||
|
|
eb34e6692f | ||
|
|
d003f67273 | ||
|
|
faa9a5e9d5 | ||
|
|
196e877711 | ||
|
|
a347175e53 | ||
|
|
f443a817e6 | ||
|
|
c507dc6923 | ||
|
|
22b0dc4e4c | ||
|
|
7f4a6cbf75 | ||
|
|
599f0972af | ||
|
|
495bc6e06f | ||
|
|
41b1197b23 | ||
|
|
cbadfd7095 | ||
|
|
536e213b57 |
@@ -1,80 +1,80 @@
|
||||
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: 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 }}
|
||||
@@ -82,23 +82,32 @@ jobs:
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
|
||||
# -----------------------------
|
||||
# Ensure namespace exists
|
||||
# -----------------------------
|
||||
- name: Create namespace if missing
|
||||
env:
|
||||
KUBECONFIG: /tmp/kube/config
|
||||
run: |
|
||||
kubectl create namespace promiscuity-auth --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# -----------------------------
|
||||
# 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
|
||||
- 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
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
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: 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 }}
|
||||
@@ -83,21 +83,30 @@ jobs:
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
|
||||
# -----------------------------
|
||||
# Apply Kubernetes manifests
|
||||
# Ensure namespace exists
|
||||
# -----------------------------
|
||||
- name: Apply Character deployment & service
|
||||
- name: Create namespace if missing
|
||||
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
|
||||
kubectl create namespace promiscuity-character --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# -----------------------------
|
||||
# Rollout restart & wait
|
||||
# Apply Kubernetes manifests
|
||||
# -----------------------------
|
||||
- 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
|
||||
- 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,112 @@
|
||||
name: Deploy Promiscuity Locations API
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
|
||||
env:
|
||||
IMAGE_NAME: promiscuity-locations:latest
|
||||
IMAGE_TAR: /tmp/promiscuity-locations.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/LocationsApi
|
||||
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-locations.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-locations.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-locations.tar"
|
||||
done
|
||||
|
||||
# -----------------------------
|
||||
# CLEANUP: delete TAR from runner
|
||||
# -----------------------------
|
||||
- name: Clean TAR on runner
|
||||
run: |
|
||||
rm -f "${IMAGE_TAR}"
|
||||
|
||||
# -----------------------------
|
||||
# Write kubeconfig from secret
|
||||
# -----------------------------
|
||||
- name: Write kubeconfig from secret
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||
run: |
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
|
||||
# -----------------------------
|
||||
# Ensure namespace exists
|
||||
# -----------------------------
|
||||
- name: Create namespace if missing
|
||||
env:
|
||||
KUBECONFIG: /tmp/kube/config
|
||||
run: |
|
||||
kubectl create namespace promiscuity-locations --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# -----------------------------
|
||||
# Apply Kubernetes manifests
|
||||
# -----------------------------
|
||||
- name: Apply Locations deployment & service
|
||||
env:
|
||||
KUBECONFIG: /tmp/kube/config
|
||||
run: |
|
||||
kubectl apply -f microservices/LocationsApi/k8s/deployment.yaml -n promiscuity-locations
|
||||
kubectl apply -f microservices/LocationsApi/k8s/service.yaml -n promiscuity-locations
|
||||
|
||||
# -----------------------------
|
||||
# Rollout restart & wait
|
||||
# -----------------------------
|
||||
- name: Restart Locations deployment
|
||||
env:
|
||||
KUBECONFIG: /tmp/kube/config
|
||||
run: |
|
||||
kubectl rollout restart deployment/promiscuity-locations -n promiscuity-locations
|
||||
kubectl rollout status deployment/promiscuity-locations -n promiscuity-locations
|
||||
@@ -1,27 +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}"
|
||||
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}"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Promiscuity
|
||||
|
||||
## Microservices
|
||||
- Auth Microservice Swagger: https://pauth.ranaze.com/swagger/index.html
|
||||
- Character Microservice Swagger: https://pchar.ranaze.com/swagger/index.html
|
||||
- Location Microservice Swagger: https://ploc.ranaze.com/swagger/index.html
|
||||
|
||||
- Microservices README: microservices/README.md
|
||||
|
||||
## Test users
|
||||
- `SUPER/SUPER` - Super User
|
||||
- `test1/test1` - Super User
|
||||
- `test3/test3` - User
|
||||
|
||||
## Controls
|
||||
- Move: WASD
|
||||
- Jump: Space
|
||||
- Interact (enter/exit car): E
|
||||
- Flashlight: F
|
||||
- Phone: Tab
|
||||
- Pause menu: Esc
|
||||
@@ -1,7 +0,0 @@
|
||||
Auth Microservice swagger is accessible at https://pauth.ranaze.com/swagger/index.html
|
||||
|
||||
Test Users:
|
||||
SUPER/SUPER - Super User
|
||||
test1/test1 - Super User
|
||||
test3/test3 - User
|
||||
|
||||
+16
-16
@@ -1,17 +1,17 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
.nomedia
|
||||
|
||||
# Godot-specific ignores
|
||||
.import/
|
||||
export.cfg
|
||||
export_credentials.cfg
|
||||
*.tmp
|
||||
|
||||
# Imported translations (automatically generated from CSV files)
|
||||
*.translation
|
||||
|
||||
# Mono-specific ignores
|
||||
.mono/
|
||||
data_*/
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
.nomedia
|
||||
|
||||
# Godot-specific ignores
|
||||
.import/
|
||||
export.cfg
|
||||
export_credentials.cfg
|
||||
*.tmp
|
||||
|
||||
# Imported translations (automatically generated from CSV files)
|
||||
*.translation
|
||||
|
||||
# Mono-specific ignores
|
||||
.mono/
|
||||
data_*/
|
||||
mono_crash.*.json
|
||||
@@ -1,30 +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
|
||||
# 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
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bttaq8w3plgqh
|
||||
uid://bttaq8w3plgqh
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://bnqaqbgynoyys"
|
||||
path="res://.godot/imported/TestCharAnimated.glb-ecf7bb997b01494120b0dfccb4e1f064.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/TestCharAnimated.glb"
|
||||
dest_files=["res://.godot/imported/TestCharAnimated.glb-ecf7bb997b01494120b0dfccb4e1f064.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={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cpuebxqhtv72i"
|
||||
path="res://.godot/imported/humanFemale.glb-45dfb97e0f5fa98244d8cb6255565c65.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/humanFemale.glb"
|
||||
dest_files=["res://.godot/imported/humanFemale.glb-45dfb97e0f5fa98244d8cb6255565c65.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={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
@@ -1,3 +1,3 @@
|
||||
[gd_scene format=3 uid="uid://cuyws13lbkmxb"]
|
||||
|
||||
[node name="Test" type="Node2D"]
|
||||
[gd_scene format=3 uid="uid://cuyws13lbkmxb"]
|
||||
|
||||
[node name="Test" type="Node2D"]
|
||||
|
||||
+12
-12
@@ -1,12 +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"
|
||||
[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"
|
||||
|
||||
+70
-70
@@ -1,70 +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
|
||||
[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
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 996 B |
@@ -25,6 +25,8 @@ 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"
|
||||
SelectedCharacter="*res://scenes/UI/selected_character.gd"
|
||||
DialogSystem="*res://scenes/UI/dialog_system.gd"
|
||||
|
||||
[dotnet]
|
||||
|
||||
@@ -69,3 +71,13 @@ player_light={
|
||||
"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)
|
||||
]
|
||||
}
|
||||
player_phone={
|
||||
"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":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
interact={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
+153
-144
@@ -1,156 +1,165 @@
|
||||
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
|
||||
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
|
||||
@export var look_target_path: NodePath = NodePath("")
|
||||
@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
|
||||
|
||||
|
||||
var _look_target: 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()
|
||||
_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
|
||||
_look_target = get_node_or_null(look_target_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()
|
||||
|
||||
|
||||
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:
|
||||
if _look_target == null and look_target_path != NodePath(""):
|
||||
_look_target = get_node_or_null(look_target_path) as Node3D
|
||||
if _look_target == null:
|
||||
var viewport_cam := get_viewport().get_camera_3d()
|
||||
if viewport_cam != null and viewport_cam != _camera:
|
||||
_camera = viewport_cam
|
||||
elif _camera == null or not _camera.is_inside_tree():
|
||||
_camera = _resolve_camera()
|
||||
if _look_target == null and _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())
|
||||
var target := _look_target.global_position if _look_target != null else _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())
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bs3eqqujhetsm
|
||||
uid://bs3eqqujhetsm
|
||||
|
||||
@@ -1,111 +1,131 @@
|
||||
[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")
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/Interaction/dialog_trigger_area.gd" id="2_dialog"]
|
||||
|
||||
[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")
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_interact"]
|
||||
radius = 1.7
|
||||
|
||||
[node name="RepoBot" type="Node3D"]
|
||||
script = ExtResource("1_repo_bot")
|
||||
|
||||
[node name="Body" type="RigidBody3D" parent="."]
|
||||
mass = 1.5
|
||||
axis_lock_angular_x = true
|
||||
axis_lock_angular_z = true
|
||||
angular_damp = 8.0
|
||||
linear_damp = 0.5
|
||||
|
||||
[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")
|
||||
|
||||
[node name="InteractArea" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
|
||||
script = ExtResource("2_dialog")
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
prompt_text = "Press E to talk"
|
||||
dialog_text = "Repo Bot: Welcome to Promiscuity.\n\nPress E again to close this dialog."
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="InteractArea"]
|
||||
shape = SubResource("SphereShape3D_interact")
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
extends Area3D
|
||||
|
||||
@export var target_group: StringName = &"player"
|
||||
@export var prompt_text := "Press E to talk"
|
||||
@export_multiline var dialog_text := ""
|
||||
@export var auto_popup := false
|
||||
@export_enum("Every Entry", "Once") var auto_popup_mode := 0
|
||||
|
||||
var _has_triggered := false
|
||||
|
||||
func _ready() -> void:
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
body_entered.connect(_on_body_entered)
|
||||
body_exited.connect(_on_body_exited)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if not auto_popup and DialogSystem:
|
||||
DialogSystem.unregister_interactable(self)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node) -> void:
|
||||
if not (target_group == StringName() or body.is_in_group(target_group)):
|
||||
return
|
||||
if auto_popup:
|
||||
if auto_popup_mode == 1 and _has_triggered:
|
||||
return
|
||||
_has_triggered = true
|
||||
if DialogSystem and DialogSystem.has_method("show_text"):
|
||||
DialogSystem.show_text(dialog_text)
|
||||
return
|
||||
if DialogSystem:
|
||||
DialogSystem.register_interactable(self)
|
||||
|
||||
|
||||
func _on_body_exited(body: Node) -> void:
|
||||
if auto_popup:
|
||||
return
|
||||
if target_group == StringName() or body.is_in_group(target_group):
|
||||
DialogSystem.unregister_interactable(self)
|
||||
|
||||
|
||||
func get_dialog_prompt() -> String:
|
||||
return prompt_text
|
||||
|
||||
|
||||
func get_dialog_text() -> String:
|
||||
return dialog_text
|
||||
@@ -0,0 +1 @@
|
||||
uid://bk53njt7i3kmv
|
||||
+29
-17
@@ -1,22 +1,34 @@
|
||||
extends Node3D
|
||||
|
||||
extends Node3D
|
||||
|
||||
@export var day_length := 120.0 # seconds for full rotation
|
||||
@export var start_light_angle := -90.0
|
||||
@export var show_spawn_dialog := true
|
||||
@export_multiline var spawn_dialog_text := "Welcome to Promiscuity.\n\nPress E to close this message."
|
||||
@export var spawn_dialog_auto_close_seconds := 4.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
|
||||
|
||||
@onready var sun := $DirectionalLight3D
|
||||
|
||||
func _ready() -> void:
|
||||
if show_spawn_dialog and DialogSystem and DialogSystem.has_method("show_text"):
|
||||
await get_tree().process_frame
|
||||
DialogSystem.show_text(spawn_dialog_text)
|
||||
if spawn_dialog_auto_close_seconds > 0.0:
|
||||
await get_tree().create_timer(spawn_dialog_auto_close_seconds).timeout
|
||||
if DialogSystem and DialogSystem.has_method("close_if_text"):
|
||||
DialogSystem.close_if_text(spawn_dialog_text)
|
||||
|
||||
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
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://brgmxhhhtakja
|
||||
uid://brgmxhhhtakja
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=17 format=3 uid="uid://dchj6g2i8ebph"]
|
||||
[gd_scene load_steps=23 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"]
|
||||
@@ -6,6 +6,9 @@
|
||||
[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"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/Vehicles/car.tscn" id="5_car"]
|
||||
[ext_resource type="PackedScene" uid="uid://bnqaqbgynoyys" path="res://assets/models/TestCharAnimated.glb" id="5_fi66n"]
|
||||
[ext_resource type="Script" uid="uid://bk53njt7i3kmv" path="res://scenes/Interaction/dialog_trigger_area.gd" id="6_dialog"]
|
||||
|
||||
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_2q6dc"]
|
||||
bounce = 0.5
|
||||
@@ -19,6 +22,20 @@ bounce = 0.5
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_mx8sn"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_dialog_zone"]
|
||||
radius = 2.5
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dialog_zone"]
|
||||
transparency = 1
|
||||
cull_mode = 2
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.2, 0.8, 0.35, 0.18)
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_dialog_zone"]
|
||||
material = SubResource("StandardMaterial3D_dialog_zone")
|
||||
radius = 2.5
|
||||
height = 5.0
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_2q6dc"]
|
||||
size = Vector3(1080, 2, 1080)
|
||||
|
||||
@@ -42,6 +59,7 @@ script = ExtResource("1_a4mo8")
|
||||
|
||||
[node name="RepoBot" parent="." instance=ExtResource("4_repo")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.9426608, 0, -4.4451966)
|
||||
look_target_path = NodePath("../Player")
|
||||
|
||||
[node name="Thing" type="RigidBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3.7986288)
|
||||
@@ -59,19 +77,173 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
mesh = SubResource("SphereMesh_w7c3h")
|
||||
|
||||
[node name="Player" type="RigidBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.6563287, 0, 0)
|
||||
physics_material_override = SubResource("PhysicsMaterial_w8frs")
|
||||
script = ExtResource("1_muv8p")
|
||||
camera_path = NodePath("Camera3D")
|
||||
phone_path = NodePath("../PhoneUI")
|
||||
|
||||
[node name="TestCharAnimated" parent="Player" instance=ExtResource("5_fi66n")]
|
||||
transform = Transform3D(-0.9998549, 0, 0.01703362, 0, 1, 0, -0.01703362, 0, -0.9998549, 0, 0, 0)
|
||||
|
||||
[node name="Skeleton3D" parent="Player/TestCharAnimated/Armature" index="0"]
|
||||
bones/0/position = Vector3(0.002616825, -0.0012634661, -0.98995954)
|
||||
bones/0/rotation = Quaternion(-0.7278332, -0.05342924, 0.012382713, 0.6835575)
|
||||
bones/1/position = Vector3(8.593209e-06, 0.09923458, -0.012273359)
|
||||
bones/1/rotation = Quaternion(-0.03163086, 0.011397719, 0.022761386, 0.9991754)
|
||||
bones/2/position = Vector3(-2.2989244e-10, 0.117319785, 2.9730852e-08)
|
||||
bones/2/rotation = Quaternion(0.0102874795, 0.0011603195, 0.013812066, 0.99985105)
|
||||
bones/3/position = Vector3(-2.3358266e-09, 0.13458829, -1.382244e-08)
|
||||
bones/3/rotation = Quaternion(0.06755522, 0.0021543663, 0.013774819, 0.99761814)
|
||||
bones/4/position = Vector3(2.508626e-07, 0.15027755, 0.008779066)
|
||||
bones/4/rotation = Quaternion(0.051899955, -0.00089839776, 0.0039267424, 0.9986442)
|
||||
bones/5/position = Vector3(2.152588e-08, 0.103218146, 0.03142428)
|
||||
bones/5/rotation = Quaternion(-0.06595736, 0.011938445, -0.0014933152, 0.9977499)
|
||||
bones/6/position = Vector3(1.5477327e-06, 0.18474667, 0.06636399)
|
||||
bones/7/position = Vector3(0.061058242, 0.09106279, 0.0075706206)
|
||||
bones/7/rotation = Quaternion(-0.598225, -0.43451372, 0.5076822, -0.44224826)
|
||||
bones/8/position = Vector3(1.1842036e-08, 0.12922287, 5.2578955e-08)
|
||||
bones/8/rotation = Quaternion(0.543452, 0.18399428, 0.20302251, 0.79346573)
|
||||
bones/9/position = Vector3(1.5054144e-07, 0.2740467, 2.2048344e-08)
|
||||
bones/9/rotation = Quaternion(-5.2907154e-09, -1.1239725e-06, 0.012734968, 0.999919)
|
||||
bones/10/position = Vector3(1.9432857e-08, 0.27614468, 9.6609995e-08)
|
||||
bones/10/rotation = Quaternion(0.04830448, -0.2848921, 0.026799988, 0.9569665)
|
||||
bones/11/position = Vector3(-0.030029751, 0.037888147, 0.021671427)
|
||||
bones/11/rotation = Quaternion(0.2098604, -0.059559863, 0.20750383, 0.9536002)
|
||||
bones/12/position = Vector3(-3.3527822e-08, 0.0474497, -1.48403245e-08)
|
||||
bones/12/rotation = Quaternion(-0.14269812, 0.08661232, -0.24053419, 0.9561793)
|
||||
bones/13/position = Vector3(-9.313215e-09, 0.043821093, 1.7813335e-08)
|
||||
bones/13/rotation = Quaternion(-0.121022485, -0.07554186, 0.024044419, 0.98947906)
|
||||
bones/14/position = Vector3(-4.847594e-08, 0.034590863, -5.3379924e-08)
|
||||
bones/15/position = Vector3(-0.02822038, 0.12266627, 0.0023182186)
|
||||
bones/15/rotation = Quaternion(0.06621808, 0.00073213095, 0.008435749, 0.99776924)
|
||||
bones/16/position = Vector3(-8.136956e-09, 0.03891963, -1.4837047e-08)
|
||||
bones/16/rotation = Quaternion(0.1509963, 7.805282e-07, -0.015479834, 0.98841316)
|
||||
bones/17/position = Vector3(7.499967e-09, 0.034151517, -5.4443618e-08)
|
||||
bones/17/rotation = Quaternion(0.070082076, 7.71772e-07, -0.007181638, 0.99751544)
|
||||
bones/18/position = Vector3(2.700483e-09, 0.030779874, 4.9589467e-08)
|
||||
bones/19/position = Vector3(-1.2452401e-07, 0.12775533, -8.01722e-08)
|
||||
bones/19/rotation = Quaternion(0.13653132, -0.0038339633, -0.022952663, 0.9903624)
|
||||
bones/20/position = Vector3(2.5990463e-09, 0.036139663, 1.713758e-10)
|
||||
bones/20/rotation = Quaternion(0.15125485, 1.1823237e-06, -0.015500045, 0.9883733)
|
||||
bones/21/position = Vector3(2.9034163e-07, 0.034597658, -3.6051716e-08)
|
||||
bones/21/rotation = Quaternion(0.1599333, 1.4124467e-06, -0.016389191, 0.98699176)
|
||||
bones/22/position = Vector3(-3.0519334e-07, 0.03680197, -9.1170705e-08)
|
||||
bones/23/position = Vector3(0.022166358, 0.12147022, -9.993157e-05)
|
||||
bones/23/rotation = Quaternion(0.18325116, -0.004651328, -0.031451236, 0.9825519)
|
||||
bones/24/position = Vector3(4.5585904e-09, 0.036011815, -8.07135e-09)
|
||||
bones/24/rotation = Quaternion(0.18138836, 1.5675009e-06, -0.018595627, 0.9832358)
|
||||
bones/25/position = Vector3(-9.647604e-09, 0.033073086, -1.46341534e-08)
|
||||
bones/25/rotation = Quaternion(0.1552087, 2.2643005e-06, -0.015905593, 0.9877537)
|
||||
bones/26/position = Vector3(-2.434721e-07, 0.03660111, 2.471306e-07)
|
||||
bones/27/position = Vector3(0.04725836, 0.109081924, 0.0022613404)
|
||||
bones/27/rotation = Quaternion(0.23387046, -0.0035344863, -0.048872076, 0.97103226)
|
||||
bones/28/position = Vector3(4.3135207e-09, 0.041366525, 3.569472e-08)
|
||||
bones/28/rotation = Quaternion(0.15124865, 1.4340599e-05, -0.015863812, 0.98836845)
|
||||
bones/29/position = Vector3(1.195453e-08, 0.02594832, 1.5542597e-08)
|
||||
bones/29/rotation = Quaternion(0.14350323, 3.322942e-05, -0.014478063, 0.98954403)
|
||||
bones/30/position = Vector3(-2.4944999e-08, 0.029238665, 6.2325825e-08)
|
||||
bones/31/position = Vector3(-0.06105696, 0.09106397, 0.007570758)
|
||||
bones/31/rotation = Quaternion(0.60012215, -0.43466684, 0.5242142, 0.41966385)
|
||||
bones/32/position = Vector3(2.116817e-08, 0.12922288, 7.016769e-08)
|
||||
bones/32/rotation = Quaternion(0.46436408, -0.26298717, -0.23268588, 0.8130566)
|
||||
bones/33/position = Vector3(-2.350565e-08, 0.27404687, 1.5161348e-08)
|
||||
bones/33/rotation = Quaternion(7.69042e-08, -5.0622584e-06, -0.058380682, 0.9982944)
|
||||
bones/34/position = Vector3(1.3629875e-07, 0.27614468, -9.158248e-09)
|
||||
bones/34/rotation = Quaternion(0.059025347, 0.17513894, -0.005969527, 0.9827547)
|
||||
bones/35/position = Vector3(0.030029776, 0.03788806, 0.021671649)
|
||||
bones/35/rotation = Quaternion(0.21378386, 0.065372065, -0.22564878, 0.94821185)
|
||||
bones/36/position = Vector3(-2.7939748e-09, 0.04744962, -1.7240383e-08)
|
||||
bones/36/rotation = Quaternion(-0.14410053, -0.076146945, 0.2772088, 0.9468854)
|
||||
bones/37/position = Vector3(5.58785e-09, 0.04382136, 3.566345e-08)
|
||||
bones/37/rotation = Quaternion(-0.1552004, 0.07467698, -0.026057797, 0.9847117)
|
||||
bones/38/position = Vector3(8.2187e-08, 0.034590647, -2.4929412e-07)
|
||||
bones/39/position = Vector3(0.028220426, 0.1226661, 0.0023184721)
|
||||
bones/39/rotation = Quaternion(0.059513006, -0.0009918809, -0.010980465, 0.99816674)
|
||||
bones/40/position = Vector3(-1.1795999e-08, 0.038919587, -4.9162594e-09)
|
||||
bones/40/rotation = Quaternion(0.15128906, 2.1640317e-06, 0.012328284, 0.98841274)
|
||||
bones/41/position = Vector3(-1.4053852e-07, 0.034151636, -1.1118345e-08)
|
||||
bones/41/rotation = Quaternion(0.07021882, 5.966413e-07, 0.0057199914, 0.9975152)
|
||||
bones/42/position = Vector3(-7.1266735e-09, 0.030779934, 3.7720538e-10)
|
||||
bones/43/position = Vector3(1.69263e-07, 0.12775521, 2.5128352e-07)
|
||||
bones/43/rotation = Quaternion(0.13051407, 0.003691554, 0.018921439, 0.99125904)
|
||||
bones/44/position = Vector3(4.8907918e-09, 0.036139727, -8.499214e-09)
|
||||
bones/44/rotation = Quaternion(0.15154669, 1.6962191e-06, 0.012345144, 0.988373)
|
||||
bones/45/position = Vector3(-2.8403383e-07, 0.03459763, -4.4995545e-09)
|
||||
bones/45/rotation = Quaternion(0.16023898, 1.233635e-06, 0.013053549, 0.98699194)
|
||||
bones/46/position = Vector3(2.8485744e-07, 0.03680191, -9.493007e-09)
|
||||
bones/47/position = Vector3(-0.022166327, 0.12146996, -9.982591e-05)
|
||||
bones/47/rotation = Quaternion(0.1774532, 0.0045050457, 0.02645378, 0.98376334)
|
||||
bones/48/position = Vector3(4.6136024e-09, 0.036011953, 3.056637e-08)
|
||||
bones/48/rotation = Quaternion(0.18173504, 1.8501213e-06, 0.014812392, 0.98323596)
|
||||
bones/49/position = Vector3(-3.407626e-08, 0.033073153, 1.8664657e-08)
|
||||
bones/49/rotation = Quaternion(0.15551038, 3.26233e-07, 0.012668259, 0.98775303)
|
||||
bones/50/position = Vector3(2.3113911e-07, 0.03660121, -1.1142021e-08)
|
||||
bones/51/position = Vector3(-0.047258336, 0.109081805, 0.0022614468)
|
||||
bones/51/rotation = Quaternion(0.22849467, 0.003407588, 0.042977322, 0.97259015)
|
||||
bones/52/position = Vector3(-1.5261804e-08, 0.041366428, -1.9607107e-09)
|
||||
bones/52/rotation = Quaternion(0.1515478, 7.4422354e-07, 0.01235583, 0.98837274)
|
||||
bones/53/position = Vector3(1.4340932e-08, 0.025948457, 3.3946527e-08)
|
||||
bones/53/rotation = Quaternion(0.14377311, 5.8452275e-07, 0.011701506, 0.98954153)
|
||||
bones/54/position = Vector3(4.750415e-09, 0.029238641, -1.2388978e-09)
|
||||
bones/55/position = Vector3(0.09123873, -0.06657194, -0.0005540352)
|
||||
bones/55/rotation = Quaternion(0.136, 0.07837879, 0.98449296, -0.07832257)
|
||||
bones/56/position = Vector3(-1.3288446e-09, 0.40599436, 1.0443015e-08)
|
||||
bones/56/rotation = Quaternion(-0.1325433, -0.014470397, 0.03215335, 0.9905499)
|
||||
bones/57/position = Vector3(5.707578e-09, 0.42099008, -1.5425995e-08)
|
||||
bones/57/rotation = Quaternion(0.5344709, -0.021695312, -0.027452054, 0.8444624)
|
||||
bones/58/position = Vector3(-6.5650525e-09, 0.15721555, -2.6694579e-08)
|
||||
bones/58/rotation = Quaternion(0.2693438, -0.032380052, -0.014592491, 0.96238893)
|
||||
bones/59/position = Vector3(2.4854705e-08, 0.09999996, -3.1755625e-09)
|
||||
bones/60/position = Vector3(-0.091250315, -0.066556, -0.0005535231)
|
||||
bones/60/rotation = Quaternion(-0.09026675, -0.016730117, 0.9954149, -0.026855767)
|
||||
bones/61/position = Vector3(1.3129254e-08, 0.40599442, 3.8557886e-09)
|
||||
bones/61/rotation = Quaternion(-0.07409451, 0.08402694, -0.024555309, 0.9934016)
|
||||
bones/62/position = Vector3(-7.3885964e-10, 0.42099023, -5.226429e-09)
|
||||
bones/62/rotation = Quaternion(0.5654398, 0.01134551, -0.008516049, 0.8246677)
|
||||
bones/63/position = Vector3(7.93632e-09, 0.1572156, -2.6982683e-10)
|
||||
bones/63/rotation = Quaternion(0.2890854, 0.031904142, 0.014081784, 0.9566679)
|
||||
bones/64/position = Vector3(7.567915e-10, 0.099999994, -3.2595668e-09)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
|
||||
shape = SubResource("SphereShape3D_mx8sn")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="Player"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.31670225, 0)
|
||||
transform = Transform3D(0.9989785, -4.651856e-10, -0.045188628, 0.006969331, 0.9880354, 0.15407, 0.044647958, -0.15422754, 0.9870261, 0.22036135, 1.8988357, 0.64972365)
|
||||
current = true
|
||||
fov = 49.0
|
||||
|
||||
[node name="SpotLight3D" type="SpotLight3D" parent="Player"]
|
||||
|
||||
[node name="Car" parent="." instance=ExtResource("5_car")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -3)
|
||||
|
||||
[node name="DialogZone" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.5, 0, -2.5)
|
||||
script = ExtResource("6_dialog")
|
||||
prompt_text = "Press E to inspect area"
|
||||
dialog_text = "Dialog trigger area"
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="DialogZone"]
|
||||
shape = SubResource("SphereShape3D_dialog_zone")
|
||||
|
||||
[node name="Visual" type="MeshInstance3D" parent="DialogZone"]
|
||||
mesh = SubResource("SphereMesh_dialog_zone")
|
||||
|
||||
[node name="AutoDialogZone" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, -6.5)
|
||||
script = ExtResource("6_dialog")
|
||||
dialog_text = "Auto dialog trigger"
|
||||
auto_popup = true
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="AutoDialogZone"]
|
||||
shape = SubResource("SphereShape3D_dialog_zone")
|
||||
|
||||
[node name="Visual" type="MeshInstance3D" parent="AutoDialogZone"]
|
||||
transform = Transform3D(0.8, 0, 0, 0, 0.8, 0, 0, 0, 0.8, 0, 0, 0)
|
||||
mesh = SubResource("SphereMesh_dialog_zone")
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
|
||||
|
||||
@@ -79,6 +251,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
|
||||
shape = SubResource("BoxShape3D_2q6dc")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Ground"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00053596497, 0.0075991154, -0.0019865036)
|
||||
mesh = SubResource("BoxMesh_w7c3h")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
@@ -134,9 +307,38 @@ text = "Main Menu"
|
||||
layout_mode = 2
|
||||
text = "Quit"
|
||||
|
||||
[node name="PhoneUI" type="CanvasLayer" parent="."]
|
||||
layer = 5
|
||||
visible = false
|
||||
|
||||
[node name="Control" type="Control" parent="PhoneUI"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="PhoneFrame" type="ColorRect" parent="PhoneUI/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 = -180.0
|
||||
offset_top = -320.0
|
||||
offset_right = 180.0
|
||||
offset_bottom = 320.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.08, 0.08, 0.1, 1)
|
||||
|
||||
[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"]
|
||||
|
||||
[editable path="Player/TestCharAnimated"]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
extends Node3D
|
||||
|
||||
@export var tile_size := 4.0
|
||||
@export var block_height := 1.0
|
||||
|
||||
@onready var _block: MeshInstance3D = $TerrainBlock
|
||||
@onready var _camera: Camera3D = $Camera3D
|
||||
|
||||
func _ready() -> void:
|
||||
var coord := SelectedCharacter.get_coord()
|
||||
var block_pos := Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size)
|
||||
_block.position = block_pos
|
||||
_block.scale = Vector3(tile_size, block_height, tile_size)
|
||||
if _camera:
|
||||
_camera.look_at(block_pos, Vector3.UP)
|
||||
@@ -0,0 +1 @@
|
||||
uid://1fico5npv6dy
|
||||
@@ -0,0 +1,23 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b7p7k1i4t0m2l"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/Levels/location_level.gd" id="1_6y4q1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yu2x4"]
|
||||
albedo_color = Color(0.2, 0.6, 0.2, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_t2a5k"]
|
||||
material = SubResource("StandardMaterial3D_yu2x4")
|
||||
|
||||
[node name="LocationLevel" type="Node3D"]
|
||||
script = ExtResource("1_6y4q1")
|
||||
|
||||
[node name="TerrainBlock" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("BoxMesh_t2a5k")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.819152, 0.573576, 0, -0.573576, 0.819152, 0, 6, 0)
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.92388, 0.382683, 0, -0.382683, 0.92388, 0, 6, 10)
|
||||
current = true
|
||||
+45
-42
@@ -1,51 +1,54 @@
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
|
||||
func resume_game():
|
||||
get_tree().paused = false
|
||||
visible = false
|
||||
|
||||
func _on_quit_button_pressed():
|
||||
get_tree().quit()
|
||||
|
||||
func _on_continue_button_pressed():
|
||||
resume_game()
|
||||
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
func _on_quit_button_pressed():
|
||||
get_tree().quit()
|
||||
|
||||
func _on_continue_button_pressed():
|
||||
resume_game()
|
||||
|
||||
func _on_main_menu_button_pressed():
|
||||
resume_game()
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://b7fopt7sx74g8
|
||||
uid://b7fopt7sx74g8
|
||||
|
||||
+112
-112
@@ -1,112 +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()
|
||||
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()
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://h1slqbemgwvr
|
||||
uid://h1slqbemgwvr
|
||||
|
||||
+157
-157
@@ -1,157 +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
|
||||
[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
|
||||
|
||||
@@ -1,15 +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 = ""
|
||||
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 = ""
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://ccloj2rh4dche
|
||||
uid://ccloj2rh4dche
|
||||
|
||||
+132
-117
@@ -1,79 +1,107 @@
|
||||
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."
|
||||
|
||||
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_select_button_pressed() -> void:
|
||||
var selected := _character_list.get_selected_items()
|
||||
if selected.is_empty():
|
||||
_status_label.text = "Select a character first."
|
||||
return
|
||||
|
||||
var index := selected[0]
|
||||
if index < 0 or index >= _characters.size():
|
||||
@@ -81,49 +109,36 @@ func _on_delete_button_pressed() -> void:
|
||||
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")
|
||||
SelectedCharacter.set_character(character)
|
||||
get_tree().change_scene_to_file("res://scenes/Levels/location_level.tscn")
|
||||
|
||||
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")
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c2y7ftq2k3v4x
|
||||
uid://c2y7ftq2k3v4x
|
||||
|
||||
@@ -1,131 +1,139 @@
|
||||
[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
|
||||
|
||||
[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="SelectButton" type="Button" parent="MarginContainer/ContentCenter/ContentVBox/ActionHBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
theme = ExtResource("4_5b3b7")
|
||||
text = "SELECT"
|
||||
text_alignment = 1
|
||||
|
||||
[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
|
||||
|
||||
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/SelectButton" to="." method="_on_select_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"]
|
||||
[connection signal="request_completed" from="LogoutRequest" to="." method="_on_logout_request_completed"]
|
||||
|
||||
@@ -1,58 +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
|
||||
}
|
||||
extends Node
|
||||
|
||||
const CHARACTER_API_URL := "https://pchar.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
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c8kchv0e77yw4
|
||||
uid://c8kchv0e77yw4
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
extends CanvasLayer
|
||||
|
||||
var _interactables: Array[Node] = []
|
||||
var _active_interactable: Node = null
|
||||
var _dialog_visible := false
|
||||
|
||||
var _prompt_label: Label
|
||||
var _dialog_panel: PanelContainer
|
||||
var _dialog_label: RichTextLabel
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 50
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_setup_ui()
|
||||
_set_dialog_visible(false)
|
||||
_update_prompt_visibility()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("interact"):
|
||||
if _dialog_visible:
|
||||
_set_dialog_visible(false)
|
||||
return
|
||||
if _active_interactable != null:
|
||||
_open_dialog_for(_active_interactable)
|
||||
|
||||
|
||||
func register_interactable(interactable: Node) -> void:
|
||||
if interactable == null:
|
||||
return
|
||||
if _interactables.has(interactable):
|
||||
return
|
||||
_interactables.append(interactable)
|
||||
_refresh_active_interactable()
|
||||
|
||||
|
||||
func unregister_interactable(interactable: Node) -> void:
|
||||
if interactable == null:
|
||||
return
|
||||
_interactables.erase(interactable)
|
||||
if _active_interactable == interactable and _dialog_visible:
|
||||
_set_dialog_visible(false)
|
||||
_refresh_active_interactable()
|
||||
|
||||
|
||||
func _refresh_active_interactable() -> void:
|
||||
while _interactables.size() > 0 and not is_instance_valid(_interactables[_interactables.size() - 1]):
|
||||
_interactables.pop_back()
|
||||
_active_interactable = _interactables[_interactables.size() - 1] if _interactables.size() > 0 else null
|
||||
if _dialog_visible and _active_interactable != null:
|
||||
_dialog_label.text = _get_dialog_text(_active_interactable)
|
||||
_update_prompt_visibility()
|
||||
|
||||
|
||||
func _open_dialog_for(interactable: Node) -> void:
|
||||
if _dialog_label:
|
||||
_dialog_label.text = _get_dialog_text(interactable)
|
||||
_set_dialog_visible(true)
|
||||
|
||||
|
||||
func show_text(text: String) -> void:
|
||||
if _dialog_label:
|
||||
_dialog_label.text = text
|
||||
_set_dialog_visible(true)
|
||||
|
||||
|
||||
func close_if_text(expected_text: String) -> void:
|
||||
if not _dialog_visible:
|
||||
return
|
||||
if _dialog_label == null:
|
||||
return
|
||||
if _dialog_label.text != expected_text:
|
||||
return
|
||||
_set_dialog_visible(false)
|
||||
|
||||
|
||||
func _set_dialog_visible(dialog_open: bool) -> void:
|
||||
_dialog_visible = dialog_open
|
||||
if _dialog_panel:
|
||||
_dialog_panel.visible = _dialog_visible
|
||||
_update_prompt_visibility()
|
||||
|
||||
|
||||
func _update_prompt_visibility() -> void:
|
||||
if _prompt_label == null:
|
||||
return
|
||||
var has_active := _active_interactable != null
|
||||
_prompt_label.visible = has_active and not _dialog_visible
|
||||
if _prompt_label.visible:
|
||||
_prompt_label.text = _get_prompt_text(_active_interactable)
|
||||
|
||||
|
||||
func _get_prompt_text(interactable: Node) -> String:
|
||||
if interactable == null:
|
||||
return ""
|
||||
if interactable.has_method("get_dialog_prompt"):
|
||||
return String(interactable.call("get_dialog_prompt"))
|
||||
return "Press E to interact"
|
||||
|
||||
|
||||
func _get_dialog_text(interactable: Node) -> String:
|
||||
if interactable == null:
|
||||
return ""
|
||||
if interactable.has_method("get_dialog_text"):
|
||||
return String(interactable.call("get_dialog_text"))
|
||||
return "..."
|
||||
|
||||
|
||||
func _setup_ui() -> void:
|
||||
var root := Control.new()
|
||||
root.name = "DialogRoot"
|
||||
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(root)
|
||||
|
||||
_prompt_label = Label.new()
|
||||
_prompt_label.name = "PromptLabel"
|
||||
_prompt_label.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
||||
_prompt_label.offset_left = -140.0
|
||||
_prompt_label.offset_top = -64.0
|
||||
_prompt_label.offset_right = 140.0
|
||||
_prompt_label.offset_bottom = -36.0
|
||||
_prompt_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
_prompt_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_prompt_label.text = "Press E to interact"
|
||||
root.add_child(_prompt_label)
|
||||
|
||||
_dialog_panel = PanelContainer.new()
|
||||
_dialog_panel.name = "DialogPanel"
|
||||
_dialog_panel.visible = false
|
||||
_dialog_panel.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
||||
_dialog_panel.offset_left = -300.0
|
||||
_dialog_panel.offset_top = -220.0
|
||||
_dialog_panel.offset_right = 300.0
|
||||
_dialog_panel.offset_bottom = -80.0
|
||||
_dialog_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
root.add_child(_dialog_panel)
|
||||
|
||||
_dialog_label = RichTextLabel.new()
|
||||
_dialog_label.name = "DialogText"
|
||||
_dialog_label.custom_minimum_size = Vector2(560.0, 120.0)
|
||||
_dialog_label.fit_content = true
|
||||
_dialog_label.scroll_active = false
|
||||
_dialog_label.bbcode_enabled = true
|
||||
_dialog_label.text = ""
|
||||
_dialog_panel.add_child(_dialog_label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://be24usagr8kml
|
||||
@@ -1,48 +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
|
||||
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
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bnrhapdcfvp04
|
||||
uid://bnrhapdcfvp04
|
||||
|
||||
+117
-117
@@ -1,117 +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"]
|
||||
[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"]
|
||||
|
||||
+107
-107
@@ -1,107 +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)
|
||||
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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://l0cqi7dvoou3
|
||||
uid://l0cqi7dvoou3
|
||||
|
||||
@@ -1,13 +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")
|
||||
[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")
|
||||
|
||||
+76
-76
@@ -1,76 +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)
|
||||
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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c7ixr4hbh5ad6
|
||||
uid://c7ixr4hbh5ad6
|
||||
|
||||
@@ -1,13 +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")
|
||||
[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,16 @@
|
||||
extends Node
|
||||
|
||||
var character: Dictionary = {}
|
||||
|
||||
func set_character(data: Dictionary) -> void:
|
||||
character = data
|
||||
|
||||
func clear() -> void:
|
||||
character = {}
|
||||
|
||||
func get_coord() -> Vector2:
|
||||
var coord: Dictionary = character.get("coord", {})
|
||||
return Vector2(
|
||||
float(coord.get("x", 0)),
|
||||
float(coord.get("y", 0))
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bbl0our5y4k7a
|
||||
@@ -1,66 +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"
|
||||
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 +1 @@
|
||||
uid://cc8lskf7y74kh
|
||||
uid://cc8lskf7y74kh
|
||||
|
||||
+100
-100
@@ -1,100 +1,100 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://b4k81taauef4q"]
|
||||
|
||||
[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="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"]
|
||||
|
||||
[node name="StartScreen" 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_o7i0r")
|
||||
|
||||
[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_r2jwc")
|
||||
|
||||
[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 = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[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
|
||||
size_flags_horizontal = 4
|
||||
theme = ExtResource("4_hm208")
|
||||
text = "PROJECT
|
||||
PROMISCUOUS"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="TitleSpacer" type="Control" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="LogInButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = ExtResource("1_tx5wa")
|
||||
text = "LOG IN"
|
||||
|
||||
[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
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = ExtResource("1_tx5wa")
|
||||
text = "SETTINGS"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = ExtResource("1_tx5wa")
|
||||
text = "QUIT"
|
||||
|
||||
[node name="LogoutRequest" type="HTTPRequest" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[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"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://b4k81taauef4q"]
|
||||
|
||||
[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="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"]
|
||||
|
||||
[node name="StartScreen" 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_o7i0r")
|
||||
|
||||
[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_r2jwc")
|
||||
|
||||
[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 = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[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
|
||||
size_flags_horizontal = 4
|
||||
theme = ExtResource("4_hm208")
|
||||
text = "PROJECT
|
||||
PROMISCUOUS"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="TitleSpacer" type="Control" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/CenterContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="LogInButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = ExtResource("1_tx5wa")
|
||||
text = "LOG IN"
|
||||
|
||||
[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
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = ExtResource("1_tx5wa")
|
||||
text = "SETTINGS"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="MarginContainer/CenterContainer/ContentVBox/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = ExtResource("1_tx5wa")
|
||||
text = "QUIT"
|
||||
|
||||
[node name="LogoutRequest" type="HTTPRequest" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[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,105 @@
|
||||
extends RigidBody3D
|
||||
|
||||
@export var drive_speed := 18.0
|
||||
@export var drive_accel := 20.0
|
||||
@export var brake_strength := 28.0
|
||||
@export var turn_speed := 2.0
|
||||
@export var turn_accel := 8.0
|
||||
@export var lateral_damp := 10.0
|
||||
@export var launch_impulse := 28.0
|
||||
@export var launch_up_impulse := 6.0
|
||||
@export var seat_path: NodePath
|
||||
@export var exit_path: NodePath
|
||||
@export var camera_path: NodePath
|
||||
@export var interact_area_path: NodePath
|
||||
|
||||
@onready var seat: Node3D = get_node(seat_path) if seat_path != NodePath("") else null
|
||||
@onready var exit_point: Node3D = get_node(exit_path) if exit_path != NodePath("") else null
|
||||
@onready var car_camera: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
|
||||
@onready var interact_area: Area3D = get_node(interact_area_path) if interact_area_path != NodePath("") else null
|
||||
|
||||
var _nearby_driver: Node = null
|
||||
var _driver: Node = null
|
||||
|
||||
func _ready() -> void:
|
||||
if interact_area:
|
||||
interact_area.collision_layer = 2
|
||||
interact_area.collision_mask = 1
|
||||
interact_area.body_entered.connect(_on_interact_body_entered)
|
||||
interact_area.body_exited.connect(_on_interact_body_exited)
|
||||
if car_camera:
|
||||
car_camera.current = false
|
||||
contact_monitor = true
|
||||
max_contacts_reported = 8
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _driver == null and _nearby_driver != null and Input.is_action_just_pressed("interact"):
|
||||
_enter_vehicle(_nearby_driver)
|
||||
elif _driver != null and Input.is_action_just_pressed("interact"):
|
||||
_exit_vehicle()
|
||||
|
||||
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
|
||||
if _driver == null:
|
||||
return
|
||||
|
||||
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||
var forward := global_transform.basis.z
|
||||
forward.y = 0.0
|
||||
forward = forward.normalized()
|
||||
|
||||
var current_speed := linear_velocity.dot(forward)
|
||||
var target_speed := input2v.y * drive_speed
|
||||
var accel := drive_accel if abs(input2v.y) > 0.01 else brake_strength
|
||||
var new_forward_speed := move_toward(current_speed, target_speed, accel * state.step)
|
||||
var forward_vel := forward * new_forward_speed
|
||||
var lateral_vel := linear_velocity - (forward * current_speed)
|
||||
lateral_vel = lateral_vel.move_toward(Vector3.ZERO, lateral_damp * state.step)
|
||||
linear_velocity = forward_vel + lateral_vel
|
||||
|
||||
var speed_factor: float = clamp(abs(new_forward_speed) / drive_speed, 0.0, 1.0)
|
||||
var turn_input := input2v.x
|
||||
if new_forward_speed < -0.1:
|
||||
turn_input = -turn_input
|
||||
var target_turn: float = turn_input * turn_speed * speed_factor
|
||||
angular_velocity.y = move_toward(angular_velocity.y, target_turn, turn_accel * state.step)
|
||||
|
||||
if speed_factor > 0.2:
|
||||
var hit_ids := {}
|
||||
for i in state.get_contact_count():
|
||||
var collider := state.get_contact_collider_object(i)
|
||||
if collider is RigidBody3D and collider != self:
|
||||
var collider_id := (collider as Node).get_instance_id()
|
||||
if hit_ids.has(collider_id):
|
||||
continue
|
||||
hit_ids[collider_id] = true
|
||||
var contact_pos := state.get_contact_collider_position(i)
|
||||
var launch_dir := (contact_pos - global_position).normalized()
|
||||
if launch_dir.length() <= 0.001:
|
||||
var normal_world := (global_transform.basis * state.get_contact_local_normal(i)).normalized()
|
||||
launch_dir = normal_world if normal_world.length() > 0.001 else forward.normalized()
|
||||
var impulse := launch_dir * (launch_impulse * speed_factor) + Vector3.UP * launch_up_impulse
|
||||
(collider as RigidBody3D).apply_central_impulse(impulse)
|
||||
|
||||
func _enter_vehicle(player: Node) -> void:
|
||||
if seat == null:
|
||||
return
|
||||
_driver = player
|
||||
player.call("enter_vehicle", self, seat, car_camera)
|
||||
if car_camera:
|
||||
car_camera.current = true
|
||||
|
||||
func _exit_vehicle() -> void:
|
||||
if _driver == null:
|
||||
return
|
||||
_driver.call("exit_vehicle", exit_point, car_camera)
|
||||
_driver = null
|
||||
if car_camera:
|
||||
car_camera.current = false
|
||||
|
||||
func _on_interact_body_entered(body: Node) -> void:
|
||||
if body.has_method("enter_vehicle"):
|
||||
_nearby_driver = body
|
||||
|
||||
func _on_interact_body_exited(body: Node) -> void:
|
||||
if body == _nearby_driver:
|
||||
_nearby_driver = null
|
||||
@@ -0,0 +1 @@
|
||||
uid://4qf5yinepytc
|
||||
@@ -0,0 +1,43 @@
|
||||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/Vehicles/car.gd" id="1_kbd20"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_7r1j6"]
|
||||
size = Vector3(1.4, 0.9, 2.6)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_jk0m1"]
|
||||
size = Vector3(2.2, 2.0, 3.8)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_4y8xk"]
|
||||
size = Vector3(1.4, 0.9, 2.6)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_red"]
|
||||
albedo_color = Color(0.85, 0.1, 0.1, 1)
|
||||
|
||||
[node name="Car" type="RigidBody3D"]
|
||||
script = ExtResource("1_kbd20")
|
||||
seat_path = NodePath("Seat")
|
||||
exit_path = NodePath("Exit")
|
||||
camera_path = NodePath("CarCamera")
|
||||
interact_area_path = NodePath("InteractArea")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("BoxShape3D_7r1j6")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("BoxMesh_4y8xk")
|
||||
surface_material_override/0 = SubResource("StandardMaterial3D_red")
|
||||
|
||||
[node name="InteractArea" type="Area3D" parent="."]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="InteractArea"]
|
||||
shape = SubResource("BoxShape3D_jk0m1")
|
||||
|
||||
[node name="Seat" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0, 0.6, 0.0)
|
||||
|
||||
[node name="Exit" type="Node3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3, 0.0, 0.0)
|
||||
|
||||
[node name="CarCamera" type="Camera3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0, 2.0, 4.2)
|
||||
+21
-21
@@ -1,21 +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")
|
||||
[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")
|
||||
|
||||
+104
-3
@@ -4,11 +4,11 @@ extends RigidBody3D
|
||||
# of collisions. So that's why we're using a RigidBody3D instead.
|
||||
|
||||
const MOVE_SPEED := 8.0
|
||||
const SPRINT_MOVE_SPEED :=13
|
||||
const ACCELLERATION := 30.0
|
||||
const DECELLERATION := 40.0
|
||||
const JUMP_SPEED := 4.0
|
||||
const MAX_NUMBER_OF_JUMPS := 2
|
||||
|
||||
const MIN_FOV := 10
|
||||
const MAX_FOV := 180
|
||||
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
|
||||
@@ -23,16 +23,35 @@ var _last_move_right := Vector3(1, 0, 0)
|
||||
var _camera_offset_local := Vector3.ZERO
|
||||
var _camera_yaw := 0.0
|
||||
var _camera_pitch := 0.0
|
||||
var _in_vehicle := false
|
||||
var _vehicle_collision_layer := 0
|
||||
var _vehicle_collision_mask := 0
|
||||
var _vehicle_original_parent: Node = null
|
||||
var _light_was_on := false
|
||||
var _jump_triggered := false
|
||||
@onready var _flashlight: SpotLight3D = $SpotLight3D
|
||||
@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer
|
||||
@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D
|
||||
|
||||
@export var camera_follow_speed := 10.0
|
||||
@export var anim_idle_name := "Idle"
|
||||
@export var anim_walk_name := "Walk"
|
||||
@export var anim_jump_name := "Jump"
|
||||
@export var anim_run_name := "Run"
|
||||
@export var anim_walk_speed_threshold := 0.25
|
||||
@export var anim_sprint_speed_threshold := 10.0
|
||||
|
||||
var jump_sound = preload("res://assets/audio/jump.ogg")
|
||||
var audio_player = AudioStreamPlayer.new()
|
||||
|
||||
@export var camera_path: NodePath
|
||||
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
|
||||
@export var phone_path: NodePath
|
||||
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
|
||||
var phone_visible := false
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
axis_lock_angular_x = true
|
||||
axis_lock_angular_z = true
|
||||
angular_damp = 6.0
|
||||
@@ -57,8 +76,13 @@ func _ready() -> void:
|
||||
_last_move_forward = forward.normalized()
|
||||
if right.length() > 0.0001:
|
||||
_last_move_right = right.normalized()
|
||||
_vehicle_collision_layer = collision_layer
|
||||
_vehicle_collision_mask = collision_mask
|
||||
|
||||
func _integrate_forces(state):
|
||||
if _in_vehicle:
|
||||
linear_velocity = Vector3.ZERO
|
||||
return
|
||||
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
|
||||
rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
|
||||
rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
|
||||
@@ -69,6 +93,11 @@ func _integrate_forces(state):
|
||||
|
||||
# Input as 2D vector
|
||||
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||
|
||||
if Input.is_action_just_pressed("player_phone"):
|
||||
phone_visible = !phone_visible
|
||||
if phone:
|
||||
phone.visible = phone_visible
|
||||
|
||||
# Camera based movement
|
||||
var forward := Vector3.FORWARD * -1.0
|
||||
@@ -91,8 +120,13 @@ func _integrate_forces(state):
|
||||
right = _last_move_right
|
||||
|
||||
var dir := (right * input2v.x + forward * input2v.y).normalized()
|
||||
|
||||
var target_v := dir * MOVE_SPEED
|
||||
|
||||
# Sprinting
|
||||
if Input.is_key_pressed(KEY_SHIFT):
|
||||
target_v = dir * SPRINT_MOVE_SPEED
|
||||
|
||||
var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION
|
||||
linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step)
|
||||
linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step)
|
||||
@@ -109,6 +143,7 @@ func _integrate_forces(state):
|
||||
current_number_of_jumps = (current_number_of_jumps + 1) % 2
|
||||
linear_velocity.y = JUMP_SPEED
|
||||
audio_player.play()
|
||||
_jump_triggered = true
|
||||
|
||||
if cam:
|
||||
var target_yaw := global_transform.basis.get_euler().y
|
||||
@@ -118,7 +153,12 @@ func _integrate_forces(state):
|
||||
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
|
||||
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
|
||||
|
||||
_update_animation(on_floor, state.linear_velocity)
|
||||
_jump_triggered = false
|
||||
|
||||
func _input(event):
|
||||
if _in_vehicle:
|
||||
return
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_MIDDLE:
|
||||
if event.pressed:
|
||||
@@ -138,9 +178,70 @@ func _input(event):
|
||||
zoom_camera(ZOOM_FACTOR) # Zoom out
|
||||
|
||||
if event.is_action_pressed("player_light"):
|
||||
$SpotLight3D.visible = !$SpotLight3D.visible
|
||||
_flashlight.visible = !_flashlight.visible
|
||||
|
||||
func zoom_camera(factor):
|
||||
var new_fov = cam.fov * factor
|
||||
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)
|
||||
|
||||
|
||||
func _update_animation(on_floor: bool, velocity: Vector3) -> void:
|
||||
if _anim_player == null:
|
||||
return
|
||||
var horizontal_speed := Vector3(velocity.x, 0.0, velocity.z).length()
|
||||
if _jump_triggered and _anim_player.has_animation(anim_jump_name):
|
||||
if _anim_player.current_animation != anim_jump_name:
|
||||
_anim_player.play(anim_jump_name)
|
||||
return
|
||||
if not on_floor and _anim_player.has_animation(anim_jump_name):
|
||||
if _anim_player.current_animation != anim_jump_name:
|
||||
_anim_player.play(anim_jump_name)
|
||||
return
|
||||
if on_floor and horizontal_speed > anim_sprint_speed_threshold and _anim_player.has_animation(anim_run_name):
|
||||
if _anim_player.current_animation != anim_walk_name:
|
||||
_anim_player.play(anim_walk_name)
|
||||
return
|
||||
if horizontal_speed > anim_walk_speed_threshold and _anim_player.has_animation(anim_walk_name):
|
||||
if _anim_player.current_animation != anim_walk_name:
|
||||
_anim_player.play(anim_walk_name)
|
||||
return
|
||||
if _anim_player.has_animation(anim_idle_name):
|
||||
if _anim_player.current_animation != anim_idle_name:
|
||||
_anim_player.play(anim_idle_name)
|
||||
|
||||
func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void:
|
||||
_in_vehicle = true
|
||||
freeze = true
|
||||
sleeping = true
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
_vehicle_original_parent = get_parent()
|
||||
_light_was_on = _flashlight.visible
|
||||
_flashlight.visible = false
|
||||
if _model_root:
|
||||
_model_root.visible = false
|
||||
if seat:
|
||||
reparent(seat, true)
|
||||
global_transform = seat.global_transform
|
||||
if cam:
|
||||
cam.current = false
|
||||
if vehicle_camera:
|
||||
vehicle_camera.current = true
|
||||
|
||||
func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void:
|
||||
_in_vehicle = false
|
||||
freeze = false
|
||||
sleeping = false
|
||||
collision_layer = _vehicle_collision_layer
|
||||
collision_mask = _vehicle_collision_mask
|
||||
if _vehicle_original_parent:
|
||||
reparent(_vehicle_original_parent, true)
|
||||
_vehicle_original_parent = null
|
||||
_flashlight.visible = _light_was_on
|
||||
if _model_root:
|
||||
_model_root.visible = true
|
||||
if exit_point:
|
||||
global_transform = exit_point.global_transform
|
||||
if vehicle_camera:
|
||||
vehicle_camera.current = false
|
||||
if cam:
|
||||
cam.current = true
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bpxggc8nr6tf6
|
||||
uid://bpxggc8nr6tf6
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
[gd_resource type="Theme" load_steps=7 format=3 uid="uid://wpxmub0n2dr3"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="1_qnv5y"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kwhvy"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bpa63"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6abfs"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hguu3"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wu4fv"]
|
||||
|
||||
[resource]
|
||||
Button/colors/font_color = Color(0.875, 0.875, 0.875, 1)
|
||||
Button/colors/font_disabled_color = Color(0.875, 0.875, 0.875, 0.5)
|
||||
Button/colors/font_focus_color = Color(0.95, 0.95, 0.95, 1)
|
||||
Button/colors/font_hover_color = Color(0.95, 0.95, 0.95, 1)
|
||||
Button/colors/font_hover_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/colors/font_outline_color = Color(8.834152, 8.190666, 9.316767, 1)
|
||||
Button/colors/font_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_disabled_color = Color(1, 1, 1, 0.4)
|
||||
Button/colors/icon_focus_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_hover_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_hover_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_normal_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/constants/align_to_largest_stylebox = 0
|
||||
Button/constants/h_separation = 4
|
||||
Button/constants/icon_max_width = 0
|
||||
Button/constants/outline_size = 0
|
||||
Button/font_sizes/font_size = 32
|
||||
Button/fonts/font = ExtResource("1_qnv5y")
|
||||
Button/styles/disabled = SubResource("StyleBoxEmpty_kwhvy")
|
||||
Button/styles/focus = SubResource("StyleBoxEmpty_bpa63")
|
||||
Button/styles/hover = SubResource("StyleBoxEmpty_6abfs")
|
||||
Button/styles/normal = SubResource("StyleBoxEmpty_hguu3")
|
||||
Button/styles/pressed = SubResource("StyleBoxEmpty_wu4fv")
|
||||
[gd_resource type="Theme" load_steps=7 format=3 uid="uid://wpxmub0n2dr3"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="1_qnv5y"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kwhvy"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_bpa63"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6abfs"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hguu3"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wu4fv"]
|
||||
|
||||
[resource]
|
||||
Button/colors/font_color = Color(0.875, 0.875, 0.875, 1)
|
||||
Button/colors/font_disabled_color = Color(0.875, 0.875, 0.875, 0.5)
|
||||
Button/colors/font_focus_color = Color(0.95, 0.95, 0.95, 1)
|
||||
Button/colors/font_hover_color = Color(0.95, 0.95, 0.95, 1)
|
||||
Button/colors/font_hover_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/colors/font_outline_color = Color(8.834152, 8.190666, 9.316767, 1)
|
||||
Button/colors/font_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_disabled_color = Color(1, 1, 1, 0.4)
|
||||
Button/colors/icon_focus_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_hover_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_hover_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_normal_color = Color(1, 1, 1, 1)
|
||||
Button/colors/icon_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/constants/align_to_largest_stylebox = 0
|
||||
Button/constants/h_separation = 4
|
||||
Button/constants/icon_max_width = 0
|
||||
Button/constants/outline_size = 0
|
||||
Button/font_sizes/font_size = 32
|
||||
Button/fonts/font = ExtResource("1_qnv5y")
|
||||
Button/styles/disabled = SubResource("StyleBoxEmpty_kwhvy")
|
||||
Button/styles/focus = SubResource("StyleBoxEmpty_bpa63")
|
||||
Button/styles/hover = SubResource("StyleBoxEmpty_6abfs")
|
||||
Button/styles/normal = SubResource("StyleBoxEmpty_hguu3")
|
||||
Button/styles/pressed = SubResource("StyleBoxEmpty_wu4fv")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://tn8qndst18d6"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="1_vd7w8"]
|
||||
|
||||
[resource]
|
||||
Label/font_sizes/font_size = 60
|
||||
Label/fonts/font = ExtResource("1_vd7w8")
|
||||
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://tn8qndst18d6"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://m5ceou0rk6j6" path="res://assets/fonts/PlayfairDisplay-VariableFont_wght.ttf" id="1_vd7w8"]
|
||||
|
||||
[resource]
|
||||
Label/font_sizes/font_size = 60
|
||||
Label/fonts/font = ExtResource("1_vd7w8")
|
||||
|
||||
+428
-14
@@ -1,14 +1,428 @@
|
||||
# ---> 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
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.env
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
|
||||
[Dd]ebug/x64/
|
||||
[Dd]ebugPublic/x64/
|
||||
[Rr]elease/x64/
|
||||
[Rr]eleases/x64/
|
||||
bin/x64/
|
||||
obj/x64/
|
||||
|
||||
[Dd]ebug/x86/
|
||||
[Dd]ebugPublic/x86/
|
||||
[Rr]elease/x86/
|
||||
[Rr]eleases/x86/
|
||||
bin/x86/
|
||||
obj/x86/
|
||||
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build results on 'Bin' directories
|
||||
**/[Bb]in/*
|
||||
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||
# (https://github.com/github/gitignore/pull/3736)
|
||||
#!**/[Bb]in/*.refresh
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.trx
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Approval Tests result files
|
||||
*.received.*
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
**/.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
**/.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
**/.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
#tools/**
|
||||
#!tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
MSBuild_Logs/
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
**/.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
**/.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
**/.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.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
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
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.
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,17 +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>
|
||||
<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>
|
||||
|
||||
@@ -1,6 +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>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>https</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,6 +1,6 @@
|
||||
@AuthApi_HostAddress = http://localhost:5279
|
||||
|
||||
GET {{AuthApi_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@AuthApi_HostAddress = http://localhost:5279
|
||||
|
||||
GET {{AuthApi_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
@@ -1,113 +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);
|
||||
}
|
||||
}
|
||||
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,49 @@
|
||||
# AuthApi document shapes
|
||||
|
||||
This service expects JSON request bodies for its auth endpoints and stores user
|
||||
documents in MongoDB.
|
||||
|
||||
Inbound JSON documents
|
||||
- RegisterRequest (`POST /api/auth/register`)
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"password": "string",
|
||||
"email": "string (optional)"
|
||||
}
|
||||
```
|
||||
- LoginRequest (`POST /api/auth/login`)
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"password": "string"
|
||||
}
|
||||
```
|
||||
- RefreshRequest (`POST /api/auth/refresh`)
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"refreshToken": "string"
|
||||
}
|
||||
```
|
||||
- ChangeRoleRequest (`POST /api/auth/role`)
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"newRole": "USER | SUPER"
|
||||
}
|
||||
```
|
||||
|
||||
Stored documents (MongoDB)
|
||||
- User
|
||||
```json
|
||||
{
|
||||
"id": "string (ObjectId)",
|
||||
"username": "string",
|
||||
"passwordHash": "string",
|
||||
"role": "USER | SUPER",
|
||||
"email": "string (optional)",
|
||||
"refreshToken": "string (optional)",
|
||||
"refreshTokenExpiry": "string (optional, ISO-8601 datetime)"
|
||||
}
|
||||
```
|
||||
@@ -1,21 +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"]
|
||||
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"]
|
||||
|
||||
@@ -1,6 +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; } = ""; }
|
||||
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; } = ""; }
|
||||
|
||||
@@ -1,16 +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; }
|
||||
}
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -1,86 +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();
|
||||
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();
|
||||
|
||||
@@ -1,23 +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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"$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,12 @@
|
||||
# AuthApi
|
||||
|
||||
## Document shapes
|
||||
See `DOCUMENTS.md` for request payloads and stored document shapes.
|
||||
|
||||
## Endpoints
|
||||
- `POST /api/auth/register` Register a new user.
|
||||
- `POST /api/auth/login` Issue access and refresh tokens.
|
||||
- `POST /api/auth/refresh` Refresh an access token.
|
||||
- `POST /api/auth/logout` Revoke the current access token.
|
||||
- `POST /api/auth/role` Update a user's role (SUPER only).
|
||||
- `GET /api/auth/users` List users (SUPER only).
|
||||
@@ -1,36 +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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,32 +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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +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": "*"
|
||||
}
|
||||
{
|
||||
"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": "*"
|
||||
}
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
{
|
||||
"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.
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user