init
This commit is contained in:
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
./scavgame.x86_64 &
|
|
||||||
GODOT_PID=$!
|
|
||||||
|
|
||||||
wmctrl -r "scavgame" -b add,hidden
|
|
||||||
|
|
||||||
wait "$GODOT_PID"
|
|
||||||
-1243
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://bj1b5wm8lhd0v"
|
uid="uid://b35ewh20gulwl"
|
||||||
path="res://.godot/imported/scavgame.144x144.png-b48426c1c0d44c524725caf94371d8cf.ctex"
|
path="res://.godot/imported/scavgame.144x144.png-b48426c1c0d44c524725caf94371d8cf.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://c3ffnf40pfswc"
|
uid="uid://dxxnbj0y02str"
|
||||||
path="res://.godot/imported/scavgame.180x180.png-98da9fc07cc8cd6f3596b8214cb44abf.ctex"
|
path="res://.godot/imported/scavgame.180x180.png-98da9fc07cc8cd6f3596b8214cb44abf.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://4ele4xirpeuo"
|
uid="uid://cwtujh1x71cxy"
|
||||||
path="res://.godot/imported/scavgame.512x512.png-fc91a6f75062f7a09d36054b561598b5.ctex"
|
path="res://.godot/imported/scavgame.512x512.png-fc91a6f75062f7a09d36054b561598b5.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://c08c15fn25u5a"
|
|
||||||
path="res://.godot/imported/scavgame.apple-touch-icon.png-fae91e97aebbd193ef8488b9ce4380d1.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://Build/scavgame.apple-touch-icon.png"
|
|
||||||
dest_files=["res://.godot/imported/scavgame.apple-touch-icon.png-fae91e97aebbd193ef8488b9ce4380d1.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
|
|
||||||
Binary file not shown.
+1
-1
@@ -110,7 +110,7 @@ body {
|
|||||||
|
|
||||||
<script src="scavgame.js"></script>
|
<script src="scavgame.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"emscriptenPoolSize":8,"ensureCrossOriginIsolationHeaders":true,"executable":"scavgame","experimentalVK":false,"fileSizes":{"scavgame.pck":200656,"scavgame.wasm":1601069},"focusCanvas":true,"gdextensionLibs":[],"godotPoolSize":4,"serviceWorker":"scavgame.service.worker.js"};
|
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"emscriptenPoolSize":8,"ensureCrossOriginIsolationHeaders":true,"executable":"scavgame","experimentalVK":false,"fileSizes":{"scavgame.pck":232728,"scavgame.wasm":1601069},"focusCanvas":true,"gdextensionLibs":[],"godotPoolSize":4,"serviceWorker":"scavgame.service.worker.js"};
|
||||||
const GODOT_THREADS_ENABLED = true;
|
const GODOT_THREADS_ENABLED = true;
|
||||||
const engine = new Engine(GODOT_CONFIG);
|
const engine = new Engine(GODOT_CONFIG);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -1,40 +0,0 @@
|
|||||||
[remap]
|
|
||||||
|
|
||||||
importer="texture"
|
|
||||||
type="CompressedTexture2D"
|
|
||||||
uid="uid://dd0gt71ad26cw"
|
|
||||||
path="res://.godot/imported/scavgame.icon.png-73f49704b23c124153b229d5b0cda394.ctex"
|
|
||||||
metadata={
|
|
||||||
"vram_texture": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[deps]
|
|
||||||
|
|
||||||
source_file="res://Build/scavgame.icon.png"
|
|
||||||
dest_files=["res://.godot/imported/scavgame.icon.png-73f49704b23c124153b229d5b0cda394.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
|
|
||||||
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://d2j704bew40yv"
|
uid="uid://cgr0gin458n38"
|
||||||
path="res://.godot/imported/scavgame.png-7a036069753eca4aeee3fbdd0ed99f6b.ctex"
|
path="res://.godot/imported/scavgame.png-7a036069753eca4aeee3fbdd0ed99f6b.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// Incrementing CACHE_VERSION will kick off the install event and force
|
// Incrementing CACHE_VERSION will kick off the install event and force
|
||||||
// previously cached resources to be updated from the network.
|
// previously cached resources to be updated from the network.
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
const CACHE_VERSION = '1788537868|6220518696';
|
const CACHE_VERSION = '1788972329|18914709609';
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
const CACHE_PREFIX = 'scavgame-sw-cache-';
|
const CACHE_PREFIX = 'scavgame-sw-cache-';
|
||||||
const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
|
const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
|
||||||
|
|||||||
-906
@@ -1,906 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import uuid
|
|
||||||
from pathlib import Path
|
|
||||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
||||||
|
|
||||||
from websockets.asyncio.server import serve
|
|
||||||
from websockets.exceptions import ConnectionClosed
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Configuration
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
HTTP_HOST = "0.0.0.0"
|
|
||||||
HTTP_PORT = 8080
|
|
||||||
|
|
||||||
WS_HOST = "0.0.0.0"
|
|
||||||
WS_PORT = 8081
|
|
||||||
|
|
||||||
GAME_EXECUTABLE = Path(
|
|
||||||
"/data/1/Projects/Scavenger-2027/Build/scavgame.x86_64"
|
|
||||||
)
|
|
||||||
|
|
||||||
GAME_DIRECTORY = GAME_EXECUTABLE.parent
|
|
||||||
WEB_DIRECTORY = GAME_DIRECTORY
|
|
||||||
|
|
||||||
MAX_SESSIONS = 4
|
|
||||||
|
|
||||||
DEBUG_LOGGING = True
|
|
||||||
|
|
||||||
sessions: dict[str, "GameSession"] = {}
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Logging
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
def log(*args):
|
|
||||||
print("[Server]", *args, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
def debug(*args):
|
|
||||||
if DEBUG_LOGGING:
|
|
||||||
print("[Server][DEBUG]", *args, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
def warning(*args):
|
|
||||||
print("[Server][WARNING]", *args, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
def error(*args):
|
|
||||||
print("[Server][ERROR]", *args, flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Message helpers
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
def normalize_sdp_message(message: dict) -> dict | None:
|
|
||||||
"""
|
|
||||||
Normalizes these formats:
|
|
||||||
|
|
||||||
Browser:
|
|
||||||
{
|
|
||||||
"type": "offer",
|
|
||||||
"offer": {
|
|
||||||
"type": "offer",
|
|
||||||
"sdp": "..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Godot:
|
|
||||||
{
|
|
||||||
"type": "offer",
|
|
||||||
"sdp": "..."
|
|
||||||
}
|
|
||||||
|
|
||||||
The same applies to answers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
message_type = message.get("type")
|
|
||||||
|
|
||||||
if message_type not in {"offer", "answer"}:
|
|
||||||
return None
|
|
||||||
|
|
||||||
nested_key = message_type
|
|
||||||
|
|
||||||
nested = message.get(nested_key)
|
|
||||||
|
|
||||||
if isinstance(nested, dict):
|
|
||||||
sdp = nested.get("sdp", "")
|
|
||||||
|
|
||||||
if not sdp:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
|
||||||
"type": message_type,
|
|
||||||
"sdp": sdp
|
|
||||||
}
|
|
||||||
|
|
||||||
sdp = message.get("sdp", "")
|
|
||||||
|
|
||||||
if not sdp:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
|
||||||
"type": message_type,
|
|
||||||
"sdp": sdp
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_ice_message(message: dict) -> dict | None:
|
|
||||||
"""
|
|
||||||
Normalizes both:
|
|
||||||
|
|
||||||
{ "type": "ice", "candidate": {...} }
|
|
||||||
|
|
||||||
and:
|
|
||||||
|
|
||||||
{ "type": "candidate", "candidate": {...} }
|
|
||||||
"""
|
|
||||||
|
|
||||||
candidate = message.get("candidate")
|
|
||||||
|
|
||||||
if not isinstance(candidate, dict):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not candidate.get("candidate"):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
|
||||||
"type": "ice",
|
|
||||||
"candidate": candidate
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Game session
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
class GameSession:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
browser,
|
|
||||||
requested_session_id: str | None = None
|
|
||||||
):
|
|
||||||
self.id = (
|
|
||||||
requested_session_id
|
|
||||||
if requested_session_id
|
|
||||||
else uuid.uuid4().hex
|
|
||||||
)
|
|
||||||
|
|
||||||
self.browser = browser
|
|
||||||
self.godot = None
|
|
||||||
self.process = None
|
|
||||||
|
|
||||||
self.browser_connected = True
|
|
||||||
self.godot_connected = False
|
|
||||||
self.stopping = False
|
|
||||||
|
|
||||||
self.send_lock = asyncio.Lock()
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
|
||||||
# Game process
|
|
||||||
# --------------------------------------------------------
|
|
||||||
|
|
||||||
async def start(self):
|
|
||||||
if not GAME_EXECUTABLE.exists():
|
|
||||||
raise FileNotFoundError(
|
|
||||||
f"Game executable not found: {GAME_EXECUTABLE}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not os.access(GAME_EXECUTABLE, os.X_OK):
|
|
||||||
raise PermissionError(
|
|
||||||
f"Game is not executable: {GAME_EXECUTABLE}"
|
|
||||||
)
|
|
||||||
|
|
||||||
env = os.environ.copy()
|
|
||||||
|
|
||||||
env["SCAV_SESSION_ID"] = self.id
|
|
||||||
|
|
||||||
# Keep Forward+ rendering.
|
|
||||||
env["DISPLAY"] = ":1"
|
|
||||||
|
|
||||||
# Start minimized and avoid taking focus.
|
|
||||||
env["SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS"] = "1"
|
|
||||||
|
|
||||||
command = [
|
|
||||||
str(GAME_EXECUTABLE),
|
|
||||||
|
|
||||||
"--session-id",
|
|
||||||
self.id,
|
|
||||||
|
|
||||||
"--debug-logging",
|
|
||||||
|
|
||||||
"--resolution",
|
|
||||||
"1x1",
|
|
||||||
]
|
|
||||||
|
|
||||||
debug(
|
|
||||||
f"[{self.id}] Starting game command:",
|
|
||||||
command
|
|
||||||
)
|
|
||||||
|
|
||||||
self.process = await asyncio.create_subprocess_exec(
|
|
||||||
*command,
|
|
||||||
|
|
||||||
cwd=str(GAME_DIRECTORY),
|
|
||||||
env=env,
|
|
||||||
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.STDOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{self.id}] Started game "
|
|
||||||
f"PID={self.process.pid}"
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.create_task(
|
|
||||||
self.read_output()
|
|
||||||
)
|
|
||||||
|
|
||||||
async def read_output(self):
|
|
||||||
process = self.process
|
|
||||||
|
|
||||||
if process is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if process.stdout is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
async for line in process.stdout:
|
|
||||||
output = line.decode(
|
|
||||||
errors="replace"
|
|
||||||
).rstrip()
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"[{self.id}] {output}",
|
|
||||||
flush=True
|
|
||||||
)
|
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
error(
|
|
||||||
f"[{self.id}] Output reader error:",
|
|
||||||
exc
|
|
||||||
)
|
|
||||||
|
|
||||||
async def stop(self):
|
|
||||||
if self.stopping:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stopping = True
|
|
||||||
|
|
||||||
process = self.process
|
|
||||||
self.process = None
|
|
||||||
|
|
||||||
if process is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if process.returncode is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{self.id}] Stopping game "
|
|
||||||
f"PID={process.pid}"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
process.terminate()
|
|
||||||
|
|
||||||
await asyncio.wait_for(
|
|
||||||
process.wait(),
|
|
||||||
timeout=3
|
|
||||||
)
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{self.id}] Game exited "
|
|
||||||
f"code={process.returncode}"
|
|
||||||
)
|
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
warning(
|
|
||||||
f"[{self.id}] Game did not exit; "
|
|
||||||
f"killing PID={process.pid}"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
process.kill()
|
|
||||||
await process.wait()
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# --------------------------------------------------------
|
|
||||||
# WebSocket sending
|
|
||||||
# --------------------------------------------------------
|
|
||||||
|
|
||||||
async def send_json(
|
|
||||||
self,
|
|
||||||
websocket,
|
|
||||||
message: dict
|
|
||||||
) -> bool:
|
|
||||||
if websocket is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
payload = json.dumps(message)
|
|
||||||
|
|
||||||
async with self.send_lock:
|
|
||||||
await websocket.send(payload)
|
|
||||||
|
|
||||||
debug(
|
|
||||||
f"[{self.id}] Sent:",
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except ConnectionClosed:
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
error(
|
|
||||||
f"[{self.id}] Send error:",
|
|
||||||
exc
|
|
||||||
)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def send_to_godot(
|
|
||||||
self,
|
|
||||||
message: dict
|
|
||||||
) -> bool:
|
|
||||||
if self.godot is None:
|
|
||||||
warning(
|
|
||||||
f"[{self.id}] Cannot send to Godot; "
|
|
||||||
f"Godot is not connected"
|
|
||||||
)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
success = await self.send_json(
|
|
||||||
self.godot,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
self.godot = None
|
|
||||||
self.godot_connected = False
|
|
||||||
|
|
||||||
return success
|
|
||||||
|
|
||||||
async def send_to_browser(
|
|
||||||
self,
|
|
||||||
message: dict
|
|
||||||
) -> bool:
|
|
||||||
if self.browser is None:
|
|
||||||
warning(
|
|
||||||
f"[{self.id}] Cannot send to browser; "
|
|
||||||
f"browser is not connected"
|
|
||||||
)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
success = await self.send_json(
|
|
||||||
self.browser,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
self.browser = None
|
|
||||||
self.browser_connected = False
|
|
||||||
|
|
||||||
return success
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# HTTP server
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
class HTTPHandler(SimpleHTTPRequestHandler):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(
|
|
||||||
*args,
|
|
||||||
directory=str(WEB_DIRECTORY),
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def log_message(self, format_string, *args):
|
|
||||||
print(
|
|
||||||
"[HTTP] " + format_string % args,
|
|
||||||
flush=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def start_http_server():
|
|
||||||
server = ThreadingHTTPServer(
|
|
||||||
(HTTP_HOST, HTTP_PORT),
|
|
||||||
HTTPHandler
|
|
||||||
)
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"HTTP server listening on "
|
|
||||||
f"http://{HTTP_HOST}:{HTTP_PORT}"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
server.serve_forever()
|
|
||||||
finally:
|
|
||||||
server.server_close()
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Browser connection
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
async def handle_browser(
|
|
||||||
websocket,
|
|
||||||
requested_session_id: str | None = None
|
|
||||||
):
|
|
||||||
if len(sessions) >= MAX_SESSIONS:
|
|
||||||
await websocket.send(json.dumps({
|
|
||||||
"type": "error",
|
|
||||||
"message": "Server is full"
|
|
||||||
}))
|
|
||||||
|
|
||||||
await websocket.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
session = GameSession(
|
|
||||||
websocket,
|
|
||||||
requested_session_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if session.id in sessions:
|
|
||||||
await websocket.send(json.dumps({
|
|
||||||
"type": "error",
|
|
||||||
"message": "Session ID is already in use"
|
|
||||||
}))
|
|
||||||
|
|
||||||
await websocket.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
sessions[session.id] = session
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Browser connected"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await session.send_to_browser({
|
|
||||||
"type": "session_created",
|
|
||||||
"session_id": session.id
|
|
||||||
})
|
|
||||||
|
|
||||||
await session.start()
|
|
||||||
|
|
||||||
# Wait for Godot to connect.
|
|
||||||
for _ in range(100):
|
|
||||||
if session.godot is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
|
|
||||||
if session.godot is None:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Godot did not connect"
|
|
||||||
)
|
|
||||||
|
|
||||||
await session.send_to_browser({
|
|
||||||
"type": "error",
|
|
||||||
"message": "Godot did not connect"
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
await session.send_to_godot({
|
|
||||||
"type": "browser_connected"
|
|
||||||
})
|
|
||||||
|
|
||||||
await session.send_to_browser({
|
|
||||||
"type": "godot_connected"
|
|
||||||
})
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Browser and Godot paired"
|
|
||||||
)
|
|
||||||
|
|
||||||
async for raw in websocket:
|
|
||||||
try:
|
|
||||||
message = json.loads(raw)
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Invalid browser JSON:",
|
|
||||||
raw
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not isinstance(message, dict):
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Browser message "
|
|
||||||
f"is not an object"
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
debug(
|
|
||||||
f"[{session.id}] Browser message:",
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
message_type = message.get("type")
|
|
||||||
|
|
||||||
# ------------------------------------------------
|
|
||||||
# WebRTC offer
|
|
||||||
# ------------------------------------------------
|
|
||||||
|
|
||||||
if message_type == "offer":
|
|
||||||
normalized = normalize_sdp_message(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if normalized is None:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Invalid browser offer"
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
await session.send_to_godot(
|
|
||||||
normalized
|
|
||||||
)
|
|
||||||
|
|
||||||
# ------------------------------------------------
|
|
||||||
# WebRTC ICE
|
|
||||||
# ------------------------------------------------
|
|
||||||
|
|
||||||
elif message_type in {
|
|
||||||
"ice",
|
|
||||||
"candidate"
|
|
||||||
}:
|
|
||||||
normalized = normalize_ice_message(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if normalized is None:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Invalid browser ICE"
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
await session.send_to_godot(
|
|
||||||
normalized
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
debug(
|
|
||||||
f"[{session.id}] Ignoring browser message:",
|
|
||||||
message_type
|
|
||||||
)
|
|
||||||
|
|
||||||
except ConnectionClosed:
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Browser WebSocket closed"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
error(
|
|
||||||
f"[{session.id}] Browser error:",
|
|
||||||
exc
|
|
||||||
)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Browser disconnected"
|
|
||||||
)
|
|
||||||
|
|
||||||
session.browser_connected = False
|
|
||||||
|
|
||||||
if session.godot is not None:
|
|
||||||
await session.send_to_godot({
|
|
||||||
"type": "browser_disconnected"
|
|
||||||
})
|
|
||||||
|
|
||||||
await asyncio.sleep(0.2)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await session.godot.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
session.godot = None
|
|
||||||
session.godot_connected = False
|
|
||||||
|
|
||||||
await session.stop()
|
|
||||||
|
|
||||||
sessions.pop(
|
|
||||||
session.id,
|
|
||||||
None
|
|
||||||
)
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Session removed"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Godot connection
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
async def handle_godot(
|
|
||||||
websocket,
|
|
||||||
session_id: str
|
|
||||||
):
|
|
||||||
session = sessions.get(session_id)
|
|
||||||
|
|
||||||
if session is None:
|
|
||||||
warning(
|
|
||||||
f"[{session_id}] Godot attempted to connect "
|
|
||||||
f"to unknown session"
|
|
||||||
)
|
|
||||||
|
|
||||||
await websocket.send(json.dumps({
|
|
||||||
"type": "error",
|
|
||||||
"message": "Unknown session"
|
|
||||||
}))
|
|
||||||
|
|
||||||
await websocket.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
if session.godot is not None:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Replacing existing Godot connection"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await session.godot.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
session.godot = websocket
|
|
||||||
session.godot_connected = True
|
|
||||||
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Godot connected"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await session.send_to_godot({
|
|
||||||
"type": "registered",
|
|
||||||
"session_id": session.id
|
|
||||||
})
|
|
||||||
|
|
||||||
if session.browser_connected:
|
|
||||||
await session.send_to_godot({
|
|
||||||
"type": "browser_connected"
|
|
||||||
})
|
|
||||||
|
|
||||||
await session.send_to_browser({
|
|
||||||
"type": "godot_connected"
|
|
||||||
})
|
|
||||||
|
|
||||||
async for raw in websocket:
|
|
||||||
try:
|
|
||||||
message = json.loads(raw)
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Invalid Godot JSON:",
|
|
||||||
raw
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not isinstance(message, dict):
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Godot message "
|
|
||||||
f"is not an object"
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
debug(
|
|
||||||
f"[{session.id}] Godot message:",
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
message_type = message.get("type")
|
|
||||||
|
|
||||||
# ------------------------------------------------
|
|
||||||
# WebRTC answer
|
|
||||||
# ------------------------------------------------
|
|
||||||
|
|
||||||
if message_type == "answer":
|
|
||||||
normalized = normalize_sdp_message(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if normalized is None:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Invalid Godot answer"
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
await session.send_to_browser({
|
|
||||||
"type": "answer",
|
|
||||||
"answer": {
|
|
||||||
"type": "answer",
|
|
||||||
"sdp": normalized["sdp"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# ------------------------------------------------
|
|
||||||
# WebRTC ICE
|
|
||||||
# ------------------------------------------------
|
|
||||||
|
|
||||||
elif message_type in {
|
|
||||||
"ice",
|
|
||||||
"candidate"
|
|
||||||
}:
|
|
||||||
normalized = normalize_ice_message(
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if normalized is None:
|
|
||||||
warning(
|
|
||||||
f"[{session.id}] Invalid Godot ICE"
|
|
||||||
)
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
await session.send_to_browser(
|
|
||||||
normalized
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
debug(
|
|
||||||
f"[{session.id}] Ignoring Godot message:",
|
|
||||||
message_type
|
|
||||||
)
|
|
||||||
|
|
||||||
except ConnectionClosed:
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Godot WebSocket closed"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
error(
|
|
||||||
f"[{session.id}] Godot error:",
|
|
||||||
exc
|
|
||||||
)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
log(
|
|
||||||
f"[{session.id}] Godot disconnected"
|
|
||||||
)
|
|
||||||
|
|
||||||
if session.godot is websocket:
|
|
||||||
session.godot = None
|
|
||||||
session.godot_connected = False
|
|
||||||
|
|
||||||
if session.browser is not None:
|
|
||||||
await session.send_to_browser({
|
|
||||||
"type": "godot_disconnected"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# WebSocket routing
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
async def websocket_handler(websocket):
|
|
||||||
try:
|
|
||||||
raw = await websocket.recv()
|
|
||||||
|
|
||||||
message = json.loads(raw)
|
|
||||||
|
|
||||||
if not isinstance(message, dict):
|
|
||||||
await websocket.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
role = message.get("role")
|
|
||||||
|
|
||||||
debug(
|
|
||||||
"Initial WebSocket registration:",
|
|
||||||
message
|
|
||||||
)
|
|
||||||
|
|
||||||
if role == "browser":
|
|
||||||
requested_session_id = message.get(
|
|
||||||
"session_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
await handle_browser(
|
|
||||||
websocket,
|
|
||||||
requested_session_id
|
|
||||||
)
|
|
||||||
|
|
||||||
elif role == "godot":
|
|
||||||
session_id = message.get(
|
|
||||||
"session_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not session_id:
|
|
||||||
await websocket.send(json.dumps({
|
|
||||||
"type": "error",
|
|
||||||
"message": "Missing session_id"
|
|
||||||
}))
|
|
||||||
|
|
||||||
await websocket.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
await handle_godot(
|
|
||||||
websocket,
|
|
||||||
session_id
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
await websocket.send(json.dumps({
|
|
||||||
"type": "error",
|
|
||||||
"message": "Invalid role"
|
|
||||||
}))
|
|
||||||
|
|
||||||
await websocket.close()
|
|
||||||
|
|
||||||
except ConnectionClosed:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
warning(
|
|
||||||
"Received invalid initial WebSocket JSON"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await websocket.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
error(
|
|
||||||
"WebSocket handler error:",
|
|
||||||
exc
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# WebSocket server
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
async def websocket_server():
|
|
||||||
log(
|
|
||||||
f"WebSocket server listening on "
|
|
||||||
f"ws://{WS_HOST}:{WS_PORT}"
|
|
||||||
)
|
|
||||||
|
|
||||||
async with serve(
|
|
||||||
websocket_handler,
|
|
||||||
WS_HOST,
|
|
||||||
WS_PORT
|
|
||||||
):
|
|
||||||
await asyncio.Future()
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Main
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
http_task = asyncio.to_thread(
|
|
||||||
start_http_server
|
|
||||||
)
|
|
||||||
|
|
||||||
websocket_task = asyncio.create_task(
|
|
||||||
websocket_server()
|
|
||||||
)
|
|
||||||
|
|
||||||
await asyncio.gather(
|
|
||||||
http_task,
|
|
||||||
websocket_task
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
asyncio.run(main())
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(
|
|
||||||
"\nServer stopped",
|
|
||||||
flush=True
|
|
||||||
)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
4.6.2.stable
|
|
||||||
+128
-64
@@ -1,97 +1,161 @@
|
|||||||
extends CharacterBody3D
|
extends CharacterBody3D
|
||||||
|
|
||||||
const SPEED := 5.0
|
# Acceleration and deceleration are m/s².
|
||||||
const JUMP_VELOCITY := 4.5
|
# max_speed is m/s.
|
||||||
const MOUSE_SENSITIVITY := 0.003
|
@export var accel := 6.7
|
||||||
|
@export var deccel := 8.5
|
||||||
|
@export var max_speed := 6.2
|
||||||
|
|
||||||
const ACCEL := 20.0
|
@export var has_suit := true
|
||||||
const DECEL := 15.0
|
@export var has_hvms2 := true
|
||||||
|
|
||||||
|
@export var gravity := 9.8
|
||||||
|
@export var jump_velocity := 4.5
|
||||||
|
|
||||||
|
# Mouse sensitivity
|
||||||
|
@export var mouse_sensitivity := 0.003
|
||||||
|
|
||||||
|
# Maximum camera pitch in degrees
|
||||||
|
@export var max_pitch := 89.0
|
||||||
|
|
||||||
|
var movement_vec := Vector3.ZERO
|
||||||
|
var input_vec := Vector4.ZERO
|
||||||
|
var jump := false
|
||||||
|
var air_jumps := 0
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
if not has_suit:
|
||||||
|
$yaw/pitch/Camera/Hud.visible = false
|
||||||
|
|
||||||
|
if has_hvms2:
|
||||||
|
max_speed = 30.0
|
||||||
|
accel = 10
|
||||||
|
deccel = 20
|
||||||
|
jump_velocity = 10
|
||||||
|
|
||||||
|
# Capture the mouse when the game starts
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
|
# Mouse-look
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||||
|
# Horizontal rotation: rotate the player
|
||||||
|
$yaw.rotate_y(-event.relative.x * mouse_sensitivity)
|
||||||
|
|
||||||
|
# Vertical rotation: rotate the camera
|
||||||
|
$yaw/pitch.rotate_x(-event.relative.y * mouse_sensitivity)
|
||||||
|
|
||||||
|
# Clamp pitch so the camera cannot flip upside down
|
||||||
|
$yaw/pitch.rotation.x = clamp(
|
||||||
|
$yaw/pitch.rotation.x,
|
||||||
|
deg_to_rad(-max_pitch),
|
||||||
|
deg_to_rad(max_pitch)
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Mouse click captures the mouse again
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Keyboard input
|
||||||
if event is InputEventKey:
|
if event is InputEventKey:
|
||||||
if event.keycode == KEY_ESCAPE and event.pressed:
|
var pressed: bool = event.pressed
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
||||||
return
|
|
||||||
|
|
||||||
if event.keycode == KEY_SPACE and event.pressed and is_on_floor():
|
match event.keycode:
|
||||||
velocity.y = JUMP_VELOCITY
|
KEY_W:
|
||||||
|
input_vec.w = 1.0 if pressed else 0.0
|
||||||
|
|
||||||
if event.keycode == KEY_E and event.pressed and not event.echo:
|
KEY_S:
|
||||||
var node = $yaw/pitch/Facing.get_collider()
|
input_vec.x = 1.0 if pressed else 0.0
|
||||||
|
|
||||||
if node and node.has_method("interact"):
|
KEY_A:
|
||||||
node.interact(self)
|
input_vec.y = 1.0 if pressed else 0.0
|
||||||
|
|
||||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
KEY_D:
|
||||||
$yaw.rotate_y(-event.relative.x * MOUSE_SENSITIVITY)
|
input_vec.z = 1.0 if pressed else 0.0
|
||||||
|
|
||||||
$yaw/pitch.rotate_x(-event.relative.y * MOUSE_SENSITIVITY)
|
KEY_SPACE:
|
||||||
|
if pressed and is_on_floor():
|
||||||
|
jump=true
|
||||||
|
|
||||||
|
KEY_ESCAPE:
|
||||||
|
if pressed:
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
|
||||||
|
KEY_SHIFT:
|
||||||
|
if has_hvms2:
|
||||||
|
velocity.y = max(velocity.y, -3) if pressed else velocity.y
|
||||||
|
gravity = 3 if pressed else 9.8
|
||||||
|
if is_on_floor():
|
||||||
|
velocity=$yaw/pitch/Camera.basis*Vector3(0,0,50)
|
||||||
|
|
||||||
$yaw/pitch.rotation.x = clamp(
|
|
||||||
$yaw/pitch.rotation.x,
|
|
||||||
deg_to_rad(-89.0),
|
|
||||||
deg_to_rad(89.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
# Gravity
|
# W/S/A/D input
|
||||||
if not is_on_floor():
|
var input_direction := Vector2(
|
||||||
velocity += get_gravity() * delta
|
input_vec.z - input_vec.y,
|
||||||
|
input_vec.w - input_vec.x
|
||||||
|
)
|
||||||
|
|
||||||
# Read keyboard state directly
|
if input_direction.length() > 1.0:
|
||||||
var input_x := 0.0
|
|
||||||
var input_z := 0.0
|
|
||||||
|
|
||||||
if Input.is_key_pressed(KEY_A):
|
|
||||||
input_x -= 1.0
|
|
||||||
|
|
||||||
if Input.is_key_pressed(KEY_D):
|
|
||||||
input_x += 1.0
|
|
||||||
|
|
||||||
if Input.is_key_pressed(KEY_W):
|
|
||||||
input_z -= 1.0
|
|
||||||
|
|
||||||
if Input.is_key_pressed(KEY_S):
|
|
||||||
input_z += 1.0
|
|
||||||
|
|
||||||
# Create movement vector
|
|
||||||
var input_direction := Vector3(input_x, 0.0, input_z)
|
|
||||||
|
|
||||||
# Prevent diagonal movement from being faster
|
|
||||||
if input_direction.length_squared() > 0.0:
|
|
||||||
input_direction = input_direction.normalized()
|
input_direction = input_direction.normalized()
|
||||||
|
|
||||||
# Rotate movement relative to yaw
|
# Camera yaw only
|
||||||
var direction : Vector3 = $yaw.global_transform.basis * input_direction
|
var yaw_basis: Basis = $yaw.global_transform.basis
|
||||||
direction.y = 0.0
|
|
||||||
|
|
||||||
if direction.length_squared() > 0.0:
|
var forward := -yaw_basis.z
|
||||||
direction = direction.normalized()
|
var right := yaw_basis.x
|
||||||
|
|
||||||
# Target velocity
|
forward.y = 0.0
|
||||||
var target := direction * SPEED
|
right.y = 0.0
|
||||||
|
|
||||||
var acceleration := ACCEL
|
forward = forward.normalized()
|
||||||
if direction.length_squared() == 0.0:
|
right = right.normalized()
|
||||||
acceleration = DECEL
|
|
||||||
|
|
||||||
# Smooth movement toward target
|
var target_direction := (
|
||||||
velocity.x = move_toward(
|
right * input_direction.x
|
||||||
|
+ forward * input_direction.y
|
||||||
|
)
|
||||||
|
|
||||||
|
# Target horizontal velocity in m/s
|
||||||
|
var target_velocity := target_direction * max_speed
|
||||||
|
|
||||||
|
# Current horizontal velocity in m/s
|
||||||
|
var current_velocity := Vector3(
|
||||||
velocity.x,
|
velocity.x,
|
||||||
target.x,
|
0.0,
|
||||||
|
velocity.z
|
||||||
|
)
|
||||||
|
|
||||||
|
# Acceleration/deceleration in m/s²
|
||||||
|
var acceleration := accel if target_direction != Vector3.ZERO else deccel
|
||||||
|
|
||||||
|
current_velocity = current_velocity.move_toward(
|
||||||
|
target_velocity,
|
||||||
acceleration * delta
|
acceleration * delta
|
||||||
)
|
)
|
||||||
|
|
||||||
velocity.z = move_toward(
|
velocity.x = current_velocity.x
|
||||||
velocity.z,
|
velocity.z = current_velocity.z
|
||||||
target.z,
|
|
||||||
acceleration * delta
|
# Gravity
|
||||||
)
|
if not is_on_floor():
|
||||||
|
velocity.y -= gravity * delta
|
||||||
|
else:
|
||||||
|
if jump:
|
||||||
|
velocity.y=jump_velocity
|
||||||
|
jump=false
|
||||||
|
else:
|
||||||
|
velocity.y=0.0
|
||||||
|
|
||||||
|
if global_position.y<=-1000:
|
||||||
|
get_tree().reload_current_scene()
|
||||||
|
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
$Container.size=get_viewport_rect().size
|
||||||
|
|
||||||
func set_tooltip(text: String) -> void:
|
func set_tooltip(text: String) -> void:
|
||||||
$Tooltip.text=text
|
$Container/Tooltip.text=text
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,72 @@
|
|||||||
|
extends MeshInstance3D
|
||||||
|
|
||||||
|
@export var max_lights := 256
|
||||||
|
@export var camera: Camera3D
|
||||||
|
|
||||||
|
var lights: Array[Light3D] = []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
find_lights(get_tree().current_scene)
|
||||||
|
|
||||||
|
func find_lights(node: Node) -> void:
|
||||||
|
if node is Light3D:
|
||||||
|
lights.append(node)
|
||||||
|
|
||||||
|
for child in node.get_children():
|
||||||
|
find_lights(child)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var mat := material_override as ShaderMaterial
|
||||||
|
if mat == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
if camera == null:
|
||||||
|
camera = get_viewport().get_camera_3d()
|
||||||
|
|
||||||
|
if camera == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
mat.set_shader_parameter(
|
||||||
|
"inv_projection",
|
||||||
|
camera.get_camera_projection().inverse()
|
||||||
|
)
|
||||||
|
|
||||||
|
mat.set_shader_parameter(
|
||||||
|
"inv_view",
|
||||||
|
camera.global_transform
|
||||||
|
)
|
||||||
|
|
||||||
|
if mat == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var positions := PackedVector3Array()
|
||||||
|
var colors := PackedColorArray()
|
||||||
|
var energies := PackedFloat32Array()
|
||||||
|
var ranges := PackedFloat32Array()
|
||||||
|
|
||||||
|
for light in lights:
|
||||||
|
if not is_instance_valid(light):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not light.visible:
|
||||||
|
continue
|
||||||
|
|
||||||
|
positions.append(light.global_position)
|
||||||
|
colors.append(light.light_color)
|
||||||
|
energies.append(light.light_energy)
|
||||||
|
|
||||||
|
if light is OmniLight3D:
|
||||||
|
ranges.append(light.omni_range)
|
||||||
|
elif light is SpotLight3D:
|
||||||
|
ranges.append(light.spot_range)
|
||||||
|
else:
|
||||||
|
ranges.append(100.0)
|
||||||
|
|
||||||
|
if positions.size() >= max_lights:
|
||||||
|
break
|
||||||
|
|
||||||
|
mat.set_shader_parameter("light_positions", positions)
|
||||||
|
mat.set_shader_parameter("light_colors", colors)
|
||||||
|
mat.set_shader_parameter("light_energies", energies)
|
||||||
|
mat.set_shader_parameter("light_ranges", ranges)
|
||||||
|
mat.set_shader_parameter("light_count", positions.size())
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://blvu4cfl61262
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
shader_type spatial;
|
||||||
|
|
||||||
|
render_mode unshaded, fog_disabled, depth_draw_never, cull_disabled, blend_mix;
|
||||||
|
|
||||||
|
const int MAX_LIGHTS = 256;
|
||||||
|
|
||||||
|
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
|
||||||
|
uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;
|
||||||
|
|
||||||
|
// Camera matrices — set from GDScript.
|
||||||
|
uniform mat4 inv_projection;
|
||||||
|
uniform mat4 inv_view;
|
||||||
|
|
||||||
|
// Fog
|
||||||
|
uniform vec3 fog_color : source_color = vec3(0.55, 0.65, 0.75);
|
||||||
|
uniform float fog_density : hint_range(0.0, 10.0) = 0.5;
|
||||||
|
uniform float fog_height_falloff : hint_range(0.0, 10.0) = 0.15;
|
||||||
|
|
||||||
|
// Light scattering
|
||||||
|
uniform int light_count = 0;
|
||||||
|
uniform vec3 light_positions[MAX_LIGHTS];
|
||||||
|
uniform vec4 light_colors[MAX_LIGHTS];
|
||||||
|
uniform float light_energies[MAX_LIGHTS];
|
||||||
|
uniform float light_ranges[MAX_LIGHTS];
|
||||||
|
|
||||||
|
uniform float scattering : hint_range(0.0, 2.0) = 0.5;
|
||||||
|
uniform float scattering_power : hint_range(0.1, 32.0) = 8.0;
|
||||||
|
|
||||||
|
uniform int steps : hint_range(8, 64) = 32;
|
||||||
|
|
||||||
|
vec3 reconstruct_view_position(vec2 uv, float depth) {
|
||||||
|
vec3 ndc = vec3(uv * 2.0 - 1.0, depth);
|
||||||
|
|
||||||
|
vec4 view_pos = inv_projection * vec4(ndc, 1.0);
|
||||||
|
view_pos.xyz /= view_pos.w;
|
||||||
|
|
||||||
|
return view_pos.xyz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec2 screen_uv = SCREEN_UV;
|
||||||
|
|
||||||
|
vec4 scene = texture(screen_texture, screen_uv);
|
||||||
|
float depth = texture(depth_texture, screen_uv).r;
|
||||||
|
|
||||||
|
vec3 ray_end_view =
|
||||||
|
reconstruct_view_position(screen_uv, depth);
|
||||||
|
|
||||||
|
vec3 camera_position =
|
||||||
|
(inv_view * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
||||||
|
|
||||||
|
vec3 ray_end =
|
||||||
|
(inv_view * vec4(ray_end_view, 1.0)).xyz;
|
||||||
|
|
||||||
|
vec3 ray_vector = ray_end - camera_position;
|
||||||
|
float ray_length = length(ray_vector);
|
||||||
|
|
||||||
|
vec3 ray_dir = normalize(ray_vector);
|
||||||
|
|
||||||
|
float step_length = ray_length / float(steps);
|
||||||
|
|
||||||
|
vec3 accumulated = vec3(0.0);
|
||||||
|
float transmittance = 1.0;
|
||||||
|
|
||||||
|
for (int i = 0; i < steps; i++) {
|
||||||
|
float t = (float(i) + 0.5) / float(steps);
|
||||||
|
|
||||||
|
vec3 world_position =
|
||||||
|
camera_position +
|
||||||
|
ray_dir * (ray_length * t);
|
||||||
|
|
||||||
|
float height_density =
|
||||||
|
exp(-world_position.y * fog_height_falloff);
|
||||||
|
|
||||||
|
float density =
|
||||||
|
fog_density * height_density;
|
||||||
|
|
||||||
|
vec3 light_scattering = vec3(0.0);
|
||||||
|
|
||||||
|
for (int l = 0; l < MAX_LIGHTS; l++) {
|
||||||
|
if (l >= light_count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
vec3 to_light =
|
||||||
|
light_positions[l] - world_position;
|
||||||
|
|
||||||
|
float light_distance = length(to_light);
|
||||||
|
|
||||||
|
if (light_distance <= 0.001)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 light_dir =
|
||||||
|
to_light / light_distance;
|
||||||
|
|
||||||
|
float attenuation =
|
||||||
|
1.0 - smoothstep(
|
||||||
|
0.0,
|
||||||
|
light_ranges[l],
|
||||||
|
light_distance
|
||||||
|
);
|
||||||
|
|
||||||
|
attenuation /=
|
||||||
|
1.0 + light_distance * light_distance * 0.01;
|
||||||
|
|
||||||
|
float phase =
|
||||||
|
pow(
|
||||||
|
max(dot(-ray_dir, light_dir), 0.0),
|
||||||
|
scattering_power
|
||||||
|
);
|
||||||
|
|
||||||
|
light_scattering +=
|
||||||
|
light_colors[l].rgb *
|
||||||
|
light_energies[l] *
|
||||||
|
attenuation *
|
||||||
|
phase *
|
||||||
|
scattering;
|
||||||
|
}
|
||||||
|
|
||||||
|
float absorption =
|
||||||
|
exp(-density * step_length * 10.0);
|
||||||
|
|
||||||
|
accumulated +=
|
||||||
|
(fog_color + light_scattering) *
|
||||||
|
(1.0 - absorption) *
|
||||||
|
transmittance;
|
||||||
|
|
||||||
|
transmittance *= absorption;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALBEDO =
|
||||||
|
scene.rgb * transmittance +
|
||||||
|
accumulated;
|
||||||
|
|
||||||
|
ALPHA = 1.0;
|
||||||
|
}
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
shader_type spatial;
|
|
||||||
render_mode blend_add, cull_disabled, unshaded;
|
|
||||||
|
|
||||||
uniform sampler2D dust : source_color;
|
|
||||||
uniform float dust_speed = 0.03;
|
|
||||||
uniform vec4 color : source_color;
|
|
||||||
uniform float intensity = 2.75;
|
|
||||||
uniform float softness = 3.0;
|
|
||||||
|
|
||||||
// Godot 4 explicit texture hints
|
|
||||||
uniform sampler2D depth_texture : hint_depth_texture, filter_linear_mipmap;
|
|
||||||
uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
|
|
||||||
|
|
||||||
uniform float proximity_fade_distance = 0.5;
|
|
||||||
uniform float distance_fade_min = 1.0;
|
|
||||||
uniform float distance_fade_max = 2.0;
|
|
||||||
|
|
||||||
varying vec3 obj_vertex;
|
|
||||||
|
|
||||||
void vertex() {
|
|
||||||
obj_vertex = VERTEX;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fragment() {
|
|
||||||
// Dust texture sampling using screen coordinates
|
|
||||||
vec2 dust_uv = vec2(SCREEN_UV.x + dust_speed * TIME, SCREEN_UV.y);
|
|
||||||
vec4 dust_overlay = texture(dust, dust_uv);
|
|
||||||
|
|
||||||
float attenuation = clamp(1.0 + obj_vertex.y / intensity, 0.0, 1.0);
|
|
||||||
float soft_edge = clamp(pow(dot(NORMAL, normalize(VERTEX)), softness), 0.0, 1.0);
|
|
||||||
|
|
||||||
ALBEDO = dust_overlay.rgb * color.rgb * attenuation * soft_edge;
|
|
||||||
|
|
||||||
// Proximity Fade calculation in Godot 4
|
|
||||||
float depth_raw = texture(depth_texture, SCREEN_UV).r;
|
|
||||||
vec4 ndc = vec4(SCREEN_UV * 2.0 - 1.0, depth_raw, 1.0);
|
|
||||||
vec4 view_pos = INV_PROJECTION_MATRIX * ndc;
|
|
||||||
view_pos.xyz /= view_pos.w;
|
|
||||||
|
|
||||||
float depth_diff = -VERTEX.z - (-view_pos.z);
|
|
||||||
float proximity_alpha = clamp(depth_diff / proximity_fade_distance, 0.0, 1.0);
|
|
||||||
|
|
||||||
// Distance Fade calculation
|
|
||||||
float dist_alpha = clamp(smoothstep(distance_fade_min, distance_fade_max, -VERTEX.z), 0.0, 1.0);
|
|
||||||
|
|
||||||
ALPHA = proximity_alpha * dist_alpha;
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,805 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const CONSOLE_THEME : String = &"console/theme"
|
||||||
|
const CONSOLE_SCALE : String = &"console/scale"
|
||||||
|
const CONSOLE_HEIGHT : String = &"console/height"
|
||||||
|
const CONSOLE_COLOR_WARNING : String = &"console/color_warning"
|
||||||
|
const CONSOLE_COLOR_ERROR : String = &"console/color_error"
|
||||||
|
const CONSOLE_COLOR_INFO : String = &"console/color_info"
|
||||||
|
const CONSOLE_COLOR_LITERAL : String = &"console/color_literal"
|
||||||
|
const CONSOLE_TABSTOP : String = &"console/tabstop"
|
||||||
|
|
||||||
|
const color_dictionary : Dictionary[String, Color] = {
|
||||||
|
CONSOLE_COLOR_ERROR: Color.LIGHT_CORAL,
|
||||||
|
CONSOLE_COLOR_INFO: Color.LIGHT_BLUE,
|
||||||
|
CONSOLE_COLOR_LITERAL: Color.PALE_GREEN,
|
||||||
|
CONSOLE_COLOR_WARNING: Color.LIGHT_GOLDENROD
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabled : bool = true
|
||||||
|
var enable_on_release_build : bool = false : set = set_enable_on_release_build
|
||||||
|
var pause_enabled : bool = false
|
||||||
|
var font_size : int : set = _set_font_size
|
||||||
|
|
||||||
|
## What visual scale should the console be
|
||||||
|
var console_scale : float : set = set_console_scale
|
||||||
|
|
||||||
|
## Is the console in full screen or part screen mode
|
||||||
|
var console_full_screen : bool = false
|
||||||
|
|
||||||
|
var _conosle_tween_time : float = 5
|
||||||
|
|
||||||
|
signal console_opened
|
||||||
|
signal console_closed
|
||||||
|
signal console_unknown_command
|
||||||
|
signal console_cvar_changed(cvar_name : String, value : Variant)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleCommand:
|
||||||
|
var function : Callable
|
||||||
|
var arguments : PackedStringArray
|
||||||
|
var required : int
|
||||||
|
var description : String
|
||||||
|
var hidden : bool
|
||||||
|
func _init(in_function : Callable, in_arguments : PackedStringArray, in_required : int = 0, in_description : String = ""):
|
||||||
|
function = in_function
|
||||||
|
arguments = in_arguments
|
||||||
|
required = in_required
|
||||||
|
description = in_description
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleCvar:
|
||||||
|
var name : String
|
||||||
|
var description : String
|
||||||
|
var type : int
|
||||||
|
var save : bool
|
||||||
|
var value : Variant
|
||||||
|
var object : Object
|
||||||
|
var property : String
|
||||||
|
|
||||||
|
func _init(in_name : String, in_type : int, in_description : String = "", in_save : bool = false):
|
||||||
|
name = in_name
|
||||||
|
type = in_type
|
||||||
|
description = in_description
|
||||||
|
save = in_save
|
||||||
|
|
||||||
|
func is_reference() -> bool:
|
||||||
|
return object != null
|
||||||
|
|
||||||
|
func is_alive() -> bool:
|
||||||
|
return object == null or is_instance_valid(object)
|
||||||
|
|
||||||
|
func get_value() -> Variant:
|
||||||
|
if object != null:
|
||||||
|
return object.get_indexed(property)
|
||||||
|
return value
|
||||||
|
|
||||||
|
func set_value(new_value : Variant) -> void:
|
||||||
|
if object != null:
|
||||||
|
object.set_indexed(property, new_value)
|
||||||
|
else:
|
||||||
|
value = new_value
|
||||||
|
|
||||||
|
var theme : Theme
|
||||||
|
var canvas_layer : CanvasLayer = CanvasLayer.new()
|
||||||
|
var v_box_container : VBoxContainer = VBoxContainer.new()
|
||||||
|
|
||||||
|
# If you want to customize the way the console looks, you can direcly modify
|
||||||
|
# the properties of the rich text and line edit here:
|
||||||
|
var rich_label : RichTextLabel = RichTextLabel.new()
|
||||||
|
var panel : Panel = Panel.new()
|
||||||
|
var line_edit : LineEdit = LineEdit.new()
|
||||||
|
|
||||||
|
var console_commands : Dictionary[String, ConsoleCommand] = {}
|
||||||
|
var console_cvars : Dictionary[String, ConsoleCvar] = {}
|
||||||
|
# Pending values are applied when cvars are registered (if loaded from config)
|
||||||
|
var _pending_cvar_values : Dictionary[String, Variant] = {}
|
||||||
|
var command_parameters : Dictionary[String, PackedStringArray] = {}
|
||||||
|
var console_history : Array[String] = []
|
||||||
|
var console_history_index : int = 0
|
||||||
|
var was_paused_already : bool = false
|
||||||
|
|
||||||
|
var tab_string : String = " "
|
||||||
|
var text_block_cache : Array[String]
|
||||||
|
|
||||||
|
## Usage: Console.add_command("command_name", <function to call>, <number of arguments or array of argument names>, <required number of arguments>, "Help description")
|
||||||
|
func add_command(command_name : String, function : Callable, arguments = [], required: int = 0, description : String = "") -> void:
|
||||||
|
if (arguments is int):
|
||||||
|
# Legacy call using an argument number
|
||||||
|
var param_array : PackedStringArray
|
||||||
|
for i in range(arguments):
|
||||||
|
param_array.append("arg_" + str(i + 1))
|
||||||
|
console_commands[command_name] = ConsoleCommand.new(function, param_array, required, description)
|
||||||
|
elif (arguments is Array):
|
||||||
|
# New array argument system
|
||||||
|
var str_args : PackedStringArray
|
||||||
|
for argument in arguments:
|
||||||
|
str_args.append(str(argument))
|
||||||
|
console_commands[command_name] = ConsoleCommand.new(function, str_args, required, description)
|
||||||
|
|
||||||
|
|
||||||
|
## Adds a secret command that will not show up in the help or auto-complete.
|
||||||
|
func add_hidden_command(command_name : String, function : Callable, arguments = [], required : int = 0) -> void:
|
||||||
|
add_command(command_name, function, arguments, required)
|
||||||
|
console_commands[command_name].hidden = true
|
||||||
|
|
||||||
|
|
||||||
|
## Removes a command from the console. This should be called on a script's _exit_tree()
|
||||||
|
## if you have console commands for things that are unloaded before the project closes.
|
||||||
|
func remove_command(command_name : String) -> void:
|
||||||
|
console_commands.erase(command_name)
|
||||||
|
command_parameters.erase(command_name)
|
||||||
|
|
||||||
|
|
||||||
|
## Registers an auto-managed cvar that stores its value internally.
|
||||||
|
## Type the name to print the value, or "name value" to set it. The value is coerced to the
|
||||||
|
## type of default_value. If save is true, the value is persisted to user://console_cvars.txt.
|
||||||
|
## Usage: Console.add_cvar("cl_fov", 90.0, "Field of view.", true)
|
||||||
|
func add_cvar(cvar_name : String, default_value : Variant, description : String = "", save : bool = false) -> void:
|
||||||
|
var cvar := ConsoleCvar.new(cvar_name, typeof(default_value), description, save)
|
||||||
|
cvar.value = default_value
|
||||||
|
console_cvars[cvar_name] = cvar
|
||||||
|
_apply_pending_cvar_value(cvar)
|
||||||
|
|
||||||
|
|
||||||
|
## Registers a cvar that reads from and writes to a property on another object.
|
||||||
|
## The cvar type is inferred from the property's current value. property may be a nested path
|
||||||
|
## (ex: "position:x").
|
||||||
|
## Usage: Console.add_cvar_reference("cl_fov", camera, "fov", "Camera field of view.")
|
||||||
|
func add_cvar_reference(cvar_name : String, object : Object, property : String, description : String = "", save : bool = false) -> void:
|
||||||
|
if not is_instance_valid(object):
|
||||||
|
print_error("Cannot register cvar \"%s\": invalid object." % cvar_name)
|
||||||
|
return
|
||||||
|
var cvar := ConsoleCvar.new(cvar_name, typeof(object.get_indexed(property)), description, save)
|
||||||
|
cvar.object = object
|
||||||
|
cvar.property = property
|
||||||
|
console_cvars[cvar_name] = cvar
|
||||||
|
_apply_pending_cvar_value(cvar)
|
||||||
|
|
||||||
|
|
||||||
|
## Removes a cvar. Call this on _exit_tree() for reference cvars whose object is freed early.
|
||||||
|
func remove_cvar(cvar_name : String) -> void:
|
||||||
|
console_cvars.erase(cvar_name)
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the current value of a cvar, or null if it doesn't exist / its object was freed.
|
||||||
|
func get_cvar(cvar_name : String) -> Variant:
|
||||||
|
if console_cvars.has(cvar_name):
|
||||||
|
var cvar : ConsoleCvar = console_cvars[cvar_name]
|
||||||
|
if cvar.is_alive():
|
||||||
|
return cvar.get_value()
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
## Sets the value of a cvar programmatically (no string coercion) and emits console_cvar_changed.
|
||||||
|
func set_cvar(cvar_name : String, value : Variant) -> void:
|
||||||
|
if console_cvars.has(cvar_name):
|
||||||
|
var cvar : ConsoleCvar = console_cvars[cvar_name]
|
||||||
|
if cvar.is_alive():
|
||||||
|
cvar.set_value(value)
|
||||||
|
console_cvar_changed.emit(cvar_name, value)
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_pending_cvar_value(cvar : ConsoleCvar) -> void:
|
||||||
|
if cvar.save and _pending_cvar_values.has(cvar.name):
|
||||||
|
var saved_value : Variant = _pending_cvar_values[cvar.name]
|
||||||
|
if typeof(saved_value) != cvar.type:
|
||||||
|
saved_value = type_convert(saved_value, cvar.type)
|
||||||
|
cvar.set_value(saved_value)
|
||||||
|
_pending_cvar_values.erase(cvar.name)
|
||||||
|
|
||||||
|
|
||||||
|
## Coerces a string into the given Variant.Type. Returns [success : bool, value : Variant].
|
||||||
|
func _coerce_string_to_type(string_value : String, type : int) -> Array:
|
||||||
|
match type:
|
||||||
|
TYPE_BOOL:
|
||||||
|
var lower := string_value.strip_edges().to_lower()
|
||||||
|
if lower in ["1", "true", "yes", "on"]:
|
||||||
|
return [true, true]
|
||||||
|
if lower in ["0", "false", "no", "off"]:
|
||||||
|
return [true, false]
|
||||||
|
return [false, null]
|
||||||
|
TYPE_INT:
|
||||||
|
var int_string := string_value.strip_edges()
|
||||||
|
if int_string.is_valid_int():
|
||||||
|
return [true, int_string.to_int()]
|
||||||
|
return [false, null]
|
||||||
|
TYPE_FLOAT:
|
||||||
|
var float_string := string_value.strip_edges()
|
||||||
|
if float_string.is_valid_float():
|
||||||
|
return [true, float_string.to_float()]
|
||||||
|
return [false, null]
|
||||||
|
TYPE_STRING:
|
||||||
|
return [true, string_value]
|
||||||
|
TYPE_STRING_NAME:
|
||||||
|
return [true, StringName(string_value)]
|
||||||
|
_:
|
||||||
|
# Complex types (Vector2, etc.) require GDScript literal syntax, ex: "Vector2(1, 2)".
|
||||||
|
var parsed : Variant = str_to_var(string_value)
|
||||||
|
if parsed != null and typeof(parsed) == type:
|
||||||
|
return [true, parsed]
|
||||||
|
return [false, null]
|
||||||
|
|
||||||
|
|
||||||
|
func _print_cvar_value(cvar : ConsoleCvar) -> void:
|
||||||
|
print_line("%s = [system_color color=CONSOLE_COLOR_LITERAL]%s[/system_color]" % [cvar.name, str(cvar.get_value())])
|
||||||
|
|
||||||
|
|
||||||
|
func _handle_cvar(cvar : ConsoleCvar, arguments : PackedStringArray) -> void:
|
||||||
|
if not cvar.is_alive():
|
||||||
|
print_error("Variable \"%s\" references a freed object." % cvar.name)
|
||||||
|
return
|
||||||
|
|
||||||
|
if arguments.is_empty():
|
||||||
|
_print_cvar_value(cvar)
|
||||||
|
if cvar.description:
|
||||||
|
print_line("%s%s" % [tab_string, cvar.description])
|
||||||
|
return
|
||||||
|
|
||||||
|
var raw_value := " ".join(arguments)
|
||||||
|
var result := _coerce_string_to_type(raw_value, cvar.type)
|
||||||
|
if not result[0]:
|
||||||
|
print_error("Invalid value \"%s\" for variable \"%s\" (expected %s)." % [raw_value, cvar.name, type_string(cvar.type)])
|
||||||
|
return
|
||||||
|
|
||||||
|
cvar.set_value(result[1])
|
||||||
|
console_cvar_changed.emit(cvar.name, result[1])
|
||||||
|
_print_cvar_value(cvar)
|
||||||
|
|
||||||
|
|
||||||
|
## Useful if you have a list of possible parameters (ex: level names).
|
||||||
|
func add_command_autocomplete_list(command_name : String, param_list : PackedStringArray):
|
||||||
|
command_parameters[command_name] = param_list
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
var console_history_file := FileAccess.open("user://console_history.txt", FileAccess.READ)
|
||||||
|
if (console_history_file):
|
||||||
|
while (!console_history_file.eof_reached()):
|
||||||
|
var line := console_history_file.get_line()
|
||||||
|
if (line.length()):
|
||||||
|
add_input_history(line)
|
||||||
|
|
||||||
|
# Load persisted cvar values. They're stashed until the matching cvar is registered.
|
||||||
|
var console_cvars_file := FileAccess.open("user://console_cvars.txt", FileAccess.READ)
|
||||||
|
if (console_cvars_file):
|
||||||
|
while (!console_cvars_file.eof_reached()):
|
||||||
|
var line := console_cvars_file.get_line()
|
||||||
|
if (line.length()):
|
||||||
|
var split := line.split(" ", true, 1) # "name var_to_str(value)"
|
||||||
|
if (split.size() == 2):
|
||||||
|
_pending_cvar_values[split[0]] = str_to_var(split[1])
|
||||||
|
|
||||||
|
if ProjectSettings.has_setting(CONSOLE_THEME):
|
||||||
|
theme = load(ProjectSettings.get_setting(CONSOLE_THEME))
|
||||||
|
if theme:
|
||||||
|
v_box_container.theme = theme
|
||||||
|
|
||||||
|
if ProjectSettings.has_setting(CONSOLE_TABSTOP):
|
||||||
|
tab_string = ""
|
||||||
|
for i in range(ProjectSettings.get_setting(CONSOLE_TABSTOP)):
|
||||||
|
tab_string += " "
|
||||||
|
|
||||||
|
canvas_layer.layer = ProjectSettings.get_setting(&"console/canvas_layer", 3)
|
||||||
|
add_child(canvas_layer)
|
||||||
|
console_scale = _get_console_scale_setting()
|
||||||
|
v_box_container.offset_bottom = 0
|
||||||
|
v_box_container.offset_left = 0
|
||||||
|
v_box_container.offset_right = 0
|
||||||
|
v_box_container.offset_top = 0
|
||||||
|
canvas_layer.add_child(v_box_container)
|
||||||
|
panel.size_flags_vertical = Control.SIZE_FILL ^ Control.SIZE_EXPAND
|
||||||
|
v_box_container.add_child(panel)
|
||||||
|
rich_label.selection_enabled = true
|
||||||
|
rich_label.context_menu_enabled = true
|
||||||
|
rich_label.bbcode_enabled = true
|
||||||
|
rich_label.scroll_following = true
|
||||||
|
rich_label.anchor_right = 1.0
|
||||||
|
rich_label.anchor_bottom = 1.0
|
||||||
|
rich_label.install_effect(preload("res://addons/console/system_color.gd").new())
|
||||||
|
panel.add_child(rich_label)
|
||||||
|
rich_label.append_text("Development console.\n")
|
||||||
|
line_edit.anchor_right = 1.0
|
||||||
|
line_edit.placeholder_text = "Enter \"help\" for instructions"
|
||||||
|
if font_size > 0:
|
||||||
|
line_edit.add_theme_font_size_override("font_size", font_size)
|
||||||
|
v_box_container.add_child(line_edit)
|
||||||
|
line_edit.text_submitted.connect(_on_text_entered)
|
||||||
|
line_edit.text_changed.connect(_on_line_edit_text_changed)
|
||||||
|
v_box_container.visible = false
|
||||||
|
process_mode = PROCESS_MODE_ALWAYS
|
||||||
|
|
||||||
|
|
||||||
|
## Get the scale of the console from the settings -- if this is not in the system settings return a default value
|
||||||
|
func _get_console_scale_setting() -> float:
|
||||||
|
if ProjectSettings.has_setting(CONSOLE_SCALE):
|
||||||
|
return ProjectSettings.get_setting(CONSOLE_SCALE)
|
||||||
|
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
|
## Set the console scale
|
||||||
|
func set_console_scale(scale : float):
|
||||||
|
console_scale = scale
|
||||||
|
v_box_container.scale = Vector2(console_scale, console_scale)
|
||||||
|
v_box_container.anchor_right = _get_console_width()
|
||||||
|
v_box_container.anchor_bottom = _get_console_height()
|
||||||
|
|
||||||
|
|
||||||
|
## Get the height of the console - this is related to the scaling of the console
|
||||||
|
func _get_console_height() -> float:
|
||||||
|
if console_full_screen:
|
||||||
|
return 1.0 / console_scale
|
||||||
|
|
||||||
|
if ProjectSettings.has_setting(CONSOLE_HEIGHT):
|
||||||
|
return ProjectSettings.get_setting(CONSOLE_HEIGHT) / console_scale
|
||||||
|
|
||||||
|
return 0.5 / console_scale
|
||||||
|
|
||||||
|
func _get_console_width() -> float:
|
||||||
|
return 1.0 / console_scale
|
||||||
|
|
||||||
|
|
||||||
|
func _set_font_size(value: int) -> void:
|
||||||
|
font_size = value
|
||||||
|
if value > 0:
|
||||||
|
line_edit.add_theme_font_size_override("font_size", font_size)
|
||||||
|
rich_label.add_theme_font_size_override("normal_font_size", font_size)
|
||||||
|
rich_label.add_theme_font_size_override("bold_font_size", font_size)
|
||||||
|
rich_label.add_theme_font_size_override("bold_italics_font_size", font_size)
|
||||||
|
rich_label.add_theme_font_size_override("italics_font_size", font_size)
|
||||||
|
rich_label.add_theme_font_size_override("mono_font_size", font_size)
|
||||||
|
else:
|
||||||
|
line_edit.remove_theme_font_size_override("font_size")
|
||||||
|
rich_label.remove_theme_font_size_override("normal_font_size")
|
||||||
|
rich_label.remove_theme_font_size_override("bold_font_size")
|
||||||
|
rich_label.remove_theme_font_size_override("bold_italics_font_size")
|
||||||
|
rich_label.remove_theme_font_size_override("italics_font_size")
|
||||||
|
rich_label.remove_theme_font_size_override("mono_font_size")
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
var console_history_file := FileAccess.open("user://console_history.txt", FileAccess.WRITE)
|
||||||
|
if (console_history_file):
|
||||||
|
var write_index := 0
|
||||||
|
var start_write_index := console_history.size() - 100 # Max lines to write
|
||||||
|
for line in console_history:
|
||||||
|
if (write_index >= start_write_index):
|
||||||
|
console_history_file.store_line(line)
|
||||||
|
write_index += 1
|
||||||
|
|
||||||
|
var console_cvars_file := FileAccess.open("user://console_cvars.txt", FileAccess.WRITE)
|
||||||
|
if (console_cvars_file):
|
||||||
|
for cvar_name in console_cvars:
|
||||||
|
var cvar : ConsoleCvar = console_cvars[cvar_name]
|
||||||
|
if (cvar.save and cvar.is_alive()):
|
||||||
|
console_cvars_file.store_line("%s %s" % [cvar_name, var_to_str(cvar.get_value())])
|
||||||
|
for pending_name in _pending_cvar_values:
|
||||||
|
if (!console_cvars.has(pending_name)):
|
||||||
|
console_cvars_file.store_line("%s %s" % [pending_name, var_to_str(_pending_cvar_values[pending_name])])
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
v_box_container.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST # For that retro look.
|
||||||
|
add_command("quit", quit, 0, 0, "Quits the game.")
|
||||||
|
add_command("exit", quit, 0, 0, "Quits the game.")
|
||||||
|
add_command("clear", clear, 0, 0, "Clears the text on the console.")
|
||||||
|
add_command("delete_history", delete_history, 0, 0, "Deletes the history of previously entered commands.")
|
||||||
|
add_command("help", help, 0, 0, "Displays instructions on how to use the console.")
|
||||||
|
add_command("commands_list", commands_list, 0, 0, "Lists all commands and their descriptions.")
|
||||||
|
add_command("commands", commands, 0, 0, "Lists commands with no descriptions.")
|
||||||
|
add_command("cvars", cvars, 0, 0, "Lists all console variables and their values.")
|
||||||
|
add_command("calc", calculate, ["mathematical expression to evaluate"], 0, "Evaluates the math passed in for quick arithmetic.")
|
||||||
|
add_command("echo", print_line, ["string"], 1, "Prints given string to the console.")
|
||||||
|
add_command("echo_warning", print_warning, ["string"], 1, "Prints given string as warning to the console.")
|
||||||
|
add_command("echo_info", print_info, ["string"], 1, "Prints given string as info to the console.")
|
||||||
|
add_command("echo_error", print_error, ["string"], 1, "Prints given string as an error to the console.")
|
||||||
|
add_command("pause", pause, 0, 0, "Pauses node processing.")
|
||||||
|
add_command("unpause", unpause, 0, 0, "Unpauses node processing.")
|
||||||
|
add_command("exec", exec, 1, 1, "Execute a script.")
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event : InputEvent) -> void:
|
||||||
|
if (event is InputEventKey):
|
||||||
|
if (event.physical_keycode == KEY_QUOTELEFT): # ~ key.
|
||||||
|
if (event.physical_keycode == KEY_QUOTELEFT and event.is_command_or_control_pressed()): # Toggles console size or opens big console.
|
||||||
|
if (event.pressed):
|
||||||
|
if (v_box_container.visible):
|
||||||
|
toggle_size()
|
||||||
|
else:
|
||||||
|
toggle_console()
|
||||||
|
toggle_size()
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
else:
|
||||||
|
if (event.pressed):
|
||||||
|
toggle_console()
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
elif (event.get_physical_keycode_with_modifiers() == KEY_ESCAPE && v_box_container.visible): # Disable console on ESC
|
||||||
|
if (event.pressed):
|
||||||
|
toggle_console()
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
if (v_box_container.visible and event.pressed):
|
||||||
|
if (event.get_physical_keycode_with_modifiers() == KEY_UP):
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
if (console_history_index > 0):
|
||||||
|
console_history_index -= 1
|
||||||
|
if (console_history_index >= 0):
|
||||||
|
line_edit.text = console_history[console_history_index]
|
||||||
|
line_edit.caret_column = line_edit.text.length()
|
||||||
|
reset_autocomplete()
|
||||||
|
if (event.get_physical_keycode_with_modifiers() == KEY_DOWN):
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
if (console_history_index < console_history.size()):
|
||||||
|
console_history_index += 1
|
||||||
|
if (console_history_index < console_history.size()):
|
||||||
|
line_edit.text = console_history[console_history_index]
|
||||||
|
line_edit.caret_column = line_edit.text.length()
|
||||||
|
reset_autocomplete()
|
||||||
|
else:
|
||||||
|
line_edit.text = ""
|
||||||
|
reset_autocomplete()
|
||||||
|
if (event.get_physical_keycode_with_modifiers() == KEY_PAGEUP):
|
||||||
|
var scroll := rich_label.get_v_scroll_bar()
|
||||||
|
var tween := create_tween()
|
||||||
|
tween.tween_property(scroll, "value", scroll.value - (scroll.page - scroll.page * 0.1), 0.1)
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
if (event.get_physical_keycode_with_modifiers() == KEY_PAGEDOWN):
|
||||||
|
var scroll := rich_label.get_v_scroll_bar()
|
||||||
|
var tween := create_tween()
|
||||||
|
tween.tween_property(scroll, "value", scroll.value + (scroll.page - scroll.page * 0.1), 0.1)
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
if (event.get_physical_keycode_with_modifiers() == KEY_TAB):
|
||||||
|
autocomplete()
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
|
||||||
|
elif event is InputEventMouseButton:
|
||||||
|
if (v_box_container.visible):
|
||||||
|
if (event.is_command_or_control_pressed()):
|
||||||
|
if event.button_index == MOUSE_BUTTON_WHEEL_UP: # Increase font size with ctrl+mouse wheel up
|
||||||
|
font_size = min(128, font_size + 2) # Limit to max of 128
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: # Decrease font size with ctrl+mouse wheel down
|
||||||
|
font_size = max(8, font_size - 2) # Limit to minimum of 8
|
||||||
|
get_tree().get_root().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
|
var suggestions := []
|
||||||
|
var current_suggest := 0
|
||||||
|
var suggesting := false
|
||||||
|
|
||||||
|
func autocomplete() -> void:
|
||||||
|
if (suggesting):
|
||||||
|
for i in range(suggestions.size()):
|
||||||
|
if (current_suggest == i):
|
||||||
|
line_edit.text = str(suggestions[i])
|
||||||
|
line_edit.caret_column = line_edit.text.length()
|
||||||
|
if (current_suggest == suggestions.size() - 1):
|
||||||
|
current_suggest = 0
|
||||||
|
else:
|
||||||
|
current_suggest += 1
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
suggesting = true
|
||||||
|
|
||||||
|
if (" " in line_edit.text): # We're searching for a parameter to autocomplete
|
||||||
|
var split_text := parse_line_input(line_edit.text)
|
||||||
|
if (split_text.size() > 1):
|
||||||
|
var command := split_text[0]
|
||||||
|
var param_input := split_text[1]
|
||||||
|
if (command_parameters.has(command)):
|
||||||
|
for param in command_parameters[command]:
|
||||||
|
if (param_input in param) or (param_input.length() == 0):
|
||||||
|
suggestions.append(str(command, " ", param))
|
||||||
|
else:
|
||||||
|
var sorted_commands := []
|
||||||
|
for command in console_commands:
|
||||||
|
if (!console_commands[command].hidden):
|
||||||
|
sorted_commands.append(str(command))
|
||||||
|
for cvar_name in console_cvars:
|
||||||
|
sorted_commands.append(str(cvar_name))
|
||||||
|
sorted_commands.sort()
|
||||||
|
sorted_commands.reverse()
|
||||||
|
|
||||||
|
var prev_index := 0
|
||||||
|
for command in sorted_commands:
|
||||||
|
if (!line_edit.text || command.contains(line_edit.text)):
|
||||||
|
var index : int = command.find(line_edit.text)
|
||||||
|
if (index <= prev_index):
|
||||||
|
suggestions.push_front(command)
|
||||||
|
else:
|
||||||
|
suggestions.push_back(command)
|
||||||
|
prev_index = index
|
||||||
|
autocomplete()
|
||||||
|
|
||||||
|
|
||||||
|
func reset_autocomplete() -> void:
|
||||||
|
suggestions.clear()
|
||||||
|
current_suggest = 0
|
||||||
|
suggesting = false
|
||||||
|
|
||||||
|
|
||||||
|
func toggle_size() -> void:
|
||||||
|
console_full_screen = !console_full_screen
|
||||||
|
v_box_container.anchor_bottom = _get_console_height()
|
||||||
|
|
||||||
|
|
||||||
|
func disable():
|
||||||
|
enabled = false
|
||||||
|
toggle_console() # Ensure hidden if opened
|
||||||
|
|
||||||
|
|
||||||
|
func enable():
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
|
||||||
|
func toggle_console() -> void:
|
||||||
|
var was_visible := v_box_container.visible
|
||||||
|
if (enabled):
|
||||||
|
v_box_container.visible = !v_box_container.visible
|
||||||
|
else:
|
||||||
|
v_box_container.visible = false
|
||||||
|
|
||||||
|
if (v_box_container.visible):
|
||||||
|
was_paused_already = get_tree().paused
|
||||||
|
get_tree().paused = was_paused_already || pause_enabled
|
||||||
|
line_edit.grab_focus()
|
||||||
|
if was_visible != v_box_container.visible:
|
||||||
|
console_opened.emit()
|
||||||
|
else:
|
||||||
|
scroll_to_bottom()
|
||||||
|
reset_autocomplete()
|
||||||
|
if (pause_enabled && !was_paused_already):
|
||||||
|
get_tree().paused = false
|
||||||
|
if was_visible != v_box_container.visible:
|
||||||
|
console_closed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func is_visible():
|
||||||
|
return v_box_container.visible
|
||||||
|
|
||||||
|
|
||||||
|
func scroll_to_bottom() -> void:
|
||||||
|
var scroll: ScrollBar = rich_label.get_v_scroll_bar()
|
||||||
|
scroll.value = scroll.max_value - scroll.page
|
||||||
|
|
||||||
|
|
||||||
|
func print_error(text : Variant, print_godot := false) -> void:
|
||||||
|
var _color : Color = Color.LIGHT_CORAL
|
||||||
|
if not text is String:
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
print_line("%s[system_color color=CONSOLE_COLOR_ERROR]ERROR:[/system_color] %s" % [tab_string, text], print_godot)
|
||||||
|
|
||||||
|
|
||||||
|
func print_info(text : Variant, print_godot := false) -> void:
|
||||||
|
var _color : Color = Color.LIGHT_BLUE
|
||||||
|
if not text is String:
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
print_line("%s[system_color color=CONSOLE_COLOR_INFO]INFO:[/system_color] %s" % [tab_string, text], print_godot)
|
||||||
|
|
||||||
|
|
||||||
|
func print_warning(text : Variant, print_godot := false) -> void:
|
||||||
|
var _color : Color = Color.LIGHT_GOLDENROD
|
||||||
|
if not text is String:
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
print_line("%s[system_color color=CONSOLE_COLOR_WARNING]WARNING:[/system_color] %s" % [tab_string, text], print_godot)
|
||||||
|
|
||||||
|
func print_line(text : Variant, print_godot := false) -> void:
|
||||||
|
if not text is String:
|
||||||
|
text = str(text)
|
||||||
|
if (!rich_label): # Tried to print something before the console was loaded.
|
||||||
|
call_deferred("print_line", text)
|
||||||
|
else:
|
||||||
|
rich_label.append_text(text)
|
||||||
|
rich_label.append_text("\n")
|
||||||
|
if (print_godot):
|
||||||
|
print_rich(text.dedent())
|
||||||
|
|
||||||
|
|
||||||
|
func parse_line_input(text : String) -> PackedStringArray:
|
||||||
|
var out_array : PackedStringArray
|
||||||
|
var first_char := true
|
||||||
|
var in_quotes := false
|
||||||
|
var escaped := false
|
||||||
|
var token : String
|
||||||
|
for c in text:
|
||||||
|
if (c == '\\'):
|
||||||
|
escaped = true
|
||||||
|
continue
|
||||||
|
elif (escaped):
|
||||||
|
if (c == 'n'):
|
||||||
|
c = '\n'
|
||||||
|
elif (c == 't'):
|
||||||
|
c = '\t'
|
||||||
|
elif (c == 'r'):
|
||||||
|
c = '\r'
|
||||||
|
elif (c == 'a'):
|
||||||
|
c = '\a'
|
||||||
|
elif (c == 'b'):
|
||||||
|
c = '\b'
|
||||||
|
elif (c == 'f'):
|
||||||
|
c = '\f'
|
||||||
|
escaped = false
|
||||||
|
elif (c == '\"'):
|
||||||
|
in_quotes = !in_quotes
|
||||||
|
continue
|
||||||
|
elif (c == ' ' || c == '\t'):
|
||||||
|
if (!in_quotes):
|
||||||
|
out_array.push_back(token)
|
||||||
|
token = ""
|
||||||
|
continue
|
||||||
|
token += c
|
||||||
|
out_array.push_back(token)
|
||||||
|
return out_array
|
||||||
|
|
||||||
|
|
||||||
|
func _on_text_entered(new_text : String) -> void:
|
||||||
|
scroll_to_bottom()
|
||||||
|
reset_autocomplete()
|
||||||
|
line_edit.clear()
|
||||||
|
if (line_edit.has_method(&"edit")):
|
||||||
|
line_edit.call_deferred(&"edit")
|
||||||
|
|
||||||
|
if not new_text.strip_edges().is_empty():
|
||||||
|
add_input_history(new_text)
|
||||||
|
print_line("[i]> " + new_text + "[/i]")
|
||||||
|
var text_split := parse_line_input(new_text)
|
||||||
|
var text_command := text_split[0]
|
||||||
|
|
||||||
|
if console_commands.has(text_command):
|
||||||
|
var arguments := text_split.slice(1)
|
||||||
|
var console_command : ConsoleCommand = console_commands[text_command]
|
||||||
|
|
||||||
|
# calc is a especial command that needs special treatment
|
||||||
|
if (text_command.match("calc")):
|
||||||
|
var expression := ""
|
||||||
|
for word in arguments:
|
||||||
|
expression += word
|
||||||
|
console_command.function.callv([expression])
|
||||||
|
return
|
||||||
|
|
||||||
|
if (arguments.size() < console_command.required):
|
||||||
|
print_error("Too few arguments! Required < %d >" % console_command.required)
|
||||||
|
return
|
||||||
|
elif (arguments.size() > console_command.arguments.size()):
|
||||||
|
arguments.resize(console_command.arguments.size())
|
||||||
|
|
||||||
|
# Functions fail to call if passed the incorrect number of arguments, so fill out with blank strings.
|
||||||
|
while (arguments.size() < console_command.arguments.size()):
|
||||||
|
arguments.append("")
|
||||||
|
|
||||||
|
console_command.function.callv(arguments)
|
||||||
|
elif console_cvars.has(text_command):
|
||||||
|
_handle_cvar(console_cvars[text_command], text_split.slice(1))
|
||||||
|
else:
|
||||||
|
console_unknown_command.emit(text_command)
|
||||||
|
print_error("Unknown command or variable.")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_line_edit_text_changed(new_text : String) -> void:
|
||||||
|
reset_autocomplete()
|
||||||
|
|
||||||
|
|
||||||
|
func quit() -> void:
|
||||||
|
get_tree().quit()
|
||||||
|
|
||||||
|
|
||||||
|
func clear() -> void:
|
||||||
|
rich_label.clear()
|
||||||
|
|
||||||
|
|
||||||
|
func delete_history() -> void:
|
||||||
|
console_history.clear()
|
||||||
|
console_history_index = 0
|
||||||
|
DirAccess.remove_absolute("user://console_history.txt")
|
||||||
|
|
||||||
|
|
||||||
|
func help() -> void:
|
||||||
|
rich_label.append_text(" Built in commands:
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]calc[/system_color]: Calculates a given expresion
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]clear[/system_color]: Clears the registry view
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]commands[/system_color]: Shows a reduced list of all the currently registered commands
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]commands_list[/system_color]: Shows a detailed list of all the currently registered commands
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]cvars[/system_color]: Lists all console variables and their values
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]delete_history[/system_color]: Deletes the commands history
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]echo[/system_color]: Prints a given string to the console
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]echo_error[/system_color]: Prints a given string as an error to the console
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]echo_info[/system_color]: Prints a given string as info to the console
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]echo_warning[/system_color]: Prints a given string as warning to the console
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]pause[/system_color]: Pauses node processing
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]unpause[/system_color]: Unpauses node processing
|
||||||
|
[system_color color=CONSOLE_COLOR_LITERAL]quit[/system_color]: Quits the game
|
||||||
|
Controls:
|
||||||
|
[system_color color=CONSOLE_COLOR_INFO]Up[/system_color] and [system_color color=CONSOLE_COLOR_INFO]Down[/system_color] arrow keys to navigate commands history
|
||||||
|
[system_color color=CONSOLE_COLOR_INFO]PageUp[/system_color] and [system_color color=CONSOLE_COLOR_INFO]PageDown[/system_color] to scroll registry
|
||||||
|
[[system_color color=CONSOLE_COLOR_INFO]Ctrl[/system_color] + [system_color color=CONSOLE_COLOR_INFO]~[/system_color]] to change console size between half screen and full screen
|
||||||
|
[[system_color color=CONSOLE_COLOR_INFO]Ctrl[/system_color] + [system_color color=CONSOLE_COLOR_INFO]Mouse Wheel[/system_color]] up/down to change console font size
|
||||||
|
[system_color color=CONSOLE_COLOR_INFO]~[/system_color] or [system_color color=CONSOLE_COLOR_INFO]Esc[/system_color] key to close the console
|
||||||
|
[system_color color=CONSOLE_COLOR_INFO]Tab[/system_color] key to autocomplete, [system_color system_color=CONSOLE_COLOR_INFO]Tab[/system_color] again to cycle between matching suggestions\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
func calculate(command : String) -> void:
|
||||||
|
var expression := Expression.new()
|
||||||
|
var error = expression.parse(command)
|
||||||
|
if error:
|
||||||
|
print_error("%s" % expression.get_error_text())
|
||||||
|
return
|
||||||
|
var result = expression.execute()
|
||||||
|
if not expression.has_execute_failed():
|
||||||
|
print_line(str(result))
|
||||||
|
else:
|
||||||
|
print_error("%s" % expression.get_error_text())
|
||||||
|
|
||||||
|
|
||||||
|
func commands() -> void:
|
||||||
|
var commands := []
|
||||||
|
for command in console_commands:
|
||||||
|
if (!console_commands[command].hidden):
|
||||||
|
commands.append(str(command))
|
||||||
|
commands.sort()
|
||||||
|
rich_label.append_text(" ")
|
||||||
|
rich_label.append_text(str(commands) + "\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
func commands_list() -> void:
|
||||||
|
var commands := []
|
||||||
|
for command in console_commands:
|
||||||
|
if (!console_commands[command].hidden):
|
||||||
|
commands.append(str(command))
|
||||||
|
commands.sort()
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
var arguments_string := ""
|
||||||
|
var description : String = console_commands[command].description
|
||||||
|
for i in range(console_commands[command].arguments.size()):
|
||||||
|
if i < console_commands[command].required:
|
||||||
|
arguments_string += " [system_color color=CONSOLE_COLOR_ERROR]<" + console_commands[command].arguments[i] + ">[/system_color]"
|
||||||
|
else:
|
||||||
|
arguments_string += " [system_color color=CONSOLE_COLOR_INFO]<" + console_commands[command].arguments[i] + ">[/system_color]"
|
||||||
|
rich_label.append_text(" [system_color color=CONSOLE_COLOR_LITERAL]%s[/system_color]%s: %s\n" % [command, arguments_string, description])
|
||||||
|
rich_label.append_text("\n")
|
||||||
|
|
||||||
|
|
||||||
|
func cvars() -> void:
|
||||||
|
var names := console_cvars.keys()
|
||||||
|
names.sort()
|
||||||
|
for cvar_name in names:
|
||||||
|
var cvar : ConsoleCvar = console_cvars[cvar_name]
|
||||||
|
var value_string := "<invalid>"
|
||||||
|
if (cvar.is_alive()):
|
||||||
|
value_string = str(cvar.get_value())
|
||||||
|
rich_label.append_text(" [system_color color=CONSOLE_COLOR_LITERAL]%s[/system_color] = [system_color color=CONSOLE_COLOR_INFO]%s[/system_color] %s\n" % [cvar_name, value_string, cvar.description])
|
||||||
|
rich_label.append_text("\n")
|
||||||
|
|
||||||
|
|
||||||
|
func add_input_history(text : String) -> void:
|
||||||
|
if (!console_history.size() || text != console_history.back()): # Don't add consecutive duplicates
|
||||||
|
console_history.append(text)
|
||||||
|
console_history_index = console_history.size()
|
||||||
|
|
||||||
|
|
||||||
|
func set_enable_on_release_build(enable : bool):
|
||||||
|
enable_on_release_build = enable
|
||||||
|
if (!enable_on_release_build):
|
||||||
|
if (!OS.is_debug_build()):
|
||||||
|
disable()
|
||||||
|
|
||||||
|
|
||||||
|
func pause() -> void:
|
||||||
|
get_tree().paused = true
|
||||||
|
|
||||||
|
|
||||||
|
func unpause() -> void:
|
||||||
|
get_tree().paused = false
|
||||||
|
|
||||||
|
|
||||||
|
func exec(filename : String) -> void:
|
||||||
|
var path := "user://%s.txt" % [filename]
|
||||||
|
var script := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if (script):
|
||||||
|
while (!script.eof_reached()):
|
||||||
|
_on_text_entered(script.get_line())
|
||||||
|
else:
|
||||||
|
print_error("File %s not found." % [path])
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ouiu5xh1cs8n
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
const SINGLETON_NAME = &"Console"
|
||||||
|
|
||||||
|
##FIXME: These should be maintained in one place.
|
||||||
|
const CONSOLE_THEME : String = &"console/theme"
|
||||||
|
const CONSOLE_SCALE : String = &"console/scale"
|
||||||
|
const CONSOLE_HEIGHT : String = &"console/height"
|
||||||
|
const CONSOLE_COLOR_WARNING : String = &"console/color_warning"
|
||||||
|
const CONSOLE_COLOR_ERROR : String = &"console/color_error"
|
||||||
|
const CONSOLE_COLOR_INFO : String = &"console/color_info"
|
||||||
|
const CONSOLE_COLOR_LITERAL : String = &"console/color_literal"
|
||||||
|
const CONSOLE_TABSTOP : String = &"console/tabstop"
|
||||||
|
const CONSOLE_CANVAS_LAYER : String = &"console/canvas_layer"
|
||||||
|
|
||||||
|
##FIXME: This is here because project settings do no return the default value naturally
|
||||||
|
##This should be fixed in 4.5
|
||||||
|
const color_dictionary : Dictionary[String, Color] = {
|
||||||
|
CONSOLE_COLOR_ERROR: Color.LIGHT_CORAL,
|
||||||
|
CONSOLE_COLOR_INFO: Color.LIGHT_BLUE,
|
||||||
|
CONSOLE_COLOR_LITERAL: Color.PALE_GREEN,
|
||||||
|
CONSOLE_COLOR_WARNING: Color.LIGHT_GOLDENROD
|
||||||
|
}
|
||||||
|
|
||||||
|
func add_setting(setting_name: String, property_info: Dictionary, default: Variant) -> void:
|
||||||
|
if not ProjectSettings.has_setting(setting_name):
|
||||||
|
ProjectSettings.set_setting(setting_name, default)
|
||||||
|
ProjectSettings.add_property_info(property_info)
|
||||||
|
ProjectSettings.set_initial_value(setting_name, default)
|
||||||
|
ProjectSettings.set_as_basic(setting_name, true)
|
||||||
|
|
||||||
|
func _setup_project_settings() -> void:
|
||||||
|
## Configure Console Theme
|
||||||
|
add_setting(CONSOLE_THEME, {
|
||||||
|
"name": CONSOLE_THEME,
|
||||||
|
"type": TYPE_STRING,
|
||||||
|
"hint": PROPERTY_HINT_FILE,
|
||||||
|
"hint_string": "*.tres",
|
||||||
|
}, "")
|
||||||
|
|
||||||
|
## Configure Console Scale
|
||||||
|
add_setting(CONSOLE_SCALE, {
|
||||||
|
"name": CONSOLE_SCALE,
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "0,10,0.1,or_greater"
|
||||||
|
}, 1.0)
|
||||||
|
|
||||||
|
## Configure Console Height
|
||||||
|
add_setting(CONSOLE_HEIGHT, {
|
||||||
|
"name": CONSOLE_HEIGHT,
|
||||||
|
"type": TYPE_FLOAT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "0,1,0.1"
|
||||||
|
}, 0.5)
|
||||||
|
|
||||||
|
## Configure Tab Spaces
|
||||||
|
add_setting(CONSOLE_TABSTOP, {
|
||||||
|
"name": CONSOLE_TABSTOP,
|
||||||
|
"type": TYPE_INT,
|
||||||
|
"hint": PROPERTY_HINT_RANGE,
|
||||||
|
"hint_string": "0,8,1,or_greater"
|
||||||
|
},4)
|
||||||
|
|
||||||
|
## Configure Canvas Layer
|
||||||
|
add_setting(CONSOLE_CANVAS_LAYER, {
|
||||||
|
"name": CONSOLE_CANVAS_LAYER,
|
||||||
|
"type": TYPE_INT,
|
||||||
|
}, 3)
|
||||||
|
|
||||||
|
#Configure Colors
|
||||||
|
add_setting(CONSOLE_COLOR_ERROR, {
|
||||||
|
"name": CONSOLE_COLOR_ERROR,
|
||||||
|
"type": TYPE_COLOR,
|
||||||
|
"hint": PROPERTY_HINT_COLOR_NO_ALPHA,
|
||||||
|
}, color_dictionary[CONSOLE_COLOR_ERROR])
|
||||||
|
|
||||||
|
add_setting(CONSOLE_COLOR_INFO, {
|
||||||
|
"name": CONSOLE_COLOR_INFO,
|
||||||
|
"type": TYPE_COLOR,
|
||||||
|
"hint": PROPERTY_HINT_COLOR_NO_ALPHA,
|
||||||
|
}, color_dictionary[CONSOLE_COLOR_INFO])
|
||||||
|
|
||||||
|
add_setting(CONSOLE_COLOR_WARNING, {
|
||||||
|
"name": CONSOLE_COLOR_WARNING,
|
||||||
|
"type": TYPE_COLOR,
|
||||||
|
"hint": PROPERTY_HINT_COLOR_NO_ALPHA,
|
||||||
|
}, color_dictionary[CONSOLE_COLOR_WARNING])
|
||||||
|
|
||||||
|
add_setting(CONSOLE_COLOR_LITERAL, {
|
||||||
|
"name": CONSOLE_COLOR_LITERAL,
|
||||||
|
"type": TYPE_COLOR,
|
||||||
|
"hint": PROPERTY_HINT_COLOR_NO_ALPHA
|
||||||
|
}, color_dictionary[CONSOLE_COLOR_LITERAL])
|
||||||
|
|
||||||
|
ProjectSettings.save()
|
||||||
|
|
||||||
|
|
||||||
|
func _enable_plugin() -> void:
|
||||||
|
add_autoload_singleton(SINGLETON_NAME, "res://addons/console/console.gd")
|
||||||
|
_setup_project_settings()
|
||||||
|
print("Console plugin enabled.")
|
||||||
|
|
||||||
|
|
||||||
|
func _disable_plugin() -> void:
|
||||||
|
remove_autoload_singleton(SINGLETON_NAME)
|
||||||
|
print("Console plugin disabled.")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cv2joe2dgkub1
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Godot;
|
||||||
|
using Godot.Collections;
|
||||||
|
|
||||||
|
public partial class Console : Node{
|
||||||
|
static GodotObject console = (GodotObject)GD.Load<GDScript>("uid://ouiu5xh1cs8n").New();
|
||||||
|
|
||||||
|
public static bool Enabled{ get{return console.Get("enabled").AsBool();} }
|
||||||
|
public static bool EnabledOnReleaseBuild{ get{return console.Get("enabled_on_release_build").AsBool();} }
|
||||||
|
public static bool PauseEnabled{ get{return console.Get("pause_enabled").AsBool();} set{console.Set("pause_enabled", value);} }
|
||||||
|
public static int FontSize{ get{return console.Get("font_size").AsInt32();} set{console.Set("font_size", value);} }
|
||||||
|
|
||||||
|
public static RichTextLabel RichLabel{ get{return (RichTextLabel)console.Get("rich_label").AsGodotObject();} set{console.Set("rich_label", value);} }
|
||||||
|
public static LineEdit LineEdit{ get{return (LineEdit)console.Get("line_edit").AsGodotObject();} set{console.Set("line_edit", value);} }
|
||||||
|
|
||||||
|
public override void _Ready(){
|
||||||
|
AddChild((Node)console);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ConnectConsoleOpened(Callable callable) => console.Connect("console_opened", callable);
|
||||||
|
public static void DisconnectConsoleOpened(Callable callable) => console.Disconnect("console_opened", callable);
|
||||||
|
public static void ConnectConsoleClosed(Callable callable) => console.Connect("console_closed", callable);
|
||||||
|
public static void DisconnectConsoleClosed(Callable callable) => console.Disconnect("console_closed", callable);
|
||||||
|
public static void ConnectConsoleUnknownCommand(Callable callable) => console.Connect("console_unknown_command", callable);
|
||||||
|
public static void DisconnectConsoleUnknownCommand(Callable callable) => console.Disconnect("console_unknown_command", callable);
|
||||||
|
public static void ConnectConsoleCvarChanged(Callable callable) => console.Connect("console_cvar_changed", callable);
|
||||||
|
public static void DisconnectConsoleCvarChanged(Callable callable) => console.Disconnect("console_cvar_changed", callable);
|
||||||
|
|
||||||
|
public static void AddCommand(string commandName, Callable function, Array<string> arguments, int required = 0, string description = "") => console.Call("add_command", commandName, function, arguments, required, description);
|
||||||
|
public static void AddHiddenCommand(string commandName, Callable function, Array<string> arguments, int required) => console.Call("add_hidden_command", commandName, function, arguments, required);
|
||||||
|
public static void RemoveCommand(string commandName) => console.Call("remove_command", commandName);
|
||||||
|
public static void AddCvar(string cvarName, Variant defaultValue, string description = "", bool save = false) => console.Call("add_cvar", cvarName, defaultValue, description, save);
|
||||||
|
public static void AddCvarReference(string cvarName, GodotObject @object, string property, string description = "", bool save = false) => console.Call("add_cvar_reference", cvarName, @object, property, description, save);
|
||||||
|
public static void RemoveCvar(string cvarName) => console.Call("remove_cvar", cvarName);
|
||||||
|
public static Variant GetCvar(string cvarName) => console.Call("get_cvar", cvarName);
|
||||||
|
public static void SetCvar(string cvarName, Variant value) => console.Call("set_cvar", cvarName, value);
|
||||||
|
public static void AddCommandAutocompleteList(string commandName, string[] paramList) => console.Call("add_command_autocomplete_list", commandName, paramList);
|
||||||
|
public static void Disable() => console.Call("disable");
|
||||||
|
public static void Enable() =>console.Call("enable");
|
||||||
|
public static void ToggleConsole() => console.Call("toggle_console");
|
||||||
|
public static void IsVisible() => console.Call("is_visible");
|
||||||
|
public static void ScrollToBottom() => console.Call("scroll_to_bottom");
|
||||||
|
public static void PrintError(Variant text, bool printGodot = false) => console.Call("print_error", text, printGodot);
|
||||||
|
public static void PrintInfo(Variant text, bool printGodot = false) => console.Call("print_info", text, printGodot);
|
||||||
|
public static void PrintWarning(Variant text, bool printGodot = false) => console.Call("print_warning", text, printGodot);
|
||||||
|
public static void PrintLine(Variant text, bool printGodot = false) => console.Call("print_line", text, printGodot);
|
||||||
|
public static void SetEnableOnReleaseBuild(bool enable) => console.Call("set_enable_on_release_build", enable);
|
||||||
|
|
||||||
|
/*
|
||||||
|
public static void Autocomplete() => console.Call("autocomplete");
|
||||||
|
public static void ResetAutocomplete() => console.Call("reset_autocomplete");
|
||||||
|
public static void ToggleSize() => console.Call("toggle_size");
|
||||||
|
public static string[] ParseLineInput(string text){ return console.Call("parse_line_input", text).AsStringArray(); }
|
||||||
|
public static void Quit() => console.Call("quit");
|
||||||
|
public static void Clear() => console.Call("clear");
|
||||||
|
public static void DeleteHistory() => console.Call("delete_history");
|
||||||
|
public static void Help() => console.Call("help");
|
||||||
|
public static void Calculate() => console.Call("calculate");
|
||||||
|
public static void Commands() => console.Call("commands");
|
||||||
|
public static void CommandsList() => console.Call("commands_list");
|
||||||
|
public static void Cvars() => console.Call("cvars");
|
||||||
|
public static void Pause() => console.Call("pause");
|
||||||
|
public static void Unpause() => console.Call("unpause");
|
||||||
|
public static void Exec(string fileName) => console.Call("exec", filename);
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bdi2oxut3dsra
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="Developer Console"
|
||||||
|
description="Developer console. Press ~ to activate it in game and execute commands."
|
||||||
|
author="jitspoe"
|
||||||
|
version="1.5.0"
|
||||||
|
script="console_plugin.gd"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@tool
|
||||||
|
extends RichTextEffect
|
||||||
|
class_name SystemColor
|
||||||
|
|
||||||
|
var bbcode : String = "system_color"
|
||||||
|
|
||||||
|
func _process_custom_fx(char_fx : CharFXTransform):
|
||||||
|
var _system_color = char_fx.env.get("color")
|
||||||
|
|
||||||
|
if not _system_color:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not _system_color in Console:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Convert string to const value
|
||||||
|
_system_color = Console.get_script().get_script_constant_map()[_system_color]
|
||||||
|
|
||||||
|
var _color : Color = Console.color_dictionary[_system_color]
|
||||||
|
|
||||||
|
if ProjectSettings.has_setting(_system_color):
|
||||||
|
_color = ProjectSettings.get_setting(_system_color)
|
||||||
|
|
||||||
|
if not _color:
|
||||||
|
return false
|
||||||
|
|
||||||
|
char_fx.color = _color
|
||||||
|
|
||||||
|
return true
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cw62jxvqhw333
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
[runnable_presets]
|
[runnable_presets]
|
||||||
|
|
||||||
Web="Web"
|
Web="Web"
|
||||||
|
Linux="Linux"
|
||||||
|
"Windows Desktop"="Windows Desktop"
|
||||||
|
|
||||||
[preset.0]
|
[preset.0]
|
||||||
|
|
||||||
@@ -53,3 +55,120 @@ threads/godot_pool_size=4
|
|||||||
dotnet/include_scripts_content=true
|
dotnet/include_scripts_content=true
|
||||||
dotnet/include_debug_symbols=true
|
dotnet/include_debug_symbols=true
|
||||||
dotnet/embed_build_outputs=true
|
dotnet/embed_build_outputs=true
|
||||||
|
|
||||||
|
[preset.1]
|
||||||
|
|
||||||
|
name="Linux"
|
||||||
|
platform="Linux"
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter="Build/*"
|
||||||
|
export_path="Build/scavgame.x86_64"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.1.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=true
|
||||||
|
binary_format/architecture="x86_64"
|
||||||
|
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="#!/usr/bin/env bash
|
||||||
|
export DISPLAY=:0
|
||||||
|
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||||
|
\"{temp_dir}/{exe_name}\" {cmd_args}"
|
||||||
|
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||||
|
pkill -x -f \"{temp_dir}/{exe_name} {cmd_args}\"
|
||||||
|
rm -rf \"{temp_dir}\""
|
||||||
|
|
||||||
|
[preset.2]
|
||||||
|
|
||||||
|
name="Windows Desktop"
|
||||||
|
platform="Windows Desktop"
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter="Build/*"
|
||||||
|
export_path="Build/scavgame.exe"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
patch_delta_encoding=false
|
||||||
|
patch_delta_compression_level_zstd=19
|
||||||
|
patch_delta_min_reduction=0.1
|
||||||
|
patch_delta_include_filters="*"
|
||||||
|
patch_delta_exclude_filters=""
|
||||||
|
encryption_include_filters=""
|
||||||
|
encryption_exclude_filters=""
|
||||||
|
seed=0
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.2.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}'"
|
||||||
|
|||||||
+16
-1
@@ -11,21 +11,32 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="scavgame"
|
config/name="scavgame"
|
||||||
|
config/version="1.0.0"
|
||||||
run/main_scene="uid://cjwfyvtoeaqid"
|
run/main_scene="uid://cjwfyvtoeaqid"
|
||||||
config/use_custom_user_dir=true
|
config/use_custom_user_dir=true
|
||||||
|
config/custom_user_dir_name="Astronand/scavgamedemo"
|
||||||
config/features=PackedStringArray("4.7")
|
config/features=PackedStringArray("4.7")
|
||||||
boot_splash/show_image=false
|
boot_splash/show_image=false
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
Console="*uid://ouiu5xh1cs8n"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/size/viewport_width=1920
|
window/size/viewport_width=1920
|
||||||
window/size/viewport_height=1080
|
window/size/viewport_height=1080
|
||||||
|
window/size/borderless=true
|
||||||
|
|
||||||
[dotnet]
|
[dotnet]
|
||||||
|
|
||||||
project/assembly_name="Scavenger"
|
project/assembly_name="Scavenger"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/console/plugin.cfg")
|
||||||
|
|
||||||
[layer_names]
|
[layer_names]
|
||||||
|
|
||||||
3d_physics/layer_1="Physics"
|
3d_physics/layer_1="Physics"
|
||||||
@@ -38,4 +49,8 @@ project/assembly_name="Scavenger"
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
renderer/rendering_method="mobile"
|
renderer/rendering_method="gl_compatibility"
|
||||||
|
|
||||||
|
[webrtc]
|
||||||
|
|
||||||
|
default_extension="res://addons/webrtc_native/webrtc_native.gdextension"
|
||||||
|
|||||||
Reference in New Issue
Block a user