21 Commits
Author SHA1 Message Date
admin 94ffe6e7b7 Add versioned legal consent to authentication
Deploy Promiscuity Auth API / deploy (push) Successful in 1m29s
k8s smoke test / test (push) Successful in 20s
2026-07-20 02:16:02 -05:00
admin 4121c59484 Reduce auth image restore disk usage
k8s smoke test / test (push) Failing after 20s
Deploy Promiscuity Auth API / deploy (push) Successful in 1m57s
2026-07-20 01:17:48 -05:00
admin c267b62afa Prevent deploy runner disk exhaustion
Deploy Promiscuity Auth API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
2026-07-20 01:15:13 -05:00
admin cde0e28e44 Add Steam OpenID authentication
Deploy Promiscuity Auth API / deploy (push) Failing after 20s
Deploy Promiscuity Character API / deploy (push) Failing after 19s
Deploy Promiscuity Crafting API / deploy (push) Failing after 20s
Deploy Promiscuity Inventory API / deploy (push) Failing after 19s
Deploy Promiscuity Locations API / deploy (push) Failing after 19s
Deploy Promiscuity Mail API / deploy (push) Failing after 20s
Deploy Promiscuity World API / deploy (push) Failing after 20s
k8s smoke test / test (push) Failing after 20s
2026-07-20 00:30:06 -05:00
admin 18c3d6fc5d Add self-service profile and account deletion APIs
Deploy Promiscuity Auth API / deploy (push) Successful in 1m28s
Deploy Promiscuity Character API / deploy (push) Successful in 1m24s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m20s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m24s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m25s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m20s
Deploy Promiscuity World API / deploy (push) Successful in 1m25s
k8s smoke test / test (push) Successful in 20s
2026-07-19 13:39:24 -05:00
admin 942cd59db8 Implmented first/third person toggle via V
Deploy Promiscuity Auth API / deploy (push) Successful in 1m25s
Deploy Promiscuity Character API / deploy (push) Successful in 1m23s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m19s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m25s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m26s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m21s
Deploy Promiscuity World API / deploy (push) Successful in 1m21s
k8s smoke test / test (push) Successful in 20s
2026-07-19 12:23:23 -05:00
admin 7faef2e976 Harden public account registration
Deploy Promiscuity Auth API / deploy (push) Successful in 1m13s
Deploy Promiscuity Character API / deploy (push) Successful in 59s
Deploy Promiscuity Crafting API / deploy (push) Successful in 58s
Deploy Promiscuity Inventory API / deploy (push) Successful in 58s
Deploy Promiscuity Locations API / deploy (push) Successful in 59s
Deploy Promiscuity Mail API / deploy (push) Successful in 59s
Deploy Promiscuity World API / deploy (push) Successful in 59s
k8s smoke test / test (push) Successful in 20s
2026-07-11 12:39:02 -05:00
null 7f21b40d93 Fix scroll wheel zoom, persist zoom, consolidate player scripts, and migrate GEMINI.md to .agents/AGENTS.md
Deploy Promiscuity Auth API / deploy (push) Successful in 1m23s
Deploy Promiscuity Character API / deploy (push) Successful in 1m1s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m0s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m1s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m2s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m2s
Deploy Promiscuity World API / deploy (push) Successful in 59s
k8s smoke test / test (push) Successful in 20s
2026-06-22 20:07:39 -05:00
admin 3b21cf3182 Adding generated sounds to car
Deploy Promiscuity Auth API / deploy (push) Successful in 1m14s
Deploy Promiscuity Character API / deploy (push) Successful in 1m14s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m14s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m13s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m13s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m13s
Deploy Promiscuity World API / deploy (push) Successful in 1m13s
k8s smoke test / test (push) Successful in 35s
2026-06-19 06:25:16 -05:00
pillboxstyx 9e3df6c691 Stealing a cooler car
Deploy Promiscuity Auth API / deploy (push) Successful in 1m36s
Deploy Promiscuity Character API / deploy (push) Successful in 1m15s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m14s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m14s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m16s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m30s
Deploy Promiscuity World API / deploy (push) Successful in 2m6s
k8s smoke test / test (push) Successful in 38s
2026-06-12 21:33:19 -05:00
null 2cb8a7fb8f Update mushroom pickup sound to use pop.ogg
Deploy Promiscuity Auth API / deploy (push) Successful in 59s
Deploy Promiscuity Character API / deploy (push) Successful in 1m0s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m0s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m0s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m0s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m0s
Deploy Promiscuity World API / deploy (push) Successful in 59s
k8s smoke test / test (push) Successful in 21s
2026-05-28 18:35:57 -05:00
null 09e6b4cc90 Add interactive mushroom pickup and highlight mechanics with pop sound
Deploy Promiscuity Auth API / deploy (push) Successful in 1m1s
Deploy Promiscuity Character API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
2026-05-28 18:34:35 -05:00
admin e6a442c98b Merge branch 'main' of https://git.ranaze.com/null/promiscuity into main
Deploy Promiscuity Auth API / deploy (push) Successful in 1m12s
Deploy Promiscuity Character API / deploy (push) Successful in 1m27s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m23s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m28s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m12s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m22s
Deploy Promiscuity World API / deploy (push) Successful in 1m22s
k8s smoke test / test (push) Successful in 22s
2026-05-21 18:31:00 -05:00
admin cce8fd3280 Adding ember character model to art directory 2026-05-21 18:30:32 -05:00
null ffee63dc57 feat: change camera rotation to be always active when mouse is captured
Deploy Promiscuity Auth API / deploy (push) Successful in 1m0s
Deploy Promiscuity Character API / deploy (push) Successful in 1m1s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m0s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m0s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m0s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m1s
Deploy Promiscuity World API / deploy (push) Successful in 1m1s
k8s smoke test / test (push) Successful in 21s
2026-05-12 17:19:34 -05:00
null 2907fabe85 feat: add spreading mushroom to playground level 2026-05-12 17:06:23 -05:00
null 3f3136c453 Update GEMINI documentation to reflect Godot 4.6 and expanded project structure 2026-05-12 16:52:57 -05:00
admin 84afc199fa Removing unsued glb files to reduce repo footprint
Deploy Promiscuity Auth API / deploy (push) Successful in 1m0s
Deploy Promiscuity Character API / deploy (push) Successful in 1m1s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m1s
Deploy Promiscuity Inventory API / deploy (push) Successful in 1m2s
Deploy Promiscuity Locations API / deploy (push) Successful in 1m20s
Deploy Promiscuity Mail API / deploy (push) Successful in 1m2s
Deploy Promiscuity World API / deploy (push) Successful in 1m0s
k8s smoke test / test (push) Successful in 21s
2026-05-12 15:46:19 -05:00
admin fecbefc15c Recommiting glbs for LFS
Deploy Promiscuity Auth API / deploy (push) Successful in 1m59s
Deploy Promiscuity Character API / deploy (push) Successful in 1m16s
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
2026-05-12 15:34:12 -05:00
pillboxstyx a9e546a121 Fireball, need to tweak the animation to get some grey at the end like the standing fire
Deploy Promiscuity Auth API / deploy (push) Has been cancelled
Deploy Promiscuity Character API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
SHIFT + B
2026-05-04 23:59:26 -05:00
deifiedexile 98220921bd Add radial command menu for minions
Deploy Promiscuity Auth API / deploy (push) Successful in 51s
Deploy Promiscuity Character API / deploy (push) Successful in 49s
Deploy Promiscuity Crafting API / deploy (push) Successful in 47s
Deploy Promiscuity Inventory API / deploy (push) Successful in 48s
Deploy Promiscuity Locations API / deploy (push) Successful in 47s
Deploy Promiscuity Mail API / deploy (push) Successful in 49s
Deploy Promiscuity World API / deploy (push) Successful in 48s
k8s smoke test / test (push) Successful in 9s
2026-05-01 12:24:54 -05:00
215 changed files with 9827 additions and 3251 deletions
+3 -3
View File
@@ -4,8 +4,8 @@ This project is a multi-layered application consisting of a Godot-based game and
## Project Structure ## Project Structure
- `game/`: The Godot 4.5 game project. - `game/`: The Godot 4.6 game project.
- `microservices/`: ASP.NET Core 8.0 microservices providing backend logic (Auth, Character, Inventory, Locations, etc.). - `microservices/`: ASP.NET Core 8.0 microservices providing backend logic (Auth, Character, Crafting, Inventory, Locations, Mail, and World).
- `.gitea/workflows/`: CI/CD pipelines for deploying microservices to Kubernetes. - `.gitea/workflows/`: CI/CD pipelines for deploying microservices to Kubernetes.
## Core Mandates ## Core Mandates
@@ -17,7 +17,7 @@ This project is a multi-layered application consisting of a Godot-based game and
## Getting Started ## Getting Started
- **Microservices**: Each microservice can be run individually or via the `micro-services.sln` solution file. They require a MongoDB instance (default: `mongodb://127.0.0.1:27017`). - **Microservices**: Each microservice can be run individually or via the `micro-services.sln` solution file. They require a MongoDB instance (default: `mongodb://127.0.0.1:27017`).
- **Game**: Open `game/project.godot` in Godot Engine 4.5+. - **Game**: Open `game/project.godot` in Godot Engine 4.6+.
## Shared Conventions ## Shared Conventions
+1 -3
View File
@@ -1,6 +1,5 @@
# Set the default behavior, in case people don't have core.autocrlf set. # Set the default behavior, in case people don't have core.autocrlf set.
* text=auto * text=auto
# Explicitly declare text files you want to always be normalized and converted # Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout. # to native line endings on checkout.
*.cs text diff=csharp *.cs text diff=csharp
@@ -14,11 +13,9 @@
*.yaml text *.yaml text
*.csproj text *.csproj text
*.sln text *.sln text
# Declare files that will always have CRLF line endings on checkout. # Declare files that will always have CRLF line endings on checkout.
*.bat text eol=crlf *.bat text eol=crlf
*.ps1 text eol=crlf *.ps1 text eol=crlf
# Denote all files that are truly binary and should not be modified. # Denote all files that are truly binary and should not be modified.
*.png binary *.png binary
*.jpg binary *.jpg binary
@@ -40,3 +37,4 @@
*.7z binary *.7z binary
*.import binary *.import binary
*.uid binary *.uid binary
*.glb filter=lfs diff=lfs merge=lfs -text
+28 -1
View File
@@ -4,6 +4,9 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/AuthApi/**"
- ".gitea/workflows/deploy-auth.yml"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
@@ -20,6 +23,16 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Reclaim Docker build space
run: |
rm -f "${IMAGE_TAR}"
docker container prune -f
docker image prune -f
docker image prune -af --filter "until=24h"
docker builder prune -af || true
docker system df
df -h
# ----------------------------- # -----------------------------
# Build Docker image # Build Docker image
# ----------------------------- # -----------------------------
@@ -59,18 +72,22 @@ jobs:
# CLEANUP: delete TAR from nodes # CLEANUP: delete TAR from nodes
# ----------------------------- # -----------------------------
- name: Clean TAR from nodes - name: Clean TAR from nodes
if: always()
run: | run: |
for node in ${NODES}; do for node in ${NODES}; do
echo "Removing image tar on $node ..." echo "Removing image tar on $node ..."
ssh -o StrictHostKeyChecking=no hz@"$node" "rm -f /tmp/promiscuity-auth.tar" ssh -o StrictHostKeyChecking=no hz@"$node" "rm -f /tmp/promiscuity-auth.tar" || true
done done
# ----------------------------- # -----------------------------
# CLEANUP: delete TAR from runner # CLEANUP: delete TAR from runner
# ----------------------------- # -----------------------------
- name: Clean TAR on runner - name: Clean TAR on runner
if: always()
run: | run: |
rm -f "${IMAGE_TAR}" rm -f "${IMAGE_TAR}"
docker image rm "${IMAGE_NAME}" 2>/dev/null || true
docker image prune -f
# ----------------------------- # -----------------------------
# Write kubeconfig from secret # Write kubeconfig from secret
@@ -91,6 +108,16 @@ jobs:
run: | run: |
kubectl create namespace promiscuity-auth --dry-run=client -o yaml | kubectl apply -f - kubectl create namespace promiscuity-auth --dry-run=client -o yaml | kubectl apply -f -
- name: Configure internal API secret
env:
KUBECONFIG: /tmp/kube/config
INTERNAL_PURGE_SECRET: ${{ secrets.INTERNAL_PURGE_SECRET }}
run: |
test -n "$INTERNAL_PURGE_SECRET"
kubectl create secret generic medmind-internal-api \
--from-literal=purge-secret="$INTERNAL_PURGE_SECRET" \
--dry-run=client -o yaml | kubectl apply -n promiscuity-auth -f -
# ----------------------------- # -----------------------------
# Apply Kubernetes manifests # Apply Kubernetes manifests
# (You create these files in your repo) # (You create these files in your repo)
+2
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/CharacterApi/**"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
+2
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/CraftingApi/**"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
+2
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/InventoryApi/**"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
+2
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/LocationsApi/**"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
+2
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/MailApi/**"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
+2
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
paths:
- "microservices/WorldApi/**"
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
+4
View File
@@ -18,5 +18,9 @@
- Sprint: Shift - Sprint: Shift
- Interact (enter/exit car): E - Interact (enter/exit car): E
- Flashlight: F - Flashlight: F
- Radial command menu: Q
- Fireball: Shift + B
- Phone: Tab - Phone: Tab
- Pause menu: Esc - Pause menu: Esc
- Toggle first/third person: V
- Zoom (in/out): Scroll Wheel (snaps to First Person on zoom in)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.
+23 -14
View File
@@ -1,21 +1,25 @@
# Godot Game Development - GEMINI.md # Godot Game Development - GEMINI.md
This directory contains the Godot 4.5 game project for "Promiscuity". This directory contains the Godot 4.6 game project for "Promiscuity".
## Core Mandates ## Core Mandates
- **Engine Version**: Godot 4.5 (Forward Plus). - **Engine Version**: Godot 4.6 (Forward Plus).
- **Language**: GDScript is the primary scripting language. C# is enabled but used only when performance or specific library needs arise. - **Language**: GDScript is the primary scripting language. C# is enabled but used only when performance or specific library needs arise.
- **Naming Conventions**: - **Naming Conventions**:
- Files/Folders: `snake_case.gd`, `snake_case.tscn`. - Files/Folders: `snake_case.gd`, `snake_case.tscn`.
- Variables/Functions: `snake_case`. - Variables/Functions: `snake_case`.
- Constants: `SCREAMING_SNAKE_CASE`. - Constants: `SCREAMING_SNAKE_CASE`.
- Classes: `PascalCase`. - Classes: `PascalCase`.
- **Autoloads**: Use the established singletons for global state and services: - **Autoloads**: Use the established singletons for global state and services:
- `AuthState`: JWT authentication and session. - `AuthState`: JWT authentication and session.
- `CharacterService`: API calls for character management. - `CharacterService`: API calls for character management.
- `QuestManager`: Game quest progression. - `SelectedCharacter`: Tracking the currently active character.
- `DialogSystem`: UI dialog and interaction. - `QuestManager`: Game quest progression.
- `DialogSystem`: UI dialog and interaction.
- `TeleportState`: Handling transitions between levels.
- `MenuMusic` / `MenuSfx`: Audio management.
- `SimpleGrass`: Singleton for grass rendering.
- **Node Paths**: Prefer `unique names` (%NodeName) or `@onready` variables for node references. Avoid fragile string paths. - **Node Paths**: Prefer `unique names` (%NodeName) or `@onready` variables for node references. Avoid fragile string paths.
## Asset Pipeline ## Asset Pipeline
@@ -27,11 +31,16 @@ This directory contains the Godot 4.5 game project for "Promiscuity".
## Project Organization ## Project Organization
- `scenes/`: Scene files and associated scripts. - `scenes/`: Scene files and associated scripts.
- `Characters/`: Player and NPC scenes. - `Characters/`: Player and NPC scenes.
- `Levels/`: Game world/environments. - `Levels/`: Game world/environments.
- `UI/`: Menus, HUDs, and UI components. - `UI/`: Menus, HUDs, and UI components.
- `Interaction/`: Interactive objects and systems (e.g., RadialCommandMenu).
- `Projectiles/`: Projectiles like fireballs.
- `Quests/`: Quest-related scenes and logic.
- `Vehicles/`: Controllable vehicles.
- `CardGame/`: Minigame components.
- `assets/`: Raw assets (textures, models, audio). - `assets/`: Raw assets (textures, models, audio).
- `addons/`: Third-party plugins (e.g., `simplegrasstextured`). - `addons/`: Third-party plugins (e.g., `simplegrasstextured`, `godot_mcp`).
## Best Practices ## Best Practices
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
uid://mcipi7y664aq
+7
View File
@@ -0,0 +1,7 @@
[plugin]
name="Godot MCP"
description="A plugin to enable communication between Godot Editor and Model Context Protocol (MCP) clients."
author="Your Name"
version="0.1.0"
script="plugin.gd"
+140
View File
@@ -0,0 +1,140 @@
# Structure for addons/godot_mcp/plugin.gd
@tool
extends EditorPlugin
const SERVER_PORT = 6400
var server: TCPServer = null
var active_connections = []
var command_handler
func _enter_tree():
# Initialize the plugin
print("Godot MCP Plugin activated")
# Create command handler
command_handler = preload("res://addons/godot_mcp/command_handler.gd").new()
command_handler.set_editor_plugin(self)
# Start the TCP server
server = TCPServer.new()
var error = server.listen(SERVER_PORT)
if error != OK:
push_error("Failed to start Godot MCP Server on port %d: %s" % [SERVER_PORT, error])
return
print("Godot MCP Server listening on port %d" % SERVER_PORT)
# Add UI
add_control_to_bottom_panel(
preload("res://addons/godot_mcp/ui/mcp_panel.tscn").instantiate(),
"MCP"
)
func _exit_tree():
# Clean up the plugin when disabled
if server:
server.stop()
server = null
for connection in active_connections:
if connection.get_status() == StreamPeerTCP.STATUS_CONNECTED:
connection.disconnect_from_host()
active_connections.clear()
# Remove UI
remove_control_from_bottom_panel(get_editor_interface().get_base_control().get_node("MCPPanel"))
print("Godot MCP Plugin deactivated")
func _process(delta):
# Check for new connections
if server and server.is_connection_available():
var connection = server.take_connection()
if connection:
active_connections.append(connection)
print("New MCP connection established")
# Process existing connections
var i = 0
while i < active_connections.size():
var connection = active_connections[i]
# Check connection status
if connection.get_status() != StreamPeerTCP.STATUS_CONNECTED:
active_connections.remove_at(i)
print("MCP connection closed")
continue
# Check for incoming messages
if connection.get_available_bytes() > 0:
var data = _read_message(connection)
if data.size() > 0:
# Process the command
var response = _process_command(data)
# Send the response
_send_message(connection, response)
i += 1
func _read_message(connection):
# Read data from the connection
var data = PackedByteArray()
var bytes_available = connection.get_available_bytes()
if bytes_available > 0:
data = connection.get_data(bytes_available)[1]
# Attempt to parse as JSON
var json_string = data.get_string_from_utf8()
var json = JSON.new()
var error = json.parse(json_string)
if error == OK:
return json.get_data()
else:
print("Failed to parse JSON: ", json.get_error_message())
return {}
func _send_message(connection, data):
# Convert to JSON and send
var json_string = JSON.stringify(data)
connection.put_data(json_string.to_utf8_buffer())
func _process_command(data):
# Process the command and return a response
if data == null or typeof(data) != TYPE_DICTIONARY:
return {
"status": "error",
"error": "Invalid command format. Expected a dictionary."
}
if not data.has("type") or not data.has("params"):
return {
"status": "error",
"error": "Invalid command format. Expected 'type' and 'params' fields."
}
var command_type = data["type"]
var params = data["params"]
if command_type == "ping":
return {"status": "success", "result": {"message": "pong"}}
# Forward to command handler
var result = command_handler.handle_command(command_type, params)
# Check if result is valid
if result == null:
return {
"status": "error",
"error": "Command handler returned null result"
}
if result.has("error"):
return {"status": "error", "error": result.error}
else:
return {"status": "success", "result": result}
+1
View File
@@ -0,0 +1 @@
uid://ddfhkiyge45tn
+77
View File
@@ -0,0 +1,77 @@
# addons/godot_mcp/ui/mcp_panel.gd
@tool
extends Control
var status_label: Label
var port_field: SpinBox
var start_button: Button
var stop_button: Button
var log_display: TextEdit
func _ready():
# Set up references to UI elements
status_label = $VBoxContainer/StatusPanel/StatusLabel
port_field = $VBoxContainer/ConfigPanel/PortField
start_button = $VBoxContainer/ButtonPanel/StartButton
stop_button = $VBoxContainer/ButtonPanel/StopButton
log_display = $VBoxContainer/LogPanel/LogDisplay
# Initialize UI
port_field.value = 6400 # Default port
start_button.disabled = false
stop_button.disabled = true
# Connect signals
start_button.pressed.connect(_on_start_button_pressed)
stop_button.pressed.connect(_on_stop_button_pressed)
# Set initial status
update_status("Not running")
add_log_message("Godot MCP Plugin initialized")
func update_status(status_text: String, is_error: bool = false):
status_label.text = "Status: " + status_text
if is_error:
status_label.add_theme_color_override("font_color", Color(1, 0.3, 0.3))
else:
status_label.remove_theme_color_override("font_color")
func add_log_message(message: String):
var timestamp = Time.get_datetime_string_from_system()
log_display.text += "[" + timestamp + "] " + message + "\n"
log_display.scroll_vertical = log_display.get_line_count()
func _on_start_button_pressed():
# This function will be called from the plugin.gd script
# when the server is actually started
update_status("Running on port " + str(port_field.value))
start_button.disabled = true
stop_button.disabled = false
add_log_message("MCP Server started on port " + str(port_field.value))
func _on_stop_button_pressed():
# This function will be called from the plugin.gd script
# when the server is actually stopped
update_status("Stopped")
start_button.disabled = false
stop_button.disabled = true
add_log_message("MCP Server stopped")
# Function to be called from plugin.gd when a client connects
func on_client_connected():
add_log_message("Client connected")
update_status("Client connected")
# Function to be called from plugin.gd when a client disconnects
func on_client_disconnected():
add_log_message("Client disconnected")
update_status("Running (no clients)")
# Function to be called from plugin.gd when a command is received
func on_command_received(command_type, params):
add_log_message("Command received: " + command_type)
# Function to be called from plugin.gd when a response is sent
func on_response_sent(command_type, success):
var status = "Success" if success else "Failed"
add_log_message("Response sent for " + command_type + ": " + status)
@@ -0,0 +1 @@
uid://bqabyd7ce60u0
+69
View File
@@ -0,0 +1,69 @@
[gd_scene load_steps=2 format=3 uid="uid://dxvt86ck6b2a4"]
[ext_resource type="Script" path="res://addons/godot_mcp/ui/mcp_panel.gd" id="1_4g23r"]
[node name="MCPPanel" 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_4g23r")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="StatusPanel" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="StatusLabel" type="Label" parent="VBoxContainer/StatusPanel"]
layout_mode = 2
text = "Status: Not running"
[node name="ConfigPanel" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/ConfigPanel"]
layout_mode = 2
[node name="PortLabel" type="Label" parent="VBoxContainer/ConfigPanel/HBoxContainer"]
layout_mode = 2
text = "Port:"
[node name="PortField" type="SpinBox" parent="VBoxContainer/ConfigPanel"]
layout_mode = 2
min_value = 1024.0
max_value = 65535.0
value = 6400.0
alignment = 1
[node name="ButtonPanel" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/ButtonPanel"]
layout_mode = 2
alignment = 1
[node name="StartButton" type="Button" parent="VBoxContainer/ButtonPanel"]
layout_mode = 2
text = "Start Server"
[node name="StopButton" type="Button" parent="VBoxContainer/ButtonPanel"]
layout_mode = 2
disabled = true
text = "Stop Server"
[node name="LogPanel" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="LogDisplay" type="TextEdit" parent="VBoxContainer/LogPanel"]
layout_mode = 2
editable = false
wrap_mode = 1
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dgt3nox7yak72"
path="res://.godot/imported/pop.ogg-e15d202808be009bef5e04746dba8616.oggvorbisstr"
[deps]
source_file="res://assets/audio/pop.ogg"
dest_files=["res://.godot/imported/pop.ogg-e15d202808be009bef5e04746dba8616.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b2e0ls6ymw45i"
path="res://.godot/imported/car_brake.wav-359538a9b1ba4240ae07e54477629dbb.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_brake.wav"
dest_files=["res://.godot/imported/car_brake.wav-359538a9b1ba4240ae07e54477629dbb.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dlg100ft5f2jy"
path="res://.godot/imported/car_engine_accel_loop.wav-25b55bfc6e1d41b17513ac3dd914f463.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_engine_accel_loop.wav"
dest_files=["res://.godot/imported/car_engine_accel_loop.wav-25b55bfc6e1d41b17513ac3dd914f463.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=1
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cljhbkwcswalc"
path="res://.godot/imported/car_engine_drive_loop.wav-be0d54cca48b174deaff89a90d5364c3.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_engine_drive_loop.wav"
dest_files=["res://.godot/imported/car_engine_drive_loop.wav-be0d54cca48b174deaff89a90d5364c3.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=1
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://c28v6t3txgn8w"
path="res://.godot/imported/car_engine_idle_loop.wav-56323a2fbc32742f6519b3630f97c275.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_engine_idle_loop.wav"
dest_files=["res://.godot/imported/car_engine_idle_loop.wav-56323a2fbc32742f6519b3630f97c275.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=1
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bhmyl3ie10u53"
path="res://.godot/imported/car_enter.wav-aeff3918a465be3f557bb484cf84ed62.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_enter.wav"
dest_files=["res://.godot/imported/car_enter.wav-aeff3918a465be3f557bb484cf84ed62.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bngm3s2yq2xwi"
path="res://.godot/imported/car_exit.wav-f78ac0b868ae38aeb13d15881f6c3491.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_exit.wav"
dest_files=["res://.godot/imported/car_exit.wav-f78ac0b868ae38aeb13d15881f6c3491.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bq2d35mekg4il"
path="res://.godot/imported/car_hit.wav-5f5ca7b335ab69bea356887a03a1eacc.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_hit.wav"
dest_files=["res://.godot/imported/car_hit.wav-5f5ca7b335ab69bea356887a03a1eacc.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://y38ybkt3uxik"
path="res://.godot/imported/car_horn.wav-4f978ca1d198f5c6c4a5fcfab9ca1ccc.sample"
[deps]
source_file="res://assets/audio/vehicles/car/car_horn.wav"
dest_files=["res://.godot/imported/car_horn.wav-4f978ca1d198f5c6c4a5fcfab9ca1ccc.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2
Binary file not shown.
@@ -0,0 +1,42 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bi13nqi11oqhx"
path="res://.godot/imported/AshlingSwarmer.glb-69896cc28ddf5352ad63aa98f43893d5.scn"
[deps]
source_file="res://assets/models/AshlingSwarmer.glb"
dest_files=["res://.godot/imported/AshlingSwarmer.glb-69896cc28ddf5352ad63aa98f43893d5.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.
Binary file not shown.
@@ -0,0 +1,21 @@
Copyright (c) 2023 Manik Sharma.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
Car Demo by Manik Sharma
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
@@ -3,13 +3,13 @@
importer="scene" importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://cpuebxqhtv72i" uid="uid://civr7hlbf2kqb"
path="res://.godot/imported/humanFemale.glb-45dfb97e0f5fa98244d8cb6255565c65.scn" path="res://.godot/imported/Wheel.glb-dddddd96691989c3e912750c9a47a858.scn"
[deps] [deps]
source_file="res://assets/models/humanFemale.glb" source_file="res://assets/models/vehicles/doge/Wheel.glb"
dest_files=["res://.godot/imported/humanFemale.glb-45dfb97e0f5fa98244d8cb6255565c65.scn"] dest_files=["res://.godot/imported/Wheel.glb-dddddd96691989c3e912750c9a47a858.scn"]
[params] [params]
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bpklxkc61avg6"
path.s3tc="res://.godot/imported/Wheel_RIMMUSCLE_01_DIF.png-5bb5174c5332f74f16abee062d1ed24a.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "cd5730d572d3e9053a526f83b6971226"
}
[deps]
source_file="res://assets/models/vehicles/doge/Wheel_RIMMUSCLE_01_DIF.png"
dest_files=["res://.godot/imported/Wheel_RIMMUSCLE_01_DIF.png-5bb5174c5332f74f16abee062d1ed24a.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://djs3o5hanxb70"
path.s3tc="res://.godot/imported/Wheel_RIMMUSCLE_01_MET-RIMMUSCLE_01_ROUGH.png-bf036ae2ba8ef4208853e0caa49ac9e7.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "ee91568e12f40d7fce962016fe4791c0"
}
[deps]
source_file="res://assets/models/vehicles/doge/Wheel_RIMMUSCLE_01_MET-RIMMUSCLE_01_ROUGH.png"
dest_files=["res://.godot/imported/Wheel_RIMMUSCLE_01_MET-RIMMUSCLE_01_ROUGH.png-bf036ae2ba8ef4208853e0caa49ac9e7.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cxufd33f0bqsx"
path.s3tc="res://.godot/imported/Wheel_RIMMUSCLE_01_NRM.png-3149169313dea5a53591f1b5ae2411f6.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "7d86721466688c1cd90634fe6d2f953a"
}
[deps]
source_file="res://assets/models/vehicles/doge/Wheel_RIMMUSCLE_01_NRM.png"
dest_files=["res://.godot/imported/Wheel_RIMMUSCLE_01_NRM.png-3149169313dea5a53591f1b5ae2411f6.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=1
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=1
roughness/src_normal="res://assets/models/vehicles/doge/Wheel_RIMMUSCLE_01_NRM.png"
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.
@@ -0,0 +1,42 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dvgiooankbnw3"
path="res://.godot/imported/doge-body.glb-d066dc7fd60eda6d05736bc65e3c5ffe.scn"
[deps]
source_file="res://assets/models/vehicles/doge/doge-body.glb"
dest_files=["res://.godot/imported/doge-body.glb-d066dc7fd60eda6d05736bc65e3c5ffe.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.

After

Width:  |  Height:  |  Size: 2.1 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dx401inrlplo1"
path.s3tc="res://.godot/imported/doge-body_GEN4DR_INTERIOR_dif.png-b5a1b7fcdb7b3160f7a0ef7e265e4505.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "b3caf55dd0d9e1e05294308101435166"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_GEN4DR_INTERIOR_dif.png"
dest_files=["res://.godot/imported/doge-body_GEN4DR_INTERIOR_dif.png-b5a1b7fcdb7b3160f7a0ef7e265e4505.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c63xvtkbodiob"
path.s3tc="res://.godot/imported/doge-body_GENERIC_BADGES_DIF.png-796b015ef1e73a8329f18564cd4000af.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "f8fce006f08ef57c4bb09b493a6126d8"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_GENERIC_BADGES_DIF.png"
dest_files=["res://.godot/imported/doge-body_GENERIC_BADGES_DIF.png-796b015ef1e73a8329f18564cd4000af.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://e5okom0atmrt"
path.s3tc="res://.godot/imported/doge-body_Numberplates_dif.png-c03cf9c73e5d679f02921a9bb6c29d63.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "da3da05f00d3caef6299f64ac091b52f"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_Numberplates_dif.png"
dest_files=["res://.godot/imported/doge-body_Numberplates_dif.png-c03cf9c73e5d679f02921a9bb6c29d63.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 942 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://2sf47hajdi3w"
path.s3tc="res://.godot/imported/doge-body_Riverside88_lights_dif.png-05ac8d7af58447f0ddebfe66c3707ac9.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "c3cf8ceff55bcc14da25561e2939d3be"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_Riverside88_lights_dif.png"
dest_files=["res://.godot/imported/doge-body_Riverside88_lights_dif.png-05ac8d7af58447f0ddebfe66c3707ac9.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c83x8syn3olne"
path.s3tc="res://.godot/imported/doge-body_UCB_BOTTOM_DIF.png-cfc19977dbe1d864173f653d08cb0672.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "0eabb927a9e9ac2e0de40610dbadc3a6"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_UCB_BOTTOM_DIF.png"
dest_files=["res://.godot/imported/doge-body_UCB_BOTTOM_DIF.png-cfc19977dbe1d864173f653d08cb0672.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://crxpnsmfsorf5"
path.s3tc="res://.godot/imported/doge-body_UCB_BOTTOM_nrm.png-b5e3eff64de1f5b644e5d70c8b7bfe37.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "ce8201694c51a8565f01b6f7688262bf"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_UCB_BOTTOM_nrm.png"
dest_files=["res://.godot/imported/doge-body_UCB_BOTTOM_nrm.png-b5e3eff64de1f5b644e5d70c8b7bfe37.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=1
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=1
roughness/src_normal="res://assets/models/vehicles/doge/doge-body_UCB_BOTTOM_nrm.png"
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dxcjtnnch0sxr"
path.s3tc="res://.godot/imported/doge-body_UCB_GLASS_CLEAN_dif.png-423a718e8cd643fb68b5ef6cfd665641.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "f8a5958ba2b852aaf7e4733c7ca90a83"
}
[deps]
source_file="res://assets/models/vehicles/doge/doge-body_UCB_GLASS_CLEAN_dif.png"
dest_files=["res://.godot/imported/doge-body_UCB_GLASS_CLEAN_dif.png-423a718e8cd643fb68b5ef6cfd665641.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bpcj2xno5aykg"
path="res://.godot/imported/ashling_swarmer_palette.png-c771be7c592755dce8c857fa3b76a603.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/textures/ashling_swarmer/ashling_swarmer_palette.png"
dest_files=["res://.godot/imported/ashling_swarmer_palette.png-c771be7c592755dce8c857fa3b76a603.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
+17 -1
View File
@@ -56,6 +56,7 @@ CharacterService="*res://scenes/UI/character_service.gd"
SelectedCharacter="*res://scenes/UI/selected_character.gd" SelectedCharacter="*res://scenes/UI/selected_character.gd"
DialogSystem="*res://scenes/UI/dialog_system.gd" DialogSystem="*res://scenes/UI/dialog_system.gd"
QuestManager="*res://scenes/Quests/quest_manager.gd" QuestManager="*res://scenes/Quests/quest_manager.gd"
TeleportState="*res://scenes/Interaction/teleport_state.gd"
SimpleGrass="*res://addons/simplegrasstextured/singleton.tscn" SimpleGrass="*res://addons/simplegrasstextured/singleton.tscn"
[dotnet] [dotnet]
@@ -64,7 +65,7 @@ project/assembly_name="Promiscuity"
[editor_plugins] [editor_plugins]
enabled=PackedStringArray("res://addons/simplegrasstextured/plugin.cfg") enabled=PackedStringArray("res://addons/godot_mcp/plugin.cfg", "res://addons/simplegrasstextured/plugin.cfg")
[input] [input]
@@ -105,16 +106,31 @@ 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) "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_fireball={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
player_phone={ player_phone={
"deadzone": 0.2, "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) "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)
] ]
} }
player_toggle_perspective={
"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":86,"key_label":0,"unicode":118,"location":0,"echo":false,"script":null)
]
}
interact={ interact={
"deadzone": 0.2, "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) "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)
] ]
} }
OpenRadialCommandMenu={
"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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
[shader_globals] [shader_globals]
+110
View File
@@ -0,0 +1,110 @@
extends Node3D
@export var model_path: NodePath = NodePath("AshlingSwarmerModel")
@export var default_animation: StringName = &"Idle"
@export var autoplay: bool = true
var _animation_player: AnimationPlayer
var _model_node: Node3D
var _current_animation: StringName = &""
func _ready() -> void:
call_deferred("_setup_animation")
func _setup_animation() -> void:
_model_node = get_node_or_null(model_path) as Node3D
_animation_player = _find_animation_player(_model_node)
if _animation_player == null:
if autoplay:
push_warning("AshlingSwarmer: no AnimationPlayer found under %s." % model_path)
return
if autoplay:
play_animation(default_animation)
func play_animation(animation_name: StringName = &"") -> void:
if _animation_player == null:
_animation_player = _find_animation_player(get_node_or_null(model_path))
if _animation_player == null:
return
var requested_animation := animation_name if animation_name != StringName() else default_animation
var resolved_animation := _resolve_animation_name(requested_animation)
if resolved_animation == StringName():
push_warning("AshlingSwarmer: animation '%s' not found. Available animations: %s" % [requested_animation, _animation_player.get_animation_list()])
return
var animation := _animation_player.get_animation(resolved_animation)
if animation != null:
animation.loop_mode = Animation.LOOP_LINEAR
_animation_player.play(resolved_animation)
_current_animation = _classify_animation(resolved_animation)
func play_idle() -> void:
play_animation(&"Idle")
func play_run() -> void:
play_animation(&"Run")
func play_attack() -> void:
play_animation(&"Attack_Leap")
func play_hit() -> void:
play_animation(&"Hit")
func play_death() -> void:
play_animation(&"Death_Explode")
func _resolve_animation_name(animation_name: StringName) -> StringName:
if _animation_player.has_animation(animation_name):
return animation_name
var desired := String(animation_name).to_lower()
for candidate in _animation_player.get_animation_list():
var candidate_text := String(candidate)
if candidate_text.to_lower() == desired:
return StringName(candidate)
for candidate in _animation_player.get_animation_list():
var candidate_text := String(candidate)
if candidate_text.to_lower().contains(desired):
return StringName(candidate)
for candidate in _animation_player.get_animation_list():
var candidate_text := String(candidate)
if candidate_text.to_lower().contains("idle"):
return StringName(candidate)
var animations := _animation_player.get_animation_list()
if animations.size() > 0:
return StringName(animations[0])
return StringName()
func _classify_animation(animation_name: StringName) -> StringName:
var text := String(animation_name).to_lower()
if text.contains("run"):
return &"Run"
if text.contains("idle"):
return &"Idle"
if text.contains("attack"):
return &"Attack_Leap"
if text.contains("hit"):
return &"Hit"
if text.contains("death"):
return &"Death_Explode"
return animation_name
func _find_animation_player(root: Node) -> AnimationPlayer:
if root == null:
return null
if root is AnimationPlayer:
return root
for child in root.get_children():
var found := _find_animation_player(child)
if found != null:
return found
return null
@@ -0,0 +1 @@
uid://dptxcarifu7e1
@@ -0,0 +1,36 @@
[gd_scene load_steps=5 format=3]
[ext_resource type="Script" path="res://scenes/Characters/ashling_swarmer.gd" id="1_script"]
[ext_resource type="PackedScene" path="res://assets/models/AshlingSwarmer.glb" id="2_model"]
[ext_resource type="Script" path="res://scenes/Interaction/dialog_trigger_area.gd" id="3_dialog"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"]
radius = 0.45
height = 1.6
[sub_resource type="SphereShape3D" id="SphereShape3D_interact"]
radius = 1.8
[node name="AshlingSwarmer" type="Node3D"]
script = ExtResource("1_script")
autoplay = false
[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.8, 0)
shape = SubResource("CapsuleShape3D_body")
[node name="AshlingSwarmerModel" parent="." instance=ExtResource("2_model")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
[node name="InteractArea" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0)
script = ExtResource("3_dialog")
collision_layer = 2
collision_mask = 1
prompt_text = "Press E to inspect"
dialog_text = "Ashling Swarmer: A small ember-born creature watches you."
[node name="CollisionShape3D" type="CollisionShape3D" parent="InteractArea"]
shape = SubResource("SphereShape3D_interact")
-249
View File
@@ -1,249 +0,0 @@
extends RigidBody3D
# Initially I used a CharacterBody3D, however, I wanted the player to bounce off
# other objects in the environment and that would have required manual handling
# of collisions. So that's why we're using a RigidBody3D instead.
signal vehicle_entered(vehicle: Node)
signal vehicle_exited(vehicle: Node)
const MOVE_SPEED := 8.0
const SPRINT_MOVE_SPEED :=13
const ACCELLERATION := 30.0
const DECELLERATION := 40.0
const JUMP_SPEED := 4.0
const MAX_NUMBER_OF_JUMPS := 2
const MIN_FOV := 10.0
const MAX_FOV := 179.0
const ZOOM_FACTOR := 1.1 # Zoom out when >1, in when < 1
var mouse_sensitivity := 0.005
var rotation_x := 0.0
var rotation_y := 0.0
var cameraMoveMode := false
var current_number_of_jumps := 0
var _pending_mouse_delta := Vector2.ZERO
var _last_move_forward := Vector3(0, 0, 1)
var _last_move_right := Vector3(1, 0, 0)
var _camera_offset_local := Vector3.ZERO
var _camera_yaw := 0.0
var _camera_pitch := 0.0
var _in_vehicle := false
var _vehicle_collision_layer := 0
var _vehicle_collision_mask := 0
var _vehicle_original_parent: Node = null
var _light_was_on := false
var _jump_triggered := false
@onready var _flashlight: SpotLight3D = $SpotLight3D
@onready var _anim_player: AnimationPlayer = find_child("AnimationPlayer", true, false) as AnimationPlayer
@onready var _anim_tree: AnimationTree = find_child("AnimationTree", true, false) as AnimationTree
@onready var _model_root: Node3D = find_child("TestCharAnimated", true, false) as Node3D
@export var camera_follow_speed := 10.0
@export var anim_idle_name := "Idle"
@export var anim_walk_name := "Walk"
@export var anim_jump_name := "Jump"
@export var anim_run_name := "Run"
@export var anim_walk_speed_threshold := 0.25
@export var anim_sprint_speed_threshold := 10.0
var jump_sound = preload("res://assets/audio/jump.ogg")
var audio_player = AudioStreamPlayer.new()
@export var camera_path: NodePath
@onready var cam: Camera3D = get_node(camera_path) if camera_path != NodePath("") else null
@export var phone_path: NodePath
@onready var phone: CanvasLayer = get_node(phone_path) if phone_path != NodePath("") else null
var phone_visible := false
func _ready() -> void:
add_to_group("player")
if _anim_tree:
_anim_tree.active = false
axis_lock_angular_x = true
axis_lock_angular_z = true
angular_damp = 6.0
contact_monitor = true
max_contacts_reported = 4
add_child(audio_player)
audio_player.stream = jump_sound
audio_player.volume_db = -20
if cam:
_camera_offset_local = cam.transform.origin
_camera_pitch = cam.rotation.x
_camera_yaw = global_transform.basis.get_euler().y
cam.set_as_top_level(true)
cam.global_position = global_position + (Basis(Vector3.UP, _camera_yaw) * _camera_offset_local)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
var move_basis := cam.global_transform.basis if cam else global_transform.basis
var forward := move_basis.z
var right := move_basis.x
forward.y = 0.0
right.y = 0.0
if forward.length() > 0.0001:
_last_move_forward = forward.normalized()
if right.length() > 0.0001:
_last_move_right = right.normalized()
_vehicle_collision_layer = collision_layer
_vehicle_collision_mask = collision_mask
func _integrate_forces(state):
if _in_vehicle:
linear_velocity = Vector3.ZERO
return
if cameraMoveMode and _pending_mouse_delta != Vector2.ZERO:
rotation_x -= _pending_mouse_delta.y * mouse_sensitivity
rotation_y -= _pending_mouse_delta.x * mouse_sensitivity
rotation_x = clamp(rotation_x, deg_to_rad(-90), deg_to_rad(90))
_camera_pitch = rotation_x
rotation.y = rotation_y
_pending_mouse_delta = Vector2.ZERO
var input2v := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var forward := Vector3.FORWARD * -1.0
var right := Vector3.RIGHT
if cam:
forward = cam.global_transform.basis.z
right = cam.global_transform.basis.x
forward.y = 0.0
right.y = 0.0
if forward.length() > 0.0001:
forward = forward.normalized()
_last_move_forward = forward
else:
forward = _last_move_forward
if right.length() > 0.0001:
right = right.normalized()
_last_move_right = right
else:
right = _last_move_right
var dir := (right * input2v.x + forward * input2v.y).normalized()
var target_v := dir * MOVE_SPEED
if Input.is_key_pressed(KEY_SHIFT):
target_v = dir * SPRINT_MOVE_SPEED
var ax := ACCELLERATION if dir != Vector3.ZERO else DECELLERATION
linear_velocity.x = move_toward(linear_velocity.x, target_v.x, ax * state.step)
linear_velocity.z = move_toward(linear_velocity.z, target_v.z, ax * state.step)
var on_floor = false
for i in state.get_contact_count():
var normal = state.get_contact_local_normal(i)
if normal.y > 0.5:
on_floor = true
break
if Input.is_action_just_pressed("ui_accept") and (on_floor or current_number_of_jumps == 1):
current_number_of_jumps = (current_number_of_jumps + 1) % 2
linear_velocity.y = JUMP_SPEED
audio_player.play()
_jump_triggered = true
if cam:
var target_yaw := global_transform.basis.get_euler().y
_camera_yaw = lerp_angle(_camera_yaw, target_yaw, camera_follow_speed * state.step)
var target_basis := Basis(Vector3.UP, _camera_yaw)
var target_pos := global_position + (target_basis * _camera_offset_local)
cam.global_position = cam.global_position.lerp(target_pos, camera_follow_speed * state.step)
cam.global_rotation = Vector3(_camera_pitch, _camera_yaw, 0.0)
_update_animation(on_floor, state.linear_velocity)
_jump_triggered = false
func _input(event):
if event.is_action_pressed("player_phone"):
phone_visible = !phone_visible
if phone:
phone.visible = phone_visible
return
if _in_vehicle:
return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_MIDDLE:
if event.pressed:
cameraMoveMode = true
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
else:
cameraMoveMode = false
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
if event is InputEventMouseMotion and cameraMoveMode:
_pending_mouse_delta += event.relative
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom_camera(1.0 / ZOOM_FACTOR)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom_camera(ZOOM_FACTOR)
if event.is_action_pressed("player_light"):
_flashlight.visible = !_flashlight.visible
func zoom_camera(factor):
if cam == null:
return
var new_fov = cam.fov * factor
cam.fov = clamp(new_fov, MIN_FOV, MAX_FOV)
func _update_animation(on_floor: bool, velocity: Vector3) -> void:
if _anim_player == null:
return
var horizontal_speed := Vector3(velocity.x, 0.0, velocity.z).length()
if _jump_triggered and _anim_player.has_animation(anim_jump_name):
if _anim_player.current_animation != anim_jump_name:
_anim_player.play(anim_jump_name)
return
if not on_floor and _anim_player.has_animation(anim_jump_name):
if _anim_player.current_animation != anim_jump_name:
_anim_player.play(anim_jump_name)
return
if on_floor and horizontal_speed > anim_sprint_speed_threshold and _anim_player.has_animation(anim_run_name):
if _anim_player.current_animation != anim_run_name:
_anim_player.play(anim_run_name)
return
if horizontal_speed > anim_walk_speed_threshold and _anim_player.has_animation(anim_walk_name):
if _anim_player.current_animation != anim_walk_name:
_anim_player.play(anim_walk_name)
return
if _anim_player.has_animation(anim_idle_name):
if _anim_player.current_animation != anim_idle_name:
_anim_player.play(anim_idle_name)
func enter_vehicle(_vehicle: Node, seat: Node3D, vehicle_camera: Camera3D) -> void:
_in_vehicle = true
freeze = true
sleeping = true
collision_layer = 0
collision_mask = 0
_vehicle_original_parent = get_parent()
_light_was_on = _flashlight.visible
_flashlight.visible = false
if _model_root:
_model_root.visible = false
if seat:
reparent(seat, true)
global_transform = seat.global_transform
if cam:
cam.current = false
if vehicle_camera:
vehicle_camera.current = true
vehicle_entered.emit(_vehicle)
func exit_vehicle(exit_point: Node3D, vehicle_camera: Camera3D) -> void:
_in_vehicle = false
freeze = false
sleeping = false
collision_layer = _vehicle_collision_layer
collision_mask = _vehicle_collision_mask
if _vehicle_original_parent:
reparent(_vehicle_original_parent, true)
_vehicle_original_parent = null
_flashlight.visible = _light_was_on
if _model_root:
_model_root.visible = true
if exit_point:
global_transform = exit_point.global_transform
if vehicle_camera:
vehicle_camera.current = false
if cam:
cam.current = true
vehicle_exited.emit(null)
@@ -1 +0,0 @@
uid://kgqaeqappow3
+1 -1
View File
@@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3] [gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://scenes/Characters/location_player.gd" id="1_player_script"] [ext_resource type="Script" path="res://scenes/player.gd" id="1_player_script"]
[ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="2_model"] [ext_resource type="PackedScene" path="res://assets/models/TestCharAnimated.glb" id="2_model"]
[sub_resource type="SphereShape3D" id="SphereShape3D_player"] [sub_resource type="SphereShape3D" id="SphereShape3D_player"]
@@ -0,0 +1,60 @@
extends Control
@export var radius: float = 150.0
@export var transition_speed: float = 0.2
func _ready():
hide() # Start hidden
_arrange_buttons()
func _input(event):
if event.is_action_pressed("OpenRadialCommandMenu"): # Map 'Q' to "open_menu" in Input Map
_toggle_menu()
func _toggle_menu():
visible = !visible
if visible:
# Center the menu on the mouse or screen
global_position = get_viewport().get_mouse_position()
_animate_menu(true)
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
_animate_menu(false)
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _arrange_buttons():
var buttons = get_children().filter(func(child): return child is Control)
var count = buttons.size()
if count == 0:
return
for i in range(count):
# Calculate the angle for each button in radians
var angle = i * (2 * PI / count)
# Standard circle math: x = cos(a) * r, y = sin(a) * r
var target_pos = Vector2(cos(angle), sin(angle)) * radius
# Offset by button size to keep them centered
buttons[i].position = target_pos - (buttons[i].size / 2)
func _animate_menu(opening: bool):
var tween = create_tween().set_parallel(true)
for button in get_children().filter(func(child): return child is Control):
var final_scale = Vector2.ONE if opening else Vector2.ZERO
tween.tween_property(button, "scale", final_scale, transition_speed).from(Vector2.ZERO if opening else Vector2.ONE)
func _on_btn_follow_pressed() -> void:
print("Follow command pressed")
_toggle_menu() # Closes the menu after clicking
func _on_btn_go_to_pressed() -> void:
print("Go to command pressed")
_toggle_menu() # Closes the menu after clicking
func _on_btn_attack_pressed() -> void:
print("Attack command pressed")
_toggle_menu() # Closes the menu after clicking
@@ -0,0 +1 @@
uid://cjfhlslc5vp7b
@@ -0,0 +1,42 @@
[gd_scene format=3 uid="uid://bnwpu7p8sbsfa"]
[ext_resource type="Script" uid="uid://cjfhlslc5vp7b" path="res://scenes/Interaction/RadialCommandMenu.gd" id="1_j0xof"]
[node name="RadialCommandCanvas" type="CanvasLayer" unique_id=1002896311]
[node name="PivotControl" type="Control" parent="." unique_id=374070149]
layout_mode = 3
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -20.0
offset_top = -20.0
offset_right = 20.0
offset_bottom = 20.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_j0xof")
[node name="BtnFollow" type="Button" parent="PivotControl" unique_id=1839549428]
layout_mode = 0
offset_right = 8.0
offset_bottom = 8.0
text = "Follow"
[node name="BtnGoTo" type="Button" parent="PivotControl" unique_id=818273339]
layout_mode = 0
offset_right = 8.0
offset_bottom = 8.0
text = "Go To"
[node name="BtnAttack" type="Button" parent="PivotControl" unique_id=744805255]
layout_mode = 0
offset_right = 8.0
offset_bottom = 8.0
text = "Attack"
[connection signal="pressed" from="PivotControl/BtnFollow" to="PivotControl" method="_on_btn_follow_pressed"]
[connection signal="pressed" from="PivotControl/BtnGoTo" to="PivotControl" method="_on_btn_go_to_pressed"]
[connection signal="pressed" from="PivotControl/BtnAttack" to="PivotControl" method="_on_btn_attack_pressed"]
+189
View File
@@ -0,0 +1,189 @@
extends Node3D
@export var spread_radius := 2.0
@export var spread_interval_min := 5.0
@export var spread_interval_max := 15.0
@export var max_mushrooms_in_radius := 3
@export var check_radius := 1.5
@export var max_total_mushrooms := 50
@export var interact_radius := 2.0
@export var look_at_threshold := 0.95
static var total_mushrooms := 0
@onready var spread_timer: Timer = $SpreadTimer
@onready var ground_ray: RayCast3D = $GroundRay
@onready var cap: MeshInstance3D = $Cap
@onready var stem: MeshInstance3D = $Stem
var _player_in_range := false
var _is_highlighted := false
var highlight_mat: StandardMaterial3D
var _audio_player: AudioStreamPlayer3D
func _ready() -> void:
total_mushrooms += 1
_setup_timer()
# Randomize initial scale for variety
scale = Vector3.ONE * randf_range(0.8, 1.2)
rotation.y = randf_range(0, TAU)
# Setup interaction area
var area = Area3D.new()
add_child(area)
var shape = CollisionShape3D.new()
var sphere = SphereShape3D.new()
sphere.radius = interact_radius
shape.shape = sphere
area.add_child(shape)
area.body_entered.connect(_on_body_entered)
area.body_exited.connect(_on_body_exited)
highlight_mat = StandardMaterial3D.new()
highlight_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
highlight_mat.albedo_color = Color(1.0, 1.0, 1.0, 0.3)
highlight_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
_audio_player = AudioStreamPlayer3D.new()
_audio_player.stream = preload("res://assets/audio/pop.ogg")
add_child(_audio_player)
func _process(_delta: float) -> void:
if not _player_in_range:
_set_highlight(false)
return
var cam = get_viewport().get_camera_3d()
if not cam:
_set_highlight(false)
return
var dir_to_mushroom = (global_position - cam.global_position).normalized()
var forward = -cam.global_transform.basis.z
var my_dot = forward.dot(dir_to_mushroom)
if my_dot > look_at_threshold:
# Check if we are the most directly looked-at mushroom in range
var is_best = true
for mushroom in get_tree().get_nodes_in_group("mushrooms"):
if mushroom == self or not mushroom.get("_player_in_range"):
continue
var m_dir = (mushroom.global_position - cam.global_position).normalized()
var m_dot = forward.dot(m_dir)
if m_dot > my_dot:
is_best = false
break
elif m_dot == my_dot and mushroom.get_instance_id() > get_instance_id():
# Tie breaker
is_best = false
break
_set_highlight(is_best)
else:
_set_highlight(false)
func _set_highlight(value: bool) -> void:
if _is_highlighted == value: return
_is_highlighted = value
if value:
if cap: cap.material_overlay = highlight_mat
if stem: stem.material_overlay = highlight_mat
else:
if cap: cap.material_overlay = null
if stem: stem.material_overlay = null
func _on_body_entered(body: Node3D) -> void:
if body.is_in_group("player"):
_player_in_range = true
func _on_body_exited(body: Node3D) -> void:
if body.is_in_group("player"):
_player_in_range = false
_set_highlight(false)
func _unhandled_input(event: InputEvent) -> void:
if _is_highlighted and event.is_action_pressed("interact"):
get_viewport().set_input_as_handled()
_collect_mushroom()
func _collect_mushroom() -> void:
# Disable interaction and visuals
_is_highlighted = false
_set_highlight(false)
_player_in_range = false
if cap: cap.visible = false
if stem: stem.visible = false
# Stop spreading logic
spread_timer.stop()
# Disable processing so it can't be interacted with again
set_process_unhandled_input(false)
set_process(false)
# Play sound
_audio_player.play()
# Wait for sound to finish, then free
await _audio_player.finished
queue_free()
func _setup_timer() -> void:
spread_timer.wait_time = randf_range(spread_interval_min, spread_interval_max)
spread_timer.start()
func _on_spread_timer_timeout() -> void:
if total_mushrooms >= max_total_mushrooms:
return
if _count_nearby_mushrooms() >= max_mushrooms_in_radius:
# Too crowded, try again later
_setup_timer()
return
_attempt_spread()
_setup_timer()
func _attempt_spread() -> void:
var random_angle := randf_range(0, TAU)
var random_dist := randf_range(spread_radius * 0.5, spread_radius)
var offset := Vector3(cos(random_angle) * random_dist, 5.0, sin(random_angle) * random_dist)
ground_ray.position = offset
ground_ray.force_raycast_update()
if ground_ray.is_colliding():
var collision_point := ground_ray.get_collision_point()
var collision_normal := ground_ray.get_collision_normal()
# Only spread on relatively flat surfaces
if collision_normal.dot(Vector3.UP) > 0.7:
_spawn_mushroom(collision_point)
func _spawn_mushroom(pos: Vector3) -> void:
var mushroom_scene = load(scene_file_path)
var new_mushroom = mushroom_scene.instantiate()
get_parent().add_child(new_mushroom)
new_mushroom.global_position = pos
# Visual feedback/animation
new_mushroom.scale = Vector3.ZERO
var tween = create_tween()
tween.tween_property(new_mushroom, "scale", Vector3.ONE * randf_range(0.8, 1.2), 1.0).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
func _count_nearby_mushrooms() -> int:
var count := 0
for mushroom in get_tree().get_nodes_in_group("mushrooms"):
if mushroom == self: continue
if global_position.distance_to(mushroom.global_position) < check_radius:
count += 1
return count
func _exit_tree() -> void:
total_mushrooms -= 1
+1
View File
@@ -0,0 +1 @@
uid://brtwt4rp650ao
+40
View File
@@ -0,0 +1,40 @@
[gd_scene load_steps=6 format=3]
[ext_resource type="Script" path="res://scenes/Interaction/mushroom.gd" id="1_m1s2h"]
[sub_resource type="CylinderMesh" id="CylinderMesh_stem"]
top_radius = 0.05
bottom_radius = 0.05
height = 0.15
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_stem"]
albedo_color = Color(0.9, 0.85, 0.7, 1)
[sub_resource type="SphereMesh" id="SphereMesh_cap"]
radius = 0.15
height = 0.1
is_hemisphere = true
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cap"]
albedo_color = Color(0.8, 0.2, 0.2, 1)
[node name="Mushroom" type="Node3D" groups=["mushrooms"]]
script = ExtResource("1_m1s2h")
[node name="Stem" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.075, 0)
mesh = SubResource("CylinderMesh_stem")
surface_material_override/0 = SubResource("StandardMaterial3D_stem")
[node name="Cap" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0)
mesh = SubResource("SphereMesh_cap")
surface_material_override/0 = SubResource("StandardMaterial3D_cap")
[node name="SpreadTimer" type="Timer" parent="."]
one_shot = true
[node name="GroundRay" type="RayCast3D" parent="."]
target_position = Vector3(0, -10, 0)
[connection signal="timeout" from="SpreadTimer" to="." method="_on_spread_timer_timeout"]
@@ -16,6 +16,21 @@ extends Node3D
one_shot = value one_shot = value
_sync_teleporter() _sync_teleporter()
@export var target_spawn_name: StringName = &"":
set(value):
target_spawn_name = value
_sync_teleporter()
@export var quest_event_name: StringName = &"":
set(value):
quest_event_name = value
_sync_teleporter()
@export var quest_id_filter: String = "":
set(value):
quest_id_filter = value
_sync_teleporter()
@onready var teleporter: Area3D = $Teleporter @onready var teleporter: Area3D = $Teleporter
@@ -29,3 +44,6 @@ func _sync_teleporter() -> void:
teleporter.set("target_scene_path", target_scene_path) teleporter.set("target_scene_path", target_scene_path)
teleporter.set("target_group", target_group) teleporter.set("target_group", target_group)
teleporter.set("one_shot", one_shot) teleporter.set("one_shot", one_shot)
teleporter.set("target_spawn_name", target_spawn_name)
teleporter.set("quest_event_name", quest_event_name)
teleporter.set("quest_id_filter", quest_id_filter)
@@ -3,6 +3,9 @@ extends Area3D
@export_file("*.tscn") var target_scene_path := "res://scenes/Levels/transportation_level.tscn" @export_file("*.tscn") var target_scene_path := "res://scenes/Levels/transportation_level.tscn"
@export var target_group: StringName = &"player" @export var target_group: StringName = &"player"
@export var one_shot := true @export var one_shot := true
@export var target_spawn_name: StringName = &""
@export var quest_event_name: StringName = &""
@export var quest_id_filter: String = ""
var _is_transitioning := false var _is_transitioning := false
@@ -22,6 +25,9 @@ func _on_body_entered(body: Node) -> void:
if not ResourceLoader.exists(target_scene_path): if not ResourceLoader.exists(target_scene_path):
push_warning("Teleporter target scene does not exist: %s" % target_scene_path) push_warning("Teleporter target scene does not exist: %s" % target_scene_path)
return return
_emit_quest_event(body)
if TeleportState != null:
TeleportState.request_spawn(target_spawn_name)
_is_transitioning = true _is_transitioning = true
if one_shot: if one_shot:
@@ -29,6 +35,17 @@ func _on_body_entered(body: Node) -> void:
call_deferred("_deferred_change_scene") call_deferred("_deferred_change_scene")
func _emit_quest_event(body: Node) -> void:
if quest_event_name == StringName() or QuestManager == null:
return
if quest_id_filter.strip_edges() != "" and QuestManager.get_active_quest_id() != StringName(quest_id_filter):
return
QuestManager.emit_event(quest_event_name, {
"body": body,
"source": self,
})
func _deferred_change_scene() -> void: func _deferred_change_scene() -> void:
var err := get_tree().change_scene_to_file(target_scene_path) var err := get_tree().change_scene_to_file(target_scene_path)
if err == OK: if err == OK:
+16
View File
@@ -0,0 +1,16 @@
extends Node
var _requested_spawn_name: StringName = &""
var camera_zoom_dist: float = 3.0
var third_person_camera_zoom_dist: float = 3.0
var camera_is_first_person: bool = false
func request_spawn(spawn_name: StringName) -> void:
_requested_spawn_name = spawn_name
func consume_spawn_name() -> StringName:
var spawn_name := _requested_spawn_name
_requested_spawn_name = &""
return spawn_name
@@ -0,0 +1 @@
uid://ds35gqf0obsxf
+43
View File
@@ -0,0 +1,43 @@
extends Control
@export var radius: float = 150.0
@export var transition_speed: float = 0.2
func _ready():
hide() # Start hidden
_arrange_buttons()
func _input(event):
if event.is_action_pressed("OpenRadialCommandMenu"): # Map 'Q' to "open_menu" in Input Map
_toggle_menu()
func _toggle_menu():
visible = !visible
if visible:
# Center the menu on the mouse or screen
global_position = get_viewport().get_mouse_position()
_animate_menu(true)
else:
_animate_menu(false)
func _arrange_buttons():
var buttons = get_children().filter(func(child): return child is Control)
var count = buttons.size()
if count == 0:
return
for i in range(count):
# Calculate the angle for each button in radians
var angle = i * (2 * PI / count)
# Standard circle math: x = cos(a) * r, y = sin(a) * r
var target_pos = Vector2(cos(angle), sin(angle)) * radius
# Offset by button size to keep them centered
buttons[i].position = target_pos - (buttons[i].size / 2)
func _animate_menu(opening: bool):
var tween = create_tween().set_parallel(true)
for button in get_children().filter(func(child): return child is Control):
var final_scale = Vector2.ONE if opening else Vector2.ZERO
tween.tween_property(button, "scale", final_scale, transition_speed).from(Vector2.ZERO if opening else Vector2.ONE)
@@ -0,0 +1 @@
uid://bv2ei5rg0dn3d

Some files were not shown because too many files have changed in this diff Show More